diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index 755f8b8..0000000 --- a/.browserslistrc +++ /dev/null @@ -1 +0,0 @@ -IE 10 diff --git a/.claude/tasks/context_session_a9973aa3-2e87-420a-9c57-c48fd5f3a2d4.md b/.claude/tasks/context_session_a9973aa3-2e87-420a-9c57-c48fd5f3a2d4.md new file mode 100644 index 0000000..f7bad6a --- /dev/null +++ b/.claude/tasks/context_session_a9973aa3-2e87-420a-9c57-c48fd5f3a2d4.md @@ -0,0 +1,45 @@ +# Session Context: TypeScript Rewrite Plan + +## Session ID +a9973aa3-2e87-420a-9c57-c48fd5f3a2d4 + +## Goal +Create a comprehensive implementation plan to rewrite the ABSmartly JavaScript SDK in TypeScript from scratch. + +## What Was Done +1. Created git worktree `worktree-typescript-rewrite` based on `main` +2. Thoroughly explored the entire codebase (43 source files, 34 test files, all config) +3. Read every source file, understanding all types, APIs, and algorithms +4. Read all 20 JsonExpr operators +5. Read all 14 test files to understand test patterns and fixture data +6. Wrote comprehensive 17-task implementation plan + +## Plan Location +`docs/superpowers/plans/2026-03-28-typescript-rewrite.md` + +## Key Decisions +- **Drop legacy support**: Node 18+ / modern browsers only (was Node 6+ / IE 10+) +- **Drop dependencies**: node-fetch, rfdc, core-js (use native fetch, structuredClone) +- **Drop polyfills**: fetch-shim, abort-controller-shim, platform detection wrappers +- **Drop Babel**: Use tsup (esbuild-based) for bundling ESM + CJS + browser IIFE +- **Test framework**: Vitest instead of Jest +- **Keep identical**: Public API, hashing algorithms, variant assignment, client retry logic + +## Task Breakdown (17 tasks) +1. Project scaffolding & build system (package.json, tsconfig, tsup, vitest) +2. Types & errors +3. Murmur3 hash +4. MD5 hash +5. Hashing utilities +6. Core utilities (isObject, isEqualsDeep, etc.) +7. Variant assigner +8. JsonExpr evaluator +9. JsonExpr operators (all 20 in one file) +10. JsonExpr facade & AudienceMatcher +11. HTTP client with retry +12. Provider & publisher +13. Context class (largest module) +14. SDK class +15. Config merge utility +16. Public API & index exports +17. Full integration & cleanup diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index ffd3563..0000000 --- a/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -root = true - -[*.{json,js,sh}] -end_of_line = lf -insert_final_newline = true -indent_style = tab -indent_size = 4 -trim_trailing_whitespace = true -charset = utf-8 - diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 12289b4..0000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -/dist/ -/src/js/ diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index f5c917c..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,58 +0,0 @@ -module.exports = { - root: true, - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint"], - env: { - browser: true, - node: true, - es6: true, - jest: true, - }, - extends: [ - "eslint:recommended", - "prettier", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - ], - rules: { - "@typescript-eslint/no-empty-function": "off", - "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", - "no-template-curly-in-string": "error", - "no-promise-executor-return": "error", - "no-useless-backreference": "error", - // "require-atomic-updates": "error", - "array-callback-return": "error", - "block-scoped-var": "error", - "class-methods-use-this": "off", - "consistent-return": "error", - "default-case": ["error", { commentPattern: "^skip|no\\s+default" }], - "default-param-last": "error", - "dot-location": ["error", "property"], - eqeqeq: ["error", "smart"], - "no-alert": "error", - "no-constructor-return": "error", - "no-else-return": "error", - "no-extend-native": "error", - "no-extra-label": "error", - "no-invalid-this": "error", - "no-loop-func": "error", - "no-return-assign": "error", - "no-return-await": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-throw-literal": "error", - "no-useless-concat": "error", - "no-useless-return": "error", - "no-void": "error", - "wrap-iife": ["error", "inside"], - "no-shadow": ["error", { builtinGlobals: true, hoist: "never" }], - "no-use-before-define": "off", - "no-var": "error", - "prefer-numeric-literals": "error", - "prefer-const": "warn", - "prefer-arrow-callback": "error", - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "error", - }, -}; diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 785efb8..0000000 --- a/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -coverage -dist -es -lib diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 627187b..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "printWidth": 120, - "useTabs": true, - "semi": true, - "singleQuote": false, - "jsxSingleQuote": false -} diff --git a/README.md b/README.md index be4a960..faad2de 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,64 @@ # A/B Smartly SDK [![npm version](https://badge.fury.io/js/%40absmartly%2Fjavascript-sdk.svg)](https://badge.fury.io/js/%40absmartly%2Fjavascript-sdk) -A/B Smartly - JavaScript SDK +A/B Smartly - JavaScript/TypeScript SDK ## Compatibility -The A/B Smartly Javascript SDK is an isomorphic library for Node.js (CommonJS and ES6) and browsers (UMD). +The A/B Smartly JavaScript SDK is an isomorphic TypeScript library for Node.js (ESM and CommonJS) and browsers (IIFE). -It's supported on Node.js version 6.x and npm 3.x or later. +### Modern (default, zero dependencies) +- **Node.js 18+** - uses native `fetch` and `AbortController` +- **All modern browsers** - Chrome, Firefox, Safari, Edge -It's supported on IE 10+ and all the other major browsers. +### Legacy Node.js (14-17) +Supported via optional polyfill injection. No extra dependencies are bundled - you provide your own: -**Note**: IE 10 does not natively support Promises. -If you target IE 10, you must include a polyfill like [es6-promise](https://www.npmjs.com/package/es6-promise) or [rsvp](https://www.npmjs.com/package/rsvp). +```typescript +import fetch from "node-fetch"; +import { AbortController } from "abort-controller"; + +const sdk = new SDK({ + endpoint: "https://sandbox.absmartly.io/v1", + apiKey: process.env.ABSMARTLY_API_KEY, + environment: "production", + application: "website", + fetchImpl: fetch, + AbortControllerImpl: AbortController, +}); +``` + +### Legacy Browsers +A pre-built legacy bundle transpiled to ES2015 is available at `dist/index.legacy.js`. + +**Important:** This is an ES2015 build, not an ES5 build. It does **not** support IE10/IE11. + +If you need legacy browser support, you must provide polyfills for missing APIs: + +| API | Polyfill | +|---|---| +| `Promise` | [es6-promise](https://www.npmjs.com/package/es6-promise) | +| `fetch` | [whatwg-fetch](https://www.npmjs.com/package/whatwg-fetch) | +| `AbortController` | [abortcontroller-polyfill](https://www.npmjs.com/package/abortcontroller-polyfill) | + +```html + + + + + + + +``` + +### Build Outputs + +| File | Target | Use case | +|---|---|---| +| `dist/index.js` | ES2022 ESM | Modern bundlers (Vite, webpack, Rollup) | +| `dist/index.cjs` | ES2022 CJS | Node.js `require()` | +| `dist/index.global.js` | ES2022 IIFE | Modern browsers via ` + +``` -Simply add the following code to your `head` section to include the latest published version. +#### Directly in the browser (legacy / ES2015-capable browsers) ```html - + + + ``` ## Getting Started @@ -43,10 +99,9 @@ Please follow the [installation](#installation) instructions before trying the f #### Initialization This example assumes an Api Key, an Application, and an Environment have been created in the A/B Smartly web console. -```javascript -// somewhere in your application initialization code -const sdk = new absmartly.SDK({ - endpoint: 'https://sandbox.absmartly.io/v1', +```typescript +const sdk = new SDK({ + endpoint: "https://sandbox.absmartly.io/v1", apiKey: process.env.ABSMARTLY_API_KEY, environment: process.env.NODE_ENV, application: process.env.APPLICATION_NAME, @@ -54,54 +109,69 @@ const sdk = new absmartly.SDK({ ``` The `application` option can also be an object with `name` and `version` to track which version of your application is generating events. The version can be a number or a semver string: -```javascript -const sdk = new absmartly.SDK({ - endpoint: 'https://sandbox.absmartly.io/v1', +```typescript +const sdk = new SDK({ + endpoint: "https://sandbox.absmartly.io/v1", apiKey: process.env.ABSMARTLY_API_KEY, environment: process.env.NODE_ENV, - application: { name: 'website', version: '1.2.3' }, + application: { name: "website", version: "1.2.3" }, }); ``` -#### Creating a new Context with raw promises -```javascript -// define a new context request -const request = { +#### SDK Options + +| Option | Type | Required | Description | +|---|---|---|---| +| `endpoint` | `string` | Yes | A/B Smartly API endpoint | +| `apiKey` | `string` | Yes | API key from the web console | +| `environment` | `string` | Yes | Environment name (e.g. `"production"`) | +| `application` | `string \| { name, version }` | Yes | Application name or object | +| `agent` | `string` | No | Custom agent identifier | +| `retries` | `number` | No | Number of retries (default: `5`) | +| `timeout` | `number` | No | Request timeout in ms (default: `3000`) | +| `keepalive` | `boolean` | No | Enable keep-alive (default: `true`) | +| `fetchImpl` | `typeof fetch` | No | Custom fetch implementation for legacy environments | +| `AbortControllerImpl` | `typeof AbortController` | No | Custom AbortController for legacy environments | +| `client` | `Client` | No | Custom HTTP client implementing the `Client` interface | +| `eventLogger` | `EventLogger` | No | Custom event logger callback | +| `publisher` | `ContextPublisher` | No | Custom context publisher | +| `provider` | `ContextDataProvider` | No | Custom context data provider | + +#### Creating a new Context with promises +```typescript +const context = sdk.createContext({ units: { - session_id: '5ebf06d8cb5d8137290c4abb64155584fbdb64d8', + session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8", }, -}; - -// create context with raw promises -const context = sdk.createContext(request); +}); -context.ready().then((response) => { - console.log("ABSmartly Context ready!") -}).catch((error) => { - console.log(error); +context.ready().then(() => { + if (context.isFailed()) { + console.error("Context failed to initialize:", context.readyError()); + // Context is still usable — all treatments return control (variant 0) + } + console.log("ABSmartly Context ready!"); }); ``` #### Creating a new Context with async/await -```javascript -// define a new context request -const request = { +```typescript +const context = sdk.createContext({ units: { - session_id: '5ebf06d8cb5d8137290c4abb64155584fbdb64d8', + session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8", }, -}; +}); -// create context with raw promises -const context = sdk.createContext(request); +await context.ready(); -try { - await context.ready(); - console.log("ABSmartly Context ready!") -} catch (error) { - console.log(error); +if (context.isFailed()) { + console.error("Context failed to initialize:", context.readyError()); + // Context is still usable — all treatments return control (variant 0) } ``` +> **Note:** `ready()` always resolves to `true`, even when initialization fails. A failed context is still "ready" — it simply has no experiment data, so all `treatment()` calls return `0` (control). Use `isFailed()` to check if initialization failed and `readyError()` to inspect the error. + #### Creating a new Context with pre-fetched data When doing full-stack experimentation with A/B Smartly, we recommend creating a context only once on the server-side. Creating a context involves a round-trip to the A/B Smartly event collector. @@ -109,17 +179,14 @@ We can avoid repeating the round-trip on the client-side by sending the server-s Then we can initialize the A/B Smartly context on the client-side directly with it. ```html - - - + + + ``` #### Setting extra units for a context @@ -128,8 +195,8 @@ This method may be used for example, when a user logs in to your application, an Please note that **you cannot override an already set unit type** as that would be a change of identity, and will throw an exception. In this case, you must create a new context instead. The `unit()` and `units()` methods can be called before the context is ready. -```javascript -context.unit('db_user_id', 1000013); +```typescript +context.unit("db_user_id", 1000013); // or context.units({ @@ -139,11 +206,11 @@ context.units({ #### Setting context attributes The `attribute()` and `attributes()` methods can be called before the context is ready. -```javascript -context.attribute('user_agent', navigator.userAgent); +```typescript +context.attribute("user_agent", navigator.userAgent); context.attributes({ - customer_age: 'new_customer', + customer_age: "new_customer", }); ``` @@ -151,7 +218,7 @@ context.attributes({ You can opt in to automatically include system attributes (SDK name, SDK version, application, environment, and application version) in every publish payload. These are sent as context attributes and can be useful for debugging and filtering in the Web Console. To enable this, set the `includeSystemAttributes` option to `true` when creating the context: -```javascript +```typescript const context = sdk.createContext(request, { includeSystemAttributes: true, }); @@ -160,9 +227,9 @@ const context = sdk.createContext(request, { When enabled, the following attributes are automatically prepended to the publish request payload: | Attribute | Description | -|:--- |---| +|:---|---| | `sdk_name` | The SDK agent name (e.g. `"absmartly-javascript-sdk"`) | -| `sdk_version` | The SDK version (e.g. `"1.13.4"`) | +| `sdk_version` | The SDK version (e.g. `"2.0.0"`) | | `application` | The application name from the SDK configuration | | `environment` | The environment from the SDK configuration | | `app_version` | The application version, only included if greater than `0` | @@ -170,17 +237,29 @@ When enabled, the following attributes are automatically prepended to the publis These system attributes are prepended before any user-defined attributes. #### Selecting a treatment -```javascript -if (context.treament("exp_test_experiment") == 0) { +```typescript +if (context.treatment("exp_test_experiment") === 0) { // user is in control group (variant 0) } else { // user is in treatment group } ``` +#### Accessing experiment variables +Experiment variables allow you to configure per-variant values directly from the A/B Smartly web console. + +```typescript +const buttonColor = context.variableValue("button.color", "grey"); // "grey" is the default +``` + +Use `peekVariableValue()` to access a variable without triggering an exposure: +```typescript +const buttonColor = context.peekVariableValue("button.color", "grey"); +``` + #### Tracking a goal achievement Goals are created in the A/B Smartly web console. -```javascript +```typescript context.track("payment", { item_count: 1, total_amount: 1999.99 }); ``` @@ -188,44 +267,37 @@ context.track("payment", { item_count: 1, total_amount: 1999.99 }); Sometimes it is necessary to ensure all events have been published to the A/B Smartly collector, before proceeding. One such case is when the user is about to navigate away right before being exposed to a treatment. You can explicitly call the `publish()` method, which returns a promise, before navigating away. -```javascript -await context.publish().then(() => { - window.location = "https://www.absmartly.com" -}) +```typescript +await context.publish(); +window.location = "https://www.absmartly.com"; ``` #### Finalizing The `finalize()` method will ensure all events have been published to the A/B Smartly collector, like `publish()`, and will also "seal" the context, throwing an error if any method that could generate an event is called. -```javascript -await context.finalize().then(() => { - window.location = "https://www.absmartly.com" -}) +```typescript +await context.finalize(); +window.location = "https://www.absmartly.com"; ``` #### Refreshing the context with fresh experiment data For long-running single-page-applications (SPA), the context is usually created once when the application is first reached. However, any experiments being tracked in your production code, but started after the context was created, will not be triggered. -To mitigate this, we can use the `refreshInterval` option when creating the context. +To mitigate this, we can use the `refreshPeriod` option when creating the context. -```javascript -const request = { - units: { - session_id: '5ebf06d8cb5d8137290c4abb64155584fbdb64d8', - }, -}; - -const context = sdk.createContext(request, { - refreshInterval: 5 * 60 * 1000 -}); +```typescript +const context = sdk.createContext( + { units: { session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8" } }, + { refreshPeriod: 5 * 60 * 1000 }, +); ``` Alternatively, the `refresh()` method can be called manually. The `refresh()` method pulls updated experiment data from the A/B Smartly collector and will trigger recently started experiments when `treatment()` is called again. -```javascript +```typescript setTimeout(async () => { try { - context.refresh(); - } catch(error) { + await context.refresh(); + } catch (error) { console.error(error); } }, 5 * 60 * 1000); @@ -235,14 +307,14 @@ setTimeout(async () => { The A/B Smartly SDK can be instantiated with an event logger used for all contexts. In addition, an event logger can be specified when creating a particular context, in the `createContext` call options. The example below illustrates this with the implementation of the default event logger, used if none is specified. -```javascript -const sdk = new absmartly.SDK({ - endpoint: 'https://sandbox-api.absmartly.com/v1', +```typescript +const sdk = new SDK({ + endpoint: "https://sandbox-api.absmartly.com/v1", apiKey: process.env.ABSMARTLY_API_KEY, environment: process.env.NODE_ENV, application: process.env.APPLICATION_NAME, eventLogger: (context, eventName, data) => { - if (eventName == "error") { + if (eventName === "error") { console.error(data); } }, @@ -253,7 +325,7 @@ The data parameter depends on the type of event. Currently, the SDK logs the following events: | eventName | when | data | -|:---: |---|---| +|:---:|---|---| | `"error"` | `Context` receives an error | error object thrown | | `"ready"` | `Context` turns ready | data used to initialize the context | | `"refresh"` | `Context.refresh()` method succeeds | data used to refresh the context | @@ -262,13 +334,14 @@ Currently, the SDK logs the following events: | `"goal"` | `Context.track()` method succeeds | goal data enqueued for publishing | | `"finalize"` | `Context.finalize()` method succeeds the first time | undefined | +> **Note:** The event logger is wrapped in a try/catch by the SDK. A broken logger will not crash SDK operations. #### Peek at treatment variants Although generally not recommended, it is sometimes necessary to peek at a treatment without triggering an exposure. The A/B Smartly SDK provides a `peek()` method for that. -```javascript -if (context.peek("exp_test_experiment") == 0) { +```typescript +if (context.peek("exp_test_experiment") === 0) { // user is in control group (variant 0) } else { // user is in treatment group @@ -278,38 +351,89 @@ if (context.peek("exp_test_experiment") == 0) { #### Overriding treatment variants During development, for example, it is useful to force a treatment for an experiment. This can be achieved with the `override()` and/or `overrides()` methods. The `override()` and `overrides()` methods can be called before the context is ready. -```javascript - context.override("exp_test_experiment", 1); // force variant 1 of treatment - context.overrides({ - exp_test_experiment: 1, - exp_another_experiment: 0, - }); +```typescript +context.override("exp_test_experiment", 1); // force variant 1 of treatment +context.overrides({ + exp_test_experiment: 1, + exp_another_experiment: 0, +}); +``` + +#### Custom fields +Experiments can have custom field values configured in the A/B Smartly web console. +```typescript +const keys = context.customFieldKeys(); +const value = context.customFieldValue("exp_test_experiment", "country"); +const type = context.customFieldValueType("exp_test_experiment", "country"); +``` + +#### Error handling +The SDK provides typed error classes for programmatic error handling: + +```typescript +import { + ABSmartlyError, + ContextNotReadyError, + ContextFinalizedError, + TimeoutError, + RetryError, +} from "@absmartly/javascript-sdk"; + +try { + context.treatment("exp_test"); +} catch (error) { + if (error instanceof ContextNotReadyError) { + // Context not ready yet — await context.ready() first + } else if (error instanceof ContextFinalizedError) { + // Context has been finalized — create a new one + } +} +``` + +#### Custom Client (Dependency Injection) +You can provide your own HTTP client implementation by implementing the `Client` interface: + +```typescript +import type { Client } from "@absmartly/javascript-sdk"; + +class MyCustomClient implements Client { + async getContext(options?) { /* ... */ } + async publish(params, options?) { /* ... */ } + getAgent() { return "my-custom-client"; } + getApplication() { return { name: "my-app", version: "1.0.0" }; } + getEnvironment() { return "production"; } +} + +const sdk = new SDK({ + client: new MyCustomClient(), + // No need for endpoint, apiKey, etc. when providing your own client +}); ``` +Similarly, `ContextDataProvider` and `ContextPublisher` interfaces can be implemented for custom data fetching and publishing strategies. + #### HTTP request timeout It is possible to set a timeout per individual HTTP request, overriding the global timeout set for all request when instantiating the SDK object. Here is an example of setting a timeout only for the createContext request. -```javascript +```typescript const context = sdk.createContext(request, { - refreshInterval: 5 * 60 * 1000 + refreshPeriod: 5 * 60 * 1000, }, { - timeout: 1500 + timeout: 1500, }); ``` #### HTTP Request cancellation -Sometimes it is useful to cancel an inflight HTTP request, for example, when the user is navigating away. The A/B Smartly SDK also supports a cancellation via an `AbortSignal`. An implementation of AbortController is provided for older platforms, but will use the native implementation where available. - -Here is an example of a cancellation scenario. +Sometimes it is useful to cancel an inflight HTTP request, for example, when the user is navigating away. The A/B Smartly SDK supports cancellation via an `AbortSignal`. -```javascript -const controller = new absmartly.AbortController(); +```typescript +const controller = new AbortController(); const context = sdk.createContext(request, { - refreshInterval: 5 * 60 * 1000 + refreshPeriod: 5 * 60 * 1000, }, { - signal: controller.signal + signal: controller.signal, }); // abort request if not ready after 1500ms @@ -320,6 +444,33 @@ await context.ready(); clearTimeout(timeoutId); ``` +## Migration from v1 + +### Breaking changes +- **Named exports** instead of default export: `import { SDK } from "@absmartly/javascript-sdk"` instead of `import absmartly from "@absmartly/javascript-sdk"` +- **Provider/Publisher classes renamed**: `ContextDataProvider` → `DefaultContextDataProvider`, `ContextPublisher` → `DefaultContextPublisher`. The old names are now interfaces. +- **`Goal` type renamed** to `GoalAchievement` for clarity +- **Node.js 14+** minimum (was Node.js 6+) +- **IE10 and IE11 are not supported by the shipped bundles** - `index.legacy.js` is ES2015, not ES5. Supporting IE10/IE11 would require an additional ES5 build plus polyfills. +- **No bundled polyfills** - `core-js`, `node-fetch`, and `rfdc` are no longer bundled. Legacy environments must provide polyfills explicitly. +- **Browser bundle renamed** - `dist/absmartly.min.js` is now `dist/index.global.js` (modern) or `dist/index.legacy.js` (ES2015 legacy build) + +### New features +- Full TypeScript support with type declarations +- Zero runtime dependencies +- Optional polyfill injection (`fetchImpl`, `AbortControllerImpl`) +- ESM, CJS, and IIFE builds from a single source +- Smaller bundle size +- Interface-based dependency injection (`Client`, `ContextDataProvider`, `ContextPublisher`) +- Domain error classes (`ABSmartlyError`, `ContextNotReadyError`, `ContextFinalizedError`) +- `readyError()` method to inspect initialization failures +- Input validation on all public methods + +### Removed exports +- `AbortController` is no longer exported by the SDK package. +- Use the platform/global `AbortController` instead. +- In legacy environments, provide your own polyfill and pass it via `AbortControllerImpl`. +- `NormalizedClientOptions` is no longer exported (internal type). ## About A/B Smartly **A/B Smartly** is the leading provider of state-of-the-art, on-premises, full-stack experimentation platforms for engineering and product teams that want to confidently deploy features as fast as they can develop them. diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 815eb05..0000000 --- a/babel.config.js +++ /dev/null @@ -1,100 +0,0 @@ -module.exports = function (api) { - api.cache.never(); - - const target = process.env.TARGET || "cjs"; - - const presets = []; - const plugins = [ - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - "@babel/plugin-proposal-nullish-coalescing-operator", - ]; - - const preset = [ - "@babel/preset-env", - { - modules: "commonjs", // transpile modules into common-js syntax by default - targets: {}, - }, - ]; - - const runtime = [ - "@babel/plugin-transform-runtime", - { - absoluteRuntime: true, - regenerator: false, - useESModules: false, // don't output es-modules by default - corejs: false, - helpers: true, - }, - ]; - - switch (target) { - case "browser": - Object.assign(preset[1], { - targets: { - ie: "10", - }, - useBuiltIns: "usage", - corejs: 3, - exclude: [ - /es\.array\.(?!(find$)).*/, - "es.array-buffer.*", - "es.function.*", - "es.json.*", - /es\.math\.(?!(imul$)).*/, - "es.map.*", - /es\.object\.(?!(assign$|entries$)).*/, - "es.promise.*", - "es.regexp.*", - "es.reflect.*", - "es.set.*", - "es.string.*", - "es.symbol.*", - /es\.typed-array\.(?!(from$|of)).*/, - "es.weak-map.*", - "web.*", - ], - }); - break; - - case "cjs": - Object.assign(preset[1], { - targets: { - node: "6", - }, - useBuiltIns: "usage", - corejs: 3, - }); - break; - - case "es": - Object.assign(runtime[1], { - useESModules: true, - }); - Object.assign(preset[1], { - modules: false, - targets: { - node: "13.20", - }, - }); - break; - default: - throw new Error(`Unsupported target '${target}'`); - } - - presets.push(preset); - plugins.push(runtime); - - return { - presets, - plugins, - }; -}; diff --git a/docs/superpowers/plans/2026-03-28-typescript-rewrite.md b/docs/superpowers/plans/2026-03-28-typescript-rewrite.md new file mode 100644 index 0000000..762f759 --- /dev/null +++ b/docs/superpowers/plans/2026-03-28-typescript-rewrite.md @@ -0,0 +1,3763 @@ +# ABSmartly JavaScript SDK - TypeScript Rewrite + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Rewrite the ABSmartly JavaScript SDK from scratch in strict TypeScript, modernizing the build system and dropping legacy polyfills while maintaining identical public API and hashing behavior. + +**Architecture:** Clean TypeScript with strict types throughout. All shared types in a central `types.ts` file. Hashing algorithms (murmur3, md5) ported verbatim to ensure byte-level compatibility. JsonExpr engine uses a clean operator interface. Client uses native `fetch` with retry/backoff. Build outputs ESM, CJS, and browser UMD via tsup. + +**Tech Stack:** TypeScript 5.x, Vitest, tsup (esbuild-based bundler), native fetch/AbortController (Node 18+, modern browsers) + +--- + +## Scope & Key Decisions + +**Dropping legacy support:** +- Node.js 6+ / IE 10+ -> Node.js 18+ / modern browsers (last 2 versions) +- Remove `core-js` polyfills +- Remove `node-fetch` dependency (native fetch in Node 18+) +- Remove `fetch-shim.ts` (XMLHttpRequest-based fetch polyfill) +- Remove `abort-controller-shim.ts` (native AbortController everywhere) +- Remove `abort.ts` and `fetch.ts` platform-detection wrappers +- Remove `rfdc` dependency (use `structuredClone`) +- Remove `browser.ts` (UMD entry point handled by bundler) + +**Dropping Babel entirely:** +- No more babel.config.js, no @babel/* devDependencies +- TypeScript compiler for type checking, tsup for bundling (ESM + CJS + browser) + +**Keeping identical:** +- Public API surface: `SDK`, `Context`, `ContextDataProvider`, `ContextPublisher`, `mergeConfig` +- Hashing algorithms: murmur3_32, md5 (byte-level identical output) +- JsonExpr evaluation engine and all 20 operators +- Variant assignment algorithm +- Client retry/backoff logic +- Context lifecycle (ready, publish, refresh, finalize) + +**Test framework:** +- Vitest instead of Jest (faster, native TypeScript, ESM-first) +- Same test coverage, modernized with async/await + +--- + +## File Structure + +``` +src/ + types.ts # All shared type definitions + errors.ts # Custom error classes (TimeoutError, RetryError, AbortError) + algorithm.ts # insertUniqueSorted utility + murmur3.ts # Murmur3 32-bit hash + md5.ts # MD5 hash + hashing.ts # hashUnit, base64UrlNoPadding, stringToUint8Array + utils.ts # isObject, isPromise, isEqualsDeep, arrayEqualsShallow, chooseVariant + assigner.ts # VariantAssigner + jsonexpr/ + evaluator.ts # Expression evaluation engine with type conversion + operators.ts # All 20 operators in one file (they are small) + jsonexpr.ts # JsonExpr class wiring operators to evaluator + matcher.ts # AudienceMatcher + client.ts # HTTP client with retry logic + provider.ts # ContextDataProvider + publisher.ts # ContextPublisher + context.ts # Context class + sdk.ts # SDK class + config.ts # mergeConfig utility + index.ts # Public API exports + version.ts # Generated SDK version + +src/__tests__/ + errors.test.ts + algorithm.test.ts + murmur3.test.ts + md5.test.ts + hashing.test.ts + utils.test.ts + assigner.test.ts + jsonexpr/ + evaluator.test.ts + operators.test.ts + jsonexpr.test.ts + matcher.test.ts + client.test.ts + provider.test.ts + publisher.test.ts + context.test.ts + sdk.test.ts + config.test.ts + +tsconfig.json +tsup.config.ts +vitest.config.ts +package.json +scripts/ + generate-version.ts +``` + +**Key structural changes from original:** +- `types.ts`: Centralized types instead of scattered across files +- `hashing.ts`: Extracted from `utils.ts` — hashing/encoding utilities are a distinct concern +- `jsonexpr/operators.ts`: All 20 operators consolidated into one file (each is 5-15 lines) +- Removed: `fetch.ts`, `fetch-shim.ts`, `abort.ts`, `abort-controller-shim.ts`, `browser.ts` + +--- + +## Task 1: Project Scaffolding & Build System + +**Files:** +- Create: `package.json` +- Create: `tsconfig.json` +- Create: `tsup.config.ts` +- Create: `vitest.config.ts` +- Create: `scripts/generate-version.ts` +- Create: `src/version.ts` +- Create: `.gitignore` + +- [ ] **Step 1: Delete all existing source files** + +Remove the old codebase to start fresh: + +```bash +rm -rf src/ js/ lib/ es/ dist/ types/ scripts/ +rm -f babel.config.js webpack.config.js jest.config.js .eslintrc.js .prettierrc.json .browserslistrc .editorconfig +``` + +- [ ] **Step 2: Write package.json** + +```json +{ + "name": "@absmartly/javascript-sdk", + "version": "2.0.0", + "description": "A/B Smartly Javascript SDK", + "homepage": "https://github.com/absmartly/javascript-sdk#README.md", + "bugs": "https://github.com/absmartly/javascript-sdk/issues", + "keywords": [ + "absmartly", + "ab-smartly", + "a/b-smartly", + "ab-testing", + "a/b-testing", + "split-testing", + "ab", + "a/b", + "cro" + ], + "license": "Apache-2.0", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "browser": "dist/index.global.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "npm run generate-version && tsup", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit", + "generate-version": "tsx scripts/generate-version.ts", + "prepack": "npm run build" + }, + "devDependencies": { + "tsup": "^8.0.0", + "typescript": "^5.4.0", + "vitest": "^3.0.0", + "tsx": "^4.0.0", + "@vitest/coverage-v8": "^3.0.0" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "README.md", + "LICENSE", + "package.json", + "dist/" + ] +} +``` + +- [ ] **Step 3: Write tsconfig.json** + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "skipLibCheck": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "isolatedModules": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "node_modules"] +} +``` + +- [ ] **Step 4: Write tsup.config.ts** + +```typescript +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs", "iife"], + globalName: "absmartly", + dts: true, + sourcemap: true, + clean: true, + minify: true, + target: "es2022", + outExtension({ format }) { + if (format === "iife") return { js: ".global.js" }; + return {}; + }, +}); +``` + +- [ ] **Step 5: Write vitest.config.ts** + +```typescript +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + coverage: { + provider: "v8", + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.ts", "src/version.ts"], + }, + }, +}); +``` + +- [ ] **Step 6: Write scripts/generate-version.ts** + +```typescript +import { readFileSync, writeFileSync } from "node:fs"; + +const pkg = JSON.parse(readFileSync("package.json", "utf-8")); +writeFileSync("src/version.ts", `export const SDK_VERSION = "${pkg.version}";\n`); +``` + +- [ ] **Step 7: Write src/version.ts** + +```typescript +export const SDK_VERSION = "2.0.0"; +``` + +- [ ] **Step 8: Write .gitignore** + +``` +node_modules/ +dist/ +coverage/ +*.tsbuildinfo +``` + +- [ ] **Step 9: Install dependencies** + +```bash +npm ci +``` + +- [ ] **Step 10: Verify build system works** + +Create a minimal `src/index.ts`: + +```typescript +export const placeholder = true; +``` + +Run: + +```bash +npx tsc --noEmit && npx tsup +``` + +Expected: Build succeeds, `dist/` contains `index.js`, `index.cjs`, `index.global.js`, `index.d.ts` + +- [ ] **Step 11: Verify test system works** + +Create `src/__tests__/placeholder.test.ts`: + +```typescript +import { expect, test } from "vitest"; + +test("placeholder", () => { + expect(true).toBe(true); +}); +``` + +Run: `npx vitest run` + +Expected: 1 test passes + +- [ ] **Step 12: Commit** + +```bash +git add -A +git commit -m "feat: scaffold TypeScript project with tsup, vitest, and modern config" +``` + +--- + +## Task 2: Types & Errors + +**Files:** +- Create: `src/types.ts` +- Create: `src/errors.ts` +- Create: `src/__tests__/errors.test.ts` + +- [ ] **Step 1: Write the failing tests for errors** + +```typescript +import { describe, expect, test } from "vitest"; +import { AbortError, RetryError, TimeoutError } from "../errors"; + +describe("TimeoutError", () => { + test("has correct name, message, and timeout", () => { + const error = new TimeoutError(3000); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(TimeoutError); + expect(error.name).toBe("TimeoutError"); + expect(error.message).toBe("Timeout exceeded."); + expect(error.timeout).toBe(3000); + }); +}); + +describe("RetryError", () => { + test("has correct name, message, retries, and exception", () => { + const cause = new Error("connection refused"); + const error = new RetryError(5, cause, "https://example.com/api"); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(RetryError); + expect(error.name).toBe("RetryError"); + expect(error.message).toBe("Retries exhausted. URL: https://example.com/api - Last Error: connection refused"); + expect(error.retries).toBe(5); + expect(error.exception).toBe(cause); + }); +}); + +describe("AbortError", () => { + test("has correct name and default message", () => { + const error = new AbortError(); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AbortError); + expect(error.name).toBe("AbortError"); + }); + + test("accepts custom message", () => { + const error = new AbortError("user cancelled"); + expect(error.message).toBe("user cancelled"); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/errors.test.ts` + +Expected: FAIL - modules not found + +- [ ] **Step 3: Write src/types.ts** + +```typescript +export type JSONPrimitive = string | number | boolean | null; +export type JSONObject = { [key: string]: JSONValue }; +export type JSONArray = JSONValue[]; +export type JSONValue = JSONPrimitive | JSONObject | JSONArray; + +export type CustomFieldValueType = "text" | "string" | "number" | "json" | "boolean"; + +export type CustomFieldValue = { + name: string; + value: string; + type: CustomFieldValueType; +}; + +export type ExperimentData = { + id: number; + name: string; + unitType: string | null; + iteration: number; + fullOnVariant: number; + trafficSplit: number[]; + trafficSeedHi: number; + trafficSeedLo: number; + audience: string; + audienceStrict: boolean; + split: number[]; + seedHi: number; + seedLo: number; + variants: { config: null | string }[]; + variables: Record; + variant: number; + overridden: boolean; + assigned: boolean; + exposed: boolean; + eligible: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; + customFieldValues: CustomFieldValue[] | null; +}; + +export type Assignment = { + id: number; + iteration: number; + fullOnVariant: number; + unitType: string | null; + variant: number; + overridden: boolean; + assigned: boolean; + exposed: boolean; + eligible: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; + trafficSplit?: number[]; + variables?: Record; + attrsSeq?: number; +}; + +export type Experiment = { + data: ExperimentData; + variables: Record[]; +}; + +export type Unit = { + type: string; + uid: string | null; +}; + +export type Exposure = { + id: number; + name: string; + exposedAt: number; + unit: string | null; + variant: number; + assigned: boolean; + eligible: boolean; + overridden: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; +}; + +export type Attribute = { + name: string; + value: unknown; + setAt: number; +}; + +export type Units = Record; + +export type Goal = { + name: string; + properties: Record | null; + achievedAt: number; +}; + +export type ContextParams = { + units: Record; +}; + +export type ContextData = { + experiments?: ExperimentData[]; +}; + +export type FetchResponse = { + status: number; + ok: boolean; + text: () => Promise; + statusText: string; + json: () => Promise; +}; + +export type ApplicationObject = { name: string; version: number | string }; + +export type ClientRequestOptions = { + query?: Record; + path: string; + method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; + body?: Record; + auth?: boolean; + signal?: AbortSignal; + timeout?: number; +}; + +export type ClientOptions = { + agent?: string; + apiKey: string; + application: string | ApplicationObject; + endpoint: string; + environment: string; + retries?: number; + timeout?: number; + keepalive?: boolean; +}; + +export type NormalizedClientOptions = Omit, "application"> & { + application: ApplicationObject; +}; + +export type PublishParams = { + units: Unit[]; + publishedAt: number; + hashed: boolean; + sdkVersion: string; + attributes?: Attribute[]; + goals?: Goal[]; + exposures?: Exposure[]; +}; + +export type EventName = "error" | "ready" | "refresh" | "publish" | "exposure" | "goal" | "finalize"; + +export type EventLoggerData = Error | Exposure | Goal | ContextData | PublishParams; + +export type EventLogger = (context: unknown, eventName: EventName, data?: EventLoggerData) => void; + +export type ContextOptions = { + publisher?: { publish: (request: PublishParams, sdk: unknown, context: unknown, requestOptions?: ClientRequestOptions) => Promise }; + dataProvider?: { getContextData: (sdk: unknown, requestOptions?: Partial) => Promise }; + eventLogger?: EventLogger; + refreshPeriod: number; + publishDelay: number; + includeSystemAttributes?: boolean; +}; + +export type SDKOptions = { + client?: unknown; + eventLogger?: EventLogger; + publisher?: unknown; + provider?: unknown; +}; +``` + +- [ ] **Step 4: Write src/errors.ts** + +```typescript +export class TimeoutError extends Error { + readonly timeout: number; + constructor(timeout: number) { + super("Timeout exceeded."); + this.name = "TimeoutError"; + this.timeout = timeout; + } +} + +export class RetryError extends Error { + readonly retries: number; + readonly exception: Error; + constructor(retries: number, reason: Error, url: string) { + super(`Retries exhausted. URL: ${url} - Last Error: ${reason.message}`); + this.name = "RetryError"; + this.retries = retries; + this.exception = reason; + } +} + +export class AbortError extends Error { + constructor(message?: string) { + super(message); + this.name = "AbortError"; + } +} +``` + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/errors.test.ts` + +Expected: 3 tests pass + +- [ ] **Step 6: Verify types compile** + +Run: `npx tsc --noEmit` + +Expected: No errors + +- [ ] **Step 7: Commit** + +```bash +git add src/types.ts src/errors.ts src/__tests__/errors.test.ts +git commit -m "feat: add shared type definitions and custom error classes" +``` + +--- + +## Task 3: Murmur3 Hash + +**Files:** +- Create: `src/murmur3.ts` +- Create: `src/__tests__/murmur3.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { murmur3_32 } from "../murmur3"; + +function stringToBuffer(value: string): ArrayBuffer { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array).buffer; +} + +describe("murmur3_32", () => { + const testCases: [string, number, number][] = [ + ["", 0x00000000, 0x00000000], + [" ", 0x00000000, 0x7ef49b98], + ["t", 0x00000000, 0xca87df4d], + ["te", 0x00000000, 0xedb8ee1b], + ["tes", 0x00000000, 0x0bb90e5a], + ["test", 0x00000000, 0xba6bd213], + ["testy", 0x00000000, 0x44af8342], + ["testy1", 0x00000000, 0x8a1a243a], + ["testy12", 0x00000000, 0x845461b9], + ["testy123", 0x00000000, 0xee0abfbc], + ["special-characters-testing-!@#$%^&*()_+-=[]{}|;':\",./<>?", 0x00000000, 0xe1b16274], + ["", 0x00000001, 0x514e28b7], + [" ", 0x00000001, 0x4f0f7132], + ["t", 0x00000001, 0x5db1831e], + ["te", 0x00000001, 0xd248bb2e], + ["tes", 0x00000001, 0xd432eb74], + ["test", 0x00000001, 0x99c02ae2], + ["testy", 0x00000001, 0xc5b2dc1e], + ["testy1", 0x00000001, 0x33925ceb], + ["testy12", 0x00000001, 0xd92c9f23], + ["testy123", 0x00000001, 0x3bc1712d], + ["special-characters-testing-!@#$%^&*()_+-=[]{}|;':\",./<>?", 0x00000001, 0x6d1d2105], + ]; + + for (const [input, seed, expected] of testCases) { + test(`murmur3_32("${input}", ${seed}) == 0x${expected.toString(16).padStart(8, "0")}`, () => { + expect(murmur3_32(stringToBuffer(input), seed)).toBe(expected); + }); + } +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/murmur3.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/murmur3.ts** + +```typescript +const C1 = 0xcc9e2d51; +const C2 = 0x1b873593; +const C3 = 0xe6546b64; + +const imul32 = Math.imul; + +function fmix32(h: number): number { + h ^= h >>> 16; + h = imul32(h, 0x85ebca6b); + h ^= h >>> 13; + h = imul32(h, 0xc2b2ae35); + h ^= h >>> 16; + return h >>> 0; +} + +function rotl32(a: number, b: number): number { + return (a << b) | (a >>> (32 - b)); +} + +function scramble32(block: number): number { + return imul32(rotl32(imul32(block, C1), 15), C2); +} + +export function murmur3_32(key: ArrayBufferLike, hash?: number): number { + hash = (hash || 0) >>> 0; + const dataView = new DataView(key); + + let i: number; + const n = dataView.byteLength & ~3; + for (i = 0; i < n; i += 4) { + const chunk = dataView.getUint32(i, true); + hash ^= scramble32(chunk); + hash = rotl32(hash, 13); + hash = imul32(hash, 5) + C3; + } + + let remaining = 0; + switch (dataView.byteLength & 3) { + case 3: + remaining ^= dataView.getUint8(i + 2) << 16; + // fallthrough + case 2: + remaining ^= dataView.getUint8(i + 1) << 8; + // fallthrough + case 1: + remaining ^= dataView.getUint8(i); + hash ^= scramble32(remaining); + // fallthrough + default: + break; + } + + hash ^= dataView.byteLength; + hash = fmix32(hash); + return hash >>> 0; +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/murmur3.test.ts` + +Expected: All 22 tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/murmur3.ts src/__tests__/murmur3.test.ts +git commit -m "feat: add murmur3_32 hash implementation" +``` + +--- + +## Task 4: MD5 Hash + +**Files:** +- Create: `src/md5.ts` +- Create: `src/__tests__/md5.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { md5 } from "../md5"; + +function stringToBuffer(value: string): ArrayBuffer { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array).buffer; +} + +function toHex(bytes: Uint8Array): string { + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +describe("md5", () => { + const testCases: [string, string][] = [ + ["", "d41d8cd98f00b204e9800998ecf8427e"], + ["a", "0cc175b9c0f1b6a831c399e269772661"], + ["abc", "900150983cd24fb0d6963f7d28e17f72"], + ["message digest", "f96b697d7cb7938d525a2f31aaf161d0"], + ["abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"], + ]; + + for (const [input, expectedHex] of testCases) { + test(`md5("${input}") == ${expectedHex}`, () => { + const result = md5(stringToBuffer(input)); + expect(toHex(result)).toBe(expectedHex); + }); + } +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/md5.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/md5.ts** + +```typescript +function cmn(q: number, a: number, b: number, x: number, s: number, t: number): number { + a = a + q + (x >>> 0) + t; + return ((a << s) | (a >>> (32 - s))) + b; +} + +function ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { + return cmn((b & c) | (~b & d), a, b, x, s, t); +} + +function gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { + return cmn((b & d) | (c & ~d), a, b, x, s, t); +} + +function hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { + return cmn(b ^ c ^ d, a, b, x, s, t); +} + +function ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { + return cmn(c ^ (b | ~d), a, b, x, s, t); +} + +function md5cycle(x: Uint32Array, k: Uint32Array): void { + let a = x[0]!; + let b = x[1]!; + let c = x[2]!; + let d = x[3]!; + + a = ff(a, b, c, d, k[0]!, 7, -680876936); + d = ff(d, a, b, c, k[1]!, 12, -389564586); + c = ff(c, d, a, b, k[2]!, 17, 606105819); + b = ff(b, c, d, a, k[3]!, 22, -1044525330); + a = ff(a, b, c, d, k[4]!, 7, -176418897); + d = ff(d, a, b, c, k[5]!, 12, 1200080426); + c = ff(c, d, a, b, k[6]!, 17, -1473231341); + b = ff(b, c, d, a, k[7]!, 22, -45705983); + a = ff(a, b, c, d, k[8]!, 7, 1770035416); + d = ff(d, a, b, c, k[9]!, 12, -1958414417); + c = ff(c, d, a, b, k[10]!, 17, -42063); + b = ff(b, c, d, a, k[11]!, 22, -1990404162); + a = ff(a, b, c, d, k[12]!, 7, 1804603682); + d = ff(d, a, b, c, k[13]!, 12, -40341101); + c = ff(c, d, a, b, k[14]!, 17, -1502002290); + b = ff(b, c, d, a, k[15]!, 22, 1236535329); + + a = gg(a, b, c, d, k[1]!, 5, -165796510); + d = gg(d, a, b, c, k[6]!, 9, -1069501632); + c = gg(c, d, a, b, k[11]!, 14, 643717713); + b = gg(b, c, d, a, k[0]!, 20, -373897302); + a = gg(a, b, c, d, k[5]!, 5, -701558691); + d = gg(d, a, b, c, k[10]!, 9, 38016083); + c = gg(c, d, a, b, k[15]!, 14, -660478335); + b = gg(b, c, d, a, k[4]!, 20, -405537848); + a = gg(a, b, c, d, k[9]!, 5, 568446438); + d = gg(d, a, b, c, k[14]!, 9, -1019803690); + c = gg(c, d, a, b, k[3]!, 14, -187363961); + b = gg(b, c, d, a, k[8]!, 20, 1163531501); + a = gg(a, b, c, d, k[13]!, 5, -1444681467); + d = gg(d, a, b, c, k[2]!, 9, -51403784); + c = gg(c, d, a, b, k[7]!, 14, 1735328473); + b = gg(b, c, d, a, k[12]!, 20, -1926607734); + + a = hh(a, b, c, d, k[5]!, 4, -378558); + d = hh(d, a, b, c, k[8]!, 11, -2022574463); + c = hh(c, d, a, b, k[11]!, 16, 1839030562); + b = hh(b, c, d, a, k[14]!, 23, -35309556); + a = hh(a, b, c, d, k[1]!, 4, -1530992060); + d = hh(d, a, b, c, k[4]!, 11, 1272893353); + c = hh(c, d, a, b, k[7]!, 16, -155497632); + b = hh(b, c, d, a, k[10]!, 23, -1094730640); + a = hh(a, b, c, d, k[13]!, 4, 681279174); + d = hh(d, a, b, c, k[0]!, 11, -358537222); + c = hh(c, d, a, b, k[3]!, 16, -722521979); + b = hh(b, c, d, a, k[6]!, 23, 76029189); + a = hh(a, b, c, d, k[9]!, 4, -640364487); + d = hh(d, a, b, c, k[12]!, 11, -421815835); + c = hh(c, d, a, b, k[15]!, 16, 530742520); + b = hh(b, c, d, a, k[2]!, 23, -995338651); + + a = ii(a, b, c, d, k[0]!, 6, -198630844); + d = ii(d, a, b, c, k[7]!, 10, 1126891415); + c = ii(c, d, a, b, k[14]!, 15, -1416354905); + b = ii(b, c, d, a, k[5]!, 21, -57434055); + a = ii(a, b, c, d, k[12]!, 6, 1700485571); + d = ii(d, a, b, c, k[3]!, 10, -1894986606); + c = ii(c, d, a, b, k[10]!, 15, -1051523); + b = ii(b, c, d, a, k[1]!, 21, -2054922799); + a = ii(a, b, c, d, k[8]!, 6, 1873313359); + d = ii(d, a, b, c, k[15]!, 10, -30611744); + c = ii(c, d, a, b, k[6]!, 15, -1560198380); + b = ii(b, c, d, a, k[13]!, 21, 1309151649); + a = ii(a, b, c, d, k[4]!, 6, -145523070); + d = ii(d, a, b, c, k[11]!, 10, -1120210379); + c = ii(c, d, a, b, k[2]!, 15, 718787259); + b = ii(b, c, d, a, k[9]!, 21, -343485551); + + x[0] = (a + x[0]!) >>> 0; + x[1] = (b + x[1]!) >>> 0; + x[2] = (c + x[2]!) >>> 0; + x[3] = (d + x[3]!) >>> 0; +} + +export function md5(key: ArrayBufferLike): Uint8Array { + const dataView = new DataView(key); + + let i: number; + const l = dataView.byteLength; + const n = l & ~63; + const block = new Uint32Array(16); + const state = Uint32Array.of(1732584193, -271733879, -1732584194, 271733878); + for (i = 0; i < n; i += 64) { + for (let w = 0; w < 16; ++w) { + block[w] = dataView.getUint32(i + (w << 2), true); + } + md5cycle(state, block); + } + + let w = 0; + const m = l & ~3; + for (; i < m; i += 4) { + block[w++] = dataView.getUint32(i, true); + } + + const p = l & 3; + switch (p) { + case 3: + block[w++] = + 0x80000000 | dataView.getUint8(i) | (dataView.getUint8(i + 1) << 8) | (dataView.getUint8(i + 2) << 16); + break; + case 2: + block[w++] = 0x800000 | dataView.getUint8(i) | (dataView.getUint8(i + 1) << 8); + break; + case 1: + block[w++] = 0x8000 | dataView.getUint8(i); + break; + default: + block[w++] = 0x80; + break; + } + + if (w > 14) { + for (; w < 16; ++w) { + block[w] = 0; + } + md5cycle(state, block); + w = 0; + } + + for (; w < 16; ++w) { + block[w] = 0; + } + + block[14] = l << 3; + md5cycle(state, block); + return new Uint8Array(state.buffer); +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/md5.test.ts` + +Expected: All 5 tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/md5.ts src/__tests__/md5.test.ts +git commit -m "feat: add MD5 hash implementation" +``` + +--- + +## Task 5: Hashing Utilities + +**Files:** +- Create: `src/hashing.ts` +- Create: `src/__tests__/hashing.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { base64UrlNoPadding, hashUnit, stringToUint8Array } from "../hashing"; + +describe("stringToUint8Array", () => { + test("encodes ASCII", () => { + const result = stringToUint8Array("abc"); + expect(Array.from(result)).toEqual([97, 98, 99]); + }); + + test("encodes multi-byte characters", () => { + const result = stringToUint8Array("\u00e9"); + expect(Array.from(result)).toEqual([0xc3, 0xa9]); + }); + + test("encodes empty string", () => { + const result = stringToUint8Array(""); + expect(result.length).toBe(0); + }); +}); + +describe("base64UrlNoPadding", () => { + test("encodes empty", () => { + expect(base64UrlNoPadding(new Uint8Array([]))).toBe(""); + }); + + test("encodes 1 byte", () => { + expect(base64UrlNoPadding(new Uint8Array([0]))).toBe("AA"); + }); + + test("encodes 2 bytes", () => { + expect(base64UrlNoPadding(new Uint8Array([0, 0]))).toBe("AAA"); + }); + + test("encodes 3 bytes", () => { + expect(base64UrlNoPadding(new Uint8Array([0, 0, 0]))).toBe("AAAA"); + }); +}); + +describe("hashUnit", () => { + test("hashes string unit", () => { + const result = hashUnit("test_unit"); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + test("hashes numeric unit", () => { + const result = hashUnit(12345); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + test("produces consistent results", () => { + expect(hashUnit("abc")).toBe(hashUnit("abc")); + expect(hashUnit(123)).toBe(hashUnit(123)); + }); + + test("produces different results for different inputs", () => { + expect(hashUnit("abc")).not.toBe(hashUnit("def")); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/hashing.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/hashing.ts** + +```typescript +import { md5 } from "./md5"; + +export function stringToUint8Array(value: string): Uint8Array { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array); +} + +const BASE64_URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +export function base64UrlNoPadding(value: Uint8Array): string { + const chars = BASE64_URL_CHARS; + const remaining = value.byteLength % 3; + const encodeLen = ((value.byteLength / 3) | 0) * 4 + (remaining === 0 ? 0 : remaining === 1 ? 2 : 3); + const result = new Array(encodeLen); + + let i: number; + let out = 0; + const len = value.byteLength - remaining; + for (i = 0; i < len; i += 3) { + const bytes = (value[i]! << 16) | (value[i + 1]! << 8) | value[i + 2]!; + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + result[out + 2] = chars[(bytes >> 6) & 63]!; + result[out + 3] = chars[bytes & 63]!; + out += 4; + } + + switch (remaining) { + case 2: { + const bytes = (value[i]! << 16) | (value[i + 1]! << 8); + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + result[out + 2] = chars[(bytes >> 6) & 63]!; + break; + } + case 1: { + const bytes = value[i]! << 16; + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + break; + } + default: + break; + } + + return result.join(""); +} + +export function hashUnit(value: string | number): string { + const unit = typeof value === "string" ? value : value.toFixed(0); + return base64UrlNoPadding(md5(stringToUint8Array(unit).buffer)); +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/hashing.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/hashing.ts src/__tests__/hashing.test.ts +git commit -m "feat: add hashing utilities (stringToUint8Array, base64UrlNoPadding, hashUnit)" +``` + +--- + +## Task 6: Core Utilities + +**Files:** +- Create: `src/utils.ts` +- Create: `src/algorithm.ts` +- Create: `src/__tests__/utils.test.ts` +- Create: `src/__tests__/algorithm.test.ts` + +- [ ] **Step 1: Write the failing tests for utils** + +```typescript +import { describe, expect, test } from "vitest"; +import { arrayEqualsShallow, chooseVariant, isEqualsDeep, isObject, isPromise } from "../utils"; + +describe("isObject", () => { + test("returns true for plain objects", () => { + expect(isObject({})).toBe(true); + expect(isObject({ a: 1 })).toBe(true); + }); + + test("returns false for non-objects", () => { + expect(isObject(null)).toBe(false); + expect(isObject(undefined)).toBe(false); + expect(isObject(42)).toBe(false); + expect(isObject("str")).toBe(false); + expect(isObject([])).toBe(false); + expect(isObject(new Date())).toBe(false); + }); +}); + +describe("isPromise", () => { + test("returns true for promises", () => { + expect(isPromise(Promise.resolve())).toBe(true); + expect(isPromise({ then: () => {} })).toBe(true); + }); + + test("returns false for non-promises", () => { + expect(isPromise(null)).toBe(false); + expect(isPromise(undefined)).toBe(false); + expect(isPromise({})).toBe(false); + expect(isPromise(42)).toBe(false); + }); +}); + +describe("isEqualsDeep", () => { + test("primitives", () => { + expect(isEqualsDeep(1, 1)).toBe(true); + expect(isEqualsDeep(1, 2)).toBe(false); + expect(isEqualsDeep("a", "a")).toBe(true); + expect(isEqualsDeep("a", "b")).toBe(false); + expect(isEqualsDeep(true, true)).toBe(true); + expect(isEqualsDeep(true, false)).toBe(false); + }); + + test("NaN", () => { + expect(isEqualsDeep(NaN, NaN)).toBe(true); + }); + + test("arrays", () => { + expect(isEqualsDeep([1, 2, 3], [1, 2, 3])).toBe(true); + expect(isEqualsDeep([1, 2, 3], [1, 2, 4])).toBe(false); + expect(isEqualsDeep([1, 2], [1, 2, 3])).toBe(false); + }); + + test("objects", () => { + expect(isEqualsDeep({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true); + expect(isEqualsDeep({ a: 1 }, { a: 2 })).toBe(false); + expect(isEqualsDeep({ a: 1 }, { a: 1, b: 2 })).toBe(false); + }); + + test("nested structures", () => { + expect(isEqualsDeep({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })).toBe(true); + expect(isEqualsDeep({ a: [1, { b: 2 }] }, { a: [1, { b: 3 }] })).toBe(false); + }); + + test("different types", () => { + expect(isEqualsDeep(1, "1")).toBe(false); + expect(isEqualsDeep([], {})).toBe(false); + }); +}); + +describe("arrayEqualsShallow", () => { + test("same reference", () => { + const arr = [1, 2, 3]; + expect(arrayEqualsShallow(arr, arr)).toBe(true); + }); + + test("equal arrays", () => { + expect(arrayEqualsShallow([1, 2, 3], [1, 2, 3])).toBe(true); + }); + + test("different arrays", () => { + expect(arrayEqualsShallow([1, 2, 3], [1, 2, 4])).toBe(false); + }); + + test("different lengths", () => { + expect(arrayEqualsShallow([1, 2], [1, 2, 3])).toBe(false); + }); + + test("both undefined", () => { + expect(arrayEqualsShallow(undefined, undefined)).toBe(true); + }); +}); + +describe("chooseVariant", () => { + test("selects correct variant based on probability", () => { + expect(chooseVariant([0.5, 0.5], 0.0)).toBe(0); + expect(chooseVariant([0.5, 0.5], 0.4)).toBe(0); + expect(chooseVariant([0.5, 0.5], 0.5)).toBe(1); + expect(chooseVariant([0.5, 0.5], 0.9)).toBe(1); + }); + + test("three-way split", () => { + expect(chooseVariant([0.33, 0.33, 0.34], 0.0)).toBe(0); + expect(chooseVariant([0.33, 0.33, 0.34], 0.3)).toBe(0); + expect(chooseVariant([0.33, 0.33, 0.34], 0.33)).toBe(1); + expect(chooseVariant([0.33, 0.33, 0.34], 0.65)).toBe(1); + expect(chooseVariant([0.33, 0.33, 0.34], 0.66)).toBe(2); + }); + + test("returns last variant for probability >= 1", () => { + expect(chooseVariant([0.5, 0.5], 1.0)).toBe(1); + }); +}); +``` + +- [ ] **Step 2: Write the failing tests for algorithm** + +```typescript +import { describe, expect, test } from "vitest"; +import { insertUniqueSorted } from "../algorithm"; + +describe("insertUniqueSorted", () => { + test("inserts into empty array", () => { + const arr: number[] = []; + insertUniqueSorted(arr, 5, (a, b) => a < b); + expect(arr).toEqual([5]); + }); + + test("inserts in sorted order", () => { + const arr = [1, 3, 5]; + insertUniqueSorted(arr, 2, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 5]); + }); + + test("inserts at beginning", () => { + const arr = [2, 3, 4]; + insertUniqueSorted(arr, 1, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 4]); + }); + + test("inserts at end", () => { + const arr = [1, 2, 3]; + insertUniqueSorted(arr, 4, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 4]); + }); + + test("does not insert duplicate", () => { + const arr = [1, 2, 3]; + insertUniqueSorted(arr, 2, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3]); + }); +}); +``` + +- [ ] **Step 3: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/utils.test.ts src/__tests__/algorithm.test.ts` + +Expected: FAIL - modules not found + +- [ ] **Step 4: Write src/utils.ts** + +```typescript +export function isObject(value: unknown): value is Record { + if (!(value instanceof Object)) return false; + const proto = Object.getPrototypeOf(value); + return proto == null || proto === Object.prototype; +} + +export function isPromise(value: unknown): value is Promise { + return value !== null && typeof value === "object" && typeof (value as Promise).then === "function"; +} + +function arrayEqualsDeep(a: unknown[], b: unknown[], astack: unknown[] = [], bstack: unknown[] = []): boolean { + let len = astack.length; + while (len--) { + if (astack[len] === a) return bstack[len] === b; + } + + astack.push(a); + bstack.push(b); + + len = a.length; + while (len--) { + if (!isEqualsDeep(a[len], b[len], astack, bstack)) return false; + } + + bstack.pop(); + astack.pop(); + + return true; +} + +function objectEqualsDeep( + a: Record, + b: Record, + keys: string[], + astack?: unknown[], + bstack?: unknown[], +): boolean { + let len = astack?.length ?? 0; + while (len--) { + if (astack && astack[len] === a) return bstack !== undefined && bstack[len] === b; + } + + astack = astack ?? []; + bstack = bstack ?? []; + + astack.push(a); + bstack.push(b); + + len = keys.length; + while (len--) { + const key = keys[len]!; + if (!Object.prototype.hasOwnProperty.call(b, key)) return false; + if (!isEqualsDeep(a[key], b[key], astack, bstack)) return false; + } + + bstack.pop(); + astack.pop(); + + return true; +} + +export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack?: unknown[]): boolean { + if (a === b) return true; + if (typeof a !== typeof b) return false; + + switch (typeof a) { + case "boolean": + return a === b; + case "number": + if (Number.isNaN(a)) return Number.isNaN(b as number); + return a === b; + case "string": + return a === b; + case "object": { + const arrays = Array.isArray(a); + if (arrays && !Array.isArray(b)) return false; + + const objects = isObject(a); + if (objects && !isObject(b)) return false; + + if (!arrays && !objects) return false; + + if (arrays && Array.isArray(b)) { + if (a.length === b.length) { + return arrayEqualsDeep(a, b, astack, bstack); + } + } else if (a && b) { + const keys = Object.keys(a); + if (keys.length === Object.keys(b as Record).length) { + return objectEqualsDeep( + a as Record, + b as Record, + keys, + astack, + bstack, + ); + } + } + break; + } + default: + break; + } + return false; +} + +export function arrayEqualsShallow(a?: unknown[], b?: unknown[]): boolean { + return a === b || (a?.length === b?.length && !a?.some((va, vi) => b && va !== b[vi])); +} + +export function chooseVariant(split: number[], prob: number): number { + let cumSum = 0.0; + for (let i = 0; i < split.length; ++i) { + cumSum += split[i]!; + if (prob < cumSum) return i; + } + return split.length - 1; +} +``` + +- [ ] **Step 5: Write src/algorithm.ts** + +```typescript +export function insertUniqueSorted( + arr: TData[], + value: TData, + isSorted: (a: TData, b: TData) => boolean, +): void { + let left = 0; + let right = arr.length - 1; + + while (left <= right) { + const mid = Math.floor(left + (right - left) / 2); + if (isSorted(arr[mid]!, value)) { + left = mid + 1; + } else if (isSorted(value, arr[mid]!)) { + right = mid - 1; + } else { + return; + } + } + + arr.splice(left, 0, value); +} +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/utils.test.ts src/__tests__/algorithm.test.ts` + +Expected: All tests pass + +- [ ] **Step 7: Commit** + +```bash +git add src/utils.ts src/algorithm.ts src/__tests__/utils.test.ts src/__tests__/algorithm.test.ts +git commit -m "feat: add core utilities (isObject, isEqualsDeep, chooseVariant, insertUniqueSorted)" +``` + +--- + +## Task 7: Variant Assigner + +**Files:** +- Create: `src/assigner.ts` +- Create: `src/__tests__/assigner.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { VariantAssigner } from "../assigner"; +import { hashUnit } from "../hashing"; + +describe("VariantAssigner", () => { + const testCases: Record = { + "bleh@absmartly.com": [ + [[0.5, 0.5], 0x00000000, 0x00000000, 0], + [[0.5, 0.5], 0x00000000, 0x00000001, 1], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 0], + [[0.5, 0.5], 0x3b2e7571, 0xca87df4d, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 2], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 0], + [[0.33, 0.33, 0.34], 0x3b2e7571, 0xca87df4d, 0], + ], + "123456789": [ + [[0.5, 0.5], 0x00000000, 0x00000000, 1], + [[0.5, 0.5], 0x00000000, 0x00000001, 0], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], + [[0.5, 0.5], 0x3b2e7571, 0xca87df4d, 1], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 0], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], + [[0.33, 0.33, 0.34], 0x3b2e7571, 0xca87df4d, 1], + ], + }; + + for (const [unit, cases] of Object.entries(testCases)) { + describe(`unit: "${unit}"`, () => { + const assigner = new VariantAssigner(hashUnit(unit)); + for (const [split, seedHi, seedLo, expected] of cases) { + test(`assign([${split}], 0x${seedHi.toString(16)}, 0x${seedLo.toString(16)}) == ${expected}`, () => { + expect(assigner.assign(split, seedHi, seedLo)).toBe(expected); + }); + } + }); + } +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/assigner.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/assigner.ts** + +```typescript +import { chooseVariant } from "./utils"; +import { stringToUint8Array } from "./hashing"; +import { murmur3_32 } from "./murmur3"; + +export class VariantAssigner { + private readonly _unitHash: number; + + constructor(unit: string) { + this._unitHash = murmur3_32(stringToUint8Array(unit).buffer); + } + + assign(split: number[], seedHi: number, seedLo: number): number { + const prob = this._probability(seedHi, seedLo); + return chooseVariant(split, prob); + } + + private _probability(seedHi: number, seedLo: number): number { + const key = this._unitHash; + const buffer = new ArrayBuffer(12); + const view = new DataView(buffer); + view.setUint32(0, seedLo, true); + view.setUint32(4, seedHi, true); + view.setUint32(8, key, true); + return murmur3_32(buffer) * (1.0 / 0xffffffff); + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/assigner.test.ts` + +Expected: All 16 tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/assigner.ts src/__tests__/assigner.test.ts +git commit -m "feat: add VariantAssigner with murmur3-based probability" +``` + +--- + +## Task 8: JsonExpr Evaluator + +**Files:** +- Create: `src/jsonexpr/evaluator.ts` +- Create: `src/__tests__/jsonexpr/evaluator.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { Evaluator } from "../../jsonexpr/evaluator"; + +function createEvaluator(vars: Record = {}) { + return new Evaluator({}, vars); +} + +describe("Evaluator", () => { + describe("booleanConvert", () => { + const evaluator = createEvaluator(); + + test("boolean values", () => { + expect(evaluator.booleanConvert(true)).toBe(true); + expect(evaluator.booleanConvert(false)).toBe(false); + }); + + test("number values", () => { + expect(evaluator.booleanConvert(1)).toBe(true); + expect(evaluator.booleanConvert(0)).toBe(false); + expect(evaluator.booleanConvert(-1)).toBe(true); + }); + + test("string values", () => { + expect(evaluator.booleanConvert("true")).toBe(true); + expect(evaluator.booleanConvert("false")).toBe(false); + expect(evaluator.booleanConvert("0")).toBe(false); + expect(evaluator.booleanConvert("")).toBe(false); + expect(evaluator.booleanConvert("abc")).toBe(true); + }); + + test("null/undefined", () => { + expect(evaluator.booleanConvert(null)).toBe(false); + expect(evaluator.booleanConvert(undefined)).toBe(false); + }); + }); + + describe("numberConvert", () => { + const evaluator = createEvaluator(); + + test("number values", () => { + expect(evaluator.numberConvert(42)).toBe(42); + expect(evaluator.numberConvert(0)).toBe(0); + expect(evaluator.numberConvert(-1.5)).toBe(-1.5); + }); + + test("boolean values", () => { + expect(evaluator.numberConvert(true)).toBe(1); + expect(evaluator.numberConvert(false)).toBe(0); + }); + + test("string values", () => { + expect(evaluator.numberConvert("42")).toBe(42); + expect(evaluator.numberConvert("3.14")).toBe(3.14); + expect(evaluator.numberConvert("abc")).toBe(null); + }); + + test("other types", () => { + expect(evaluator.numberConvert(null)).toBe(null); + expect(evaluator.numberConvert({})).toBe(null); + }); + }); + + describe("stringConvert", () => { + const evaluator = createEvaluator(); + + test("string values", () => { + expect(evaluator.stringConvert("hello")).toBe("hello"); + }); + + test("boolean values", () => { + expect(evaluator.stringConvert(true)).toBe("true"); + expect(evaluator.stringConvert(false)).toBe("false"); + }); + + test("number values", () => { + expect(evaluator.stringConvert(42)).toBe("42"); + expect(evaluator.stringConvert(0)).toBe("0"); + }); + + test("other types", () => { + expect(evaluator.stringConvert(null)).toBe(null); + expect(evaluator.stringConvert({})).toBe(null); + }); + }); + + describe("extractVar", () => { + test("extracts top-level variable", () => { + const evaluator = createEvaluator({ name: "John" }); + expect(evaluator.extractVar("name")).toBe("John"); + }); + + test("extracts nested variable", () => { + const evaluator = createEvaluator({ user: { name: "John" } }); + expect(evaluator.extractVar("user/name")).toBe("John"); + }); + + test("returns null for missing path", () => { + const evaluator = createEvaluator({ name: "John" }); + expect(evaluator.extractVar("missing")).toBe(null); + }); + }); + + describe("compare", () => { + const evaluator = createEvaluator(); + + test("numbers", () => { + expect(evaluator.compare(1, 2)).toBe(-1); + expect(evaluator.compare(2, 1)).toBe(1); + expect(evaluator.compare(1, 1)).toBe(0); + }); + + test("strings", () => { + expect(evaluator.compare("a", "b")).toBe(-1); + expect(evaluator.compare("b", "a")).toBe(1); + expect(evaluator.compare("a", "a")).toBe(0); + }); + + test("booleans", () => { + expect(evaluator.compare(true, true)).toBe(0); + expect(evaluator.compare(false, false)).toBe(0); + }); + + test("null handling", () => { + expect(evaluator.compare(null, null)).toBe(0); + expect(evaluator.compare(null, 1)).toBe(null); + expect(evaluator.compare(1, null)).toBe(null); + }); + }); + + describe("versionCompare", () => { + const evaluator = createEvaluator(); + + test("equal versions", () => { + expect(evaluator.versionCompare("1.0.0", "1.0.0")).toBe(0); + }); + + test("greater version", () => { + expect(evaluator.versionCompare("2.0.0", "1.0.0")).toBe(1); + }); + + test("lesser version", () => { + expect(evaluator.versionCompare("1.0.0", "2.0.0")).toBe(-1); + }); + + test("prerelease is less than release", () => { + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0")).toBe(-1); + }); + + test("v prefix", () => { + expect(evaluator.versionCompare("v1.0.0", "1.0.0")).toBe(0); + }); + + test("build metadata ignored", () => { + expect(evaluator.versionCompare("1.0.0+build1", "1.0.0+build2")).toBe(0); + }); + + test("null inputs", () => { + expect(evaluator.versionCompare(null, "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", null)).toBe(null); + }); + + test("empty inputs", () => { + expect(evaluator.versionCompare("", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", "")).toBe(null); + }); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/jsonexpr/evaluator.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/jsonexpr/evaluator.ts** + +Port the evaluator verbatim from the original. The implementation is in the code read earlier (`src/jsonexpr/evaluator.ts` from original codebase). Copy it with proper strict TypeScript typing: + +```typescript +import { isEqualsDeep, isObject } from "../utils"; + +export interface Operator { + evaluate(evaluator: Evaluator, args: unknown): unknown; +} + +function parseSemver(version: string) { + let v = version; + if (v.startsWith("v") || v.startsWith("V")) { + v = v.substring(1); + } + + const plusIndex = v.indexOf("+"); + if (plusIndex >= 0) { + v = v.substring(0, plusIndex); + } + + if (v === "") return null; + + const [core, ...preReleaseParts] = v.split("-"); + const preRelease = preReleaseParts.join("-"); + + if (core === "") return null; + + const parts = core!.split("."); + return { parts, preRelease }; +} + +const NUMERIC_IDENTIFIER = /^\d+$/; + +function stripLeadingZeros(s: string): string { + const stripped = s.replace(/^0+/, ""); + return stripped === "" ? "0" : stripped; +} + +function compareIdentifiers(a: string, b: string): number { + const aIsNum = NUMERIC_IDENTIFIER.test(a); + const bIsNum = NUMERIC_IDENTIFIER.test(b); + + if (aIsNum && bIsNum) { + const aNorm = stripLeadingZeros(a); + const bNorm = stripLeadingZeros(b); + if (aNorm.length !== bNorm.length) return aNorm.length > bNorm.length ? 1 : -1; + return aNorm === bNorm ? 0 : aNorm > bNorm ? 1 : -1; + } + if (aIsNum) return -1; + if (bIsNum) return 1; + return a === b ? 0 : a > b ? 1 : -1; +} + +export class Evaluator { + private readonly operators: Record; + private readonly vars: Record; + + constructor(operators: Record, vars: Record) { + this.operators = operators; + this.vars = vars; + } + + evaluate(expr: unknown): unknown { + if (Array.isArray(expr)) { + return this.operators["and"]?.evaluate(this, expr) ?? null; + } else if (isObject(expr)) { + for (const [key, value] of Object.entries(expr)) { + const op = this.operators[key]; + if (op !== undefined) { + return op.evaluate(this, value); + } + break; + } + } + return null; + } + + booleanConvert(x: unknown): boolean { + const type = typeof x; + switch (type) { + case "boolean": + return x as boolean; + case "number": + return x !== 0; + case "string": + return x !== "false" && x !== "0" && x !== ""; + default: + return x !== null && x !== undefined; + } + } + + numberConvert(x: unknown): number | null { + switch (typeof x) { + case "number": + return x; + case "boolean": + return x ? 1 : 0; + case "string": { + const y = parseFloat(x); + return Number.isFinite(y) ? y : null; + } + default: + return null; + } + } + + stringConvert(x: unknown): string | null { + switch (typeof x) { + case "string": + return x; + case "boolean": + return x.toString(); + case "number": + return x.toFixed(15).replace(/\.?0{0,15}$/, ""); + default: + return null; + } + } + + extractVar(path: string): unknown { + const frags = path.split("/"); + let target: unknown = this.vars ?? {}; + for (const frag of frags) { + if (target !== null && typeof target === "object" && frag in (target as Record)) { + target = (target as Record)[frag]; + continue; + } + return null; + } + return target; + } + + versionCompare(lhs: unknown, rhs: unknown): number | null { + const lhsStr = this.stringConvert(lhs); + const rhsStr = this.stringConvert(rhs); + if (lhsStr === null || rhsStr === null || lhsStr === "" || rhsStr === "") return null; + + const l = parseSemver(lhsStr); + const r = parseSemver(rhsStr); + if (l === null || r === null) return null; + + const maxLen = Math.max(l.parts.length, r.parts.length); + for (let i = 0; i < maxLen; i++) { + const lPart = l.parts[i] ?? "0"; + const rPart = r.parts[i] ?? "0"; + const result = compareIdentifiers(lPart, rPart); + if (result !== 0) return result; + } + + if (!l.preRelease && !r.preRelease) return 0; + if (!l.preRelease) return 1; + if (!r.preRelease) return -1; + + const lPreParts = l.preRelease.split("."); + const rPreParts = r.preRelease.split("."); + const preLen = Math.max(lPreParts.length, rPreParts.length); + for (let i = 0; i < preLen; i++) { + if (i >= lPreParts.length) return -1; + if (i >= rPreParts.length) return 1; + const result = compareIdentifiers(lPreParts[i]!, rPreParts[i]!); + if (result !== 0) return result; + } + + return 0; + } + + compare(lhs: unknown, rhs: unknown): number | null { + if (lhs === null) return rhs === null ? 0 : null; + if (rhs === null) return null; + + switch (typeof lhs) { + case "number": { + const rvalue = this.numberConvert(rhs); + if (rvalue !== null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; + break; + } + case "string": { + const rvalue = this.stringConvert(rhs); + if (rvalue !== null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; + break; + } + case "boolean": { + const rvalue = this.booleanConvert(rhs); + if (rvalue != null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; + break; + } + default: { + if (isEqualsDeep(lhs, rhs)) return 0; + break; + } + } + + return null; + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/jsonexpr/evaluator.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/jsonexpr/evaluator.ts src/__tests__/jsonexpr/evaluator.test.ts +git commit -m "feat: add JsonExpr evaluator with type conversion and comparison" +``` + +--- + +## Task 9: JsonExpr Operators + +**Files:** +- Create: `src/jsonexpr/operators.ts` +- Create: `src/__tests__/jsonexpr/operators.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { Evaluator } from "../../jsonexpr/evaluator"; +import { + AndCombinator, + EqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualOperator, + InOperator, + LessThanOperator, + LessThanOrEqualOperator, + MatchOperator, + NotOperator, + NullOperator, + OrCombinator, + SemverEqualsOperator, + SemverGreaterThanOperator, + SemverGreaterThanOrEqualOperator, + SemverLessThanOperator, + SemverLessThanOrEqualOperator, + ValueOperator, + VarOperator, +} from "../../jsonexpr/operators"; + +function makeEvaluator(vars: Record = {}): Evaluator { + const operators = { + and: new AndCombinator(), + or: new OrCombinator(), + value: new ValueOperator(), + var: new VarOperator(), + null: new NullOperator(), + not: new NotOperator(), + in: new InOperator(), + match: new MatchOperator(), + eq: new EqualsOperator(), + gt: new GreaterThanOperator(), + gte: new GreaterThanOrEqualOperator(), + lt: new LessThanOperator(), + lte: new LessThanOrEqualOperator(), + semver_eq: new SemverEqualsOperator(), + semver_gt: new SemverGreaterThanOperator(), + semver_gte: new SemverGreaterThanOrEqualOperator(), + semver_lt: new SemverLessThanOperator(), + semver_lte: new SemverLessThanOrEqualOperator(), + }; + return new Evaluator(operators, vars); +} + +describe("ValueOperator", () => { + test("returns the value as-is", () => { + const evaluator = makeEvaluator(); + const op = new ValueOperator(); + expect(op.evaluate(evaluator, 42)).toBe(42); + expect(op.evaluate(evaluator, "hello")).toBe("hello"); + expect(op.evaluate(evaluator, null)).toBe(null); + }); +}); + +describe("VarOperator", () => { + test("extracts variable by path string", () => { + const evaluator = makeEvaluator({ name: "John", nested: { key: "val" } }); + const op = new VarOperator(); + expect(op.evaluate(evaluator, "name")).toBe("John"); + expect(op.evaluate(evaluator, "nested/key")).toBe("val"); + }); + + test("extracts variable by path object", () => { + const evaluator = makeEvaluator({ name: "John" }); + const op = new VarOperator(); + expect(op.evaluate(evaluator, { path: "name" })).toBe("John"); + }); + + test("returns null for non-string path", () => { + const evaluator = makeEvaluator(); + const op = new VarOperator(); + expect(op.evaluate(evaluator, 42)).toBe(null); + }); +}); + +describe("AndCombinator", () => { + test("returns true when all truthy", () => { + const evaluator = makeEvaluator(); + const op = new AndCombinator(); + expect(op.evaluate(evaluator, [{ value: true }, { value: 1 }])).toBe(true); + }); + + test("returns false when any falsy", () => { + const evaluator = makeEvaluator(); + const op = new AndCombinator(); + expect(op.evaluate(evaluator, [{ value: true }, { value: false }])).toBe(false); + }); + + test("returns true for empty array", () => { + const evaluator = makeEvaluator(); + const op = new AndCombinator(); + expect(op.evaluate(evaluator, [])).toBe(true); + }); +}); + +describe("OrCombinator", () => { + test("returns true when any truthy", () => { + const evaluator = makeEvaluator(); + const op = new OrCombinator(); + expect(op.evaluate(evaluator, [{ value: false }, { value: true }])).toBe(true); + }); + + test("returns false when all falsy", () => { + const evaluator = makeEvaluator(); + const op = new OrCombinator(); + expect(op.evaluate(evaluator, [{ value: false }, { value: 0 }])).toBe(false); + }); + + test("returns true for empty array (vacuous truth)", () => { + const evaluator = makeEvaluator(); + const op = new OrCombinator(); + expect(op.evaluate(evaluator, [])).toBe(true); + }); +}); + +describe("NotOperator", () => { + test("negates boolean", () => { + const evaluator = makeEvaluator(); + const op = new NotOperator(); + expect(op.evaluate(evaluator, { value: true })).toBe(false); + expect(op.evaluate(evaluator, { value: false })).toBe(true); + }); +}); + +describe("NullOperator", () => { + test("checks if value is null", () => { + const evaluator = makeEvaluator(); + const op = new NullOperator(); + expect(op.evaluate(evaluator, { value: null })).toBe(true); + expect(op.evaluate(evaluator, { value: 0 })).toBe(false); + }); +}); + +describe("EqualsOperator", () => { + test("compares values for equality", () => { + const evaluator = makeEvaluator(); + const op = new EqualsOperator(); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 1 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(false); + expect(op.evaluate(evaluator, [{ value: "a" }, { value: "a" }])).toBe(true); + }); +}); + +describe("Comparison operators", () => { + const evaluator = makeEvaluator(); + + test("GreaterThan", () => { + const op = new GreaterThanOperator(); + expect(op.evaluate(evaluator, [{ value: 2 }, { value: 1 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(false); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 1 }])).toBe(false); + }); + + test("GreaterThanOrEqual", () => { + const op = new GreaterThanOrEqualOperator(); + expect(op.evaluate(evaluator, [{ value: 2 }, { value: 1 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 1 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(false); + }); + + test("LessThan", () => { + const op = new LessThanOperator(); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 2 }, { value: 1 }])).toBe(false); + }); + + test("LessThanOrEqual", () => { + const op = new LessThanOrEqualOperator(); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 1 }])).toBe(true); + }); +}); + +describe("InOperator", () => { + const evaluator = makeEvaluator(); + const op = new InOperator(); + + test("array membership", () => { + expect(op.evaluate(evaluator, [{ value: [1, 2, 3] }, { value: 2 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: [1, 2, 3] }, { value: 4 }])).toBe(false); + }); + + test("string containment", () => { + expect(op.evaluate(evaluator, [{ value: "hello world" }, { value: "world" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: "hello" }, { value: "world" }])).toBe(false); + }); + + test("object key check", () => { + expect(op.evaluate(evaluator, [{ value: { a: 1 } }, { value: "a" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: { a: 1 } }, { value: "b" }])).toBe(false); + }); +}); + +describe("MatchOperator", () => { + const evaluator = makeEvaluator(); + const op = new MatchOperator(); + + test("matches regex", () => { + expect(op.evaluate(evaluator, [{ value: "hello123" }, { value: "\\d+" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: "hello" }, { value: "\\d+" }])).toBe(false); + }); +}); + +describe("Semver operators", () => { + const evaluator = makeEvaluator(); + + test("semver_eq", () => { + const op = new SemverEqualsOperator(); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "1.0.0" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "1.0.1" }])).toBe(false); + }); + + test("semver_gt", () => { + const op = new SemverGreaterThanOperator(); + expect(op.evaluate(evaluator, [{ value: "2.0.0" }, { value: "1.0.0" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "2.0.0" }])).toBe(false); + }); + + test("semver_gte", () => { + const op = new SemverGreaterThanOrEqualOperator(); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "1.0.0" }])).toBe(true); + }); + + test("semver_lt", () => { + const op = new SemverLessThanOperator(); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "2.0.0" }])).toBe(true); + }); + + test("semver_lte", () => { + const op = new SemverLessThanOrEqualOperator(); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "1.0.0" }])).toBe(true); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/jsonexpr/operators.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/jsonexpr/operators.ts** + +All 20 operators consolidated in one file: + +```typescript +import { Evaluator } from "./evaluator"; +import { isObject } from "../utils"; + +export class ValueOperator { + evaluate(_: Evaluator, value: unknown): unknown { + return value; + } +} + +export class VarOperator { + evaluate(evaluator: Evaluator, path: unknown): unknown { + if (isObject(path)) { + path = (path as { path: string }).path; + } + return typeof path === "string" ? evaluator.extractVar(path) : null; + } +} + +export class AndCombinator { + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + for (const expr of args) { + if (!evaluator.booleanConvert(evaluator.evaluate(expr))) return false; + } + return true; + } + return null; + } +} + +export class OrCombinator { + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + for (const expr of args) { + if (evaluator.booleanConvert(evaluator.evaluate(expr))) return true; + } + return args.length === 0; + } + return null; + } +} + +abstract class UnaryOperator { + abstract unary(evaluator: Evaluator, arg: unknown): boolean; + evaluate(evaluator: Evaluator, arg: unknown): boolean { + arg = evaluator.evaluate(arg); + return this.unary(evaluator, arg); + } +} + +export class NotOperator extends UnaryOperator { + unary(evaluator: Evaluator, arg: unknown): boolean { + return !evaluator.booleanConvert(arg); + } +} + +export class NullOperator extends UnaryOperator { + unary(_: Evaluator, value: unknown): boolean { + return value === null; + } +} + +abstract class BinaryOperator { + abstract binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null; + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + const lhs = args.length > 0 ? evaluator.evaluate(args[0]) : null; + if (lhs !== null) { + const rhs = args.length > 1 ? evaluator.evaluate(args[1]) : null; + if (rhs !== null) { + return this.binary(evaluator, lhs, rhs); + } + } + } + return null; + } +} + +export class EqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} + +export class GreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} + +export class GreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} + +export class LessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} + +export class LessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +} + +export class InOperator extends BinaryOperator { + binary(evaluator: Evaluator, haystack: unknown, needle: unknown): boolean | null { + if (Array.isArray(haystack)) { + for (const item of haystack) { + if (evaluator.compare(item, needle) === 0) return true; + } + return false; + } else if (typeof haystack === "string") { + const needleString = evaluator.stringConvert(needle); + return needleString !== null && haystack.includes(needleString); + } else if (isObject(haystack)) { + const needleString = evaluator.stringConvert(needle); + return needleString != null && Object.prototype.hasOwnProperty.call(haystack, needleString); + } + return null; + } +} + +export class MatchOperator extends BinaryOperator { + binary(evaluator: Evaluator, text: unknown, pattern: unknown): boolean | null { + const textStr = evaluator.stringConvert(text); + if (textStr !== null) { + const patternStr = evaluator.stringConvert(pattern); + if (patternStr !== null) { + try { + return new RegExp(patternStr).test(textStr); + } catch { + return null; + } + } + } + return null; + } +} + +export class SemverEqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} + +export class SemverGreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} + +export class SemverGreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} + +export class SemverLessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} + +export class SemverLessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/jsonexpr/operators.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/jsonexpr/operators.ts src/__tests__/jsonexpr/operators.test.ts +git commit -m "feat: add all 20 JsonExpr operators" +``` + +--- + +## Task 10: JsonExpr Facade & AudienceMatcher + +**Files:** +- Create: `src/jsonexpr/jsonexpr.ts` +- Create: `src/matcher.ts` +- Create: `src/__tests__/jsonexpr/jsonexpr.test.ts` +- Create: `src/__tests__/matcher.test.ts` + +- [ ] **Step 1: Write the failing tests for JsonExpr** + +```typescript +import { describe, expect, test } from "vitest"; +import { JsonExpr } from "../../jsonexpr/jsonexpr"; + +describe("JsonExpr", () => { + const jsonExpr = new JsonExpr(); + + test("evaluateBooleanExpr with and-array", () => { + expect(jsonExpr.evaluateBooleanExpr([{ value: true }, { value: 1 }], {})).toBe(true); + expect(jsonExpr.evaluateBooleanExpr([{ value: true }, { value: false }], {})).toBe(false); + }); + + test("evaluateBooleanExpr with object expr", () => { + expect(jsonExpr.evaluateBooleanExpr({ value: true }, {})).toBe(true); + expect(jsonExpr.evaluateBooleanExpr({ value: false }, {})).toBe(false); + }); + + test("evaluateExpr returns raw value", () => { + expect(jsonExpr.evaluateExpr({ value: 42 }, {})).toBe(42); + expect(jsonExpr.evaluateExpr({ value: "hello" }, {})).toBe("hello"); + }); + + test("var operator extracts from vars", () => { + expect(jsonExpr.evaluateExpr({ var: "name" }, { name: "Alice" })).toBe("Alice"); + }); + + test("complex expression with eq and var", () => { + const expr = { eq: [{ var: "age" }, { value: 25 }] }; + expect(jsonExpr.evaluateBooleanExpr(expr, { age: 25 })).toBe(true); + expect(jsonExpr.evaluateBooleanExpr(expr, { age: 30 })).toBe(false); + }); +}); +``` + +- [ ] **Step 2: Write the failing tests for AudienceMatcher** + +```typescript +import { describe, expect, test } from "vitest"; +import { AudienceMatcher } from "../matcher"; + +describe("AudienceMatcher", () => { + const matcher = new AudienceMatcher(); + + test("evaluates matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: true }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + test("evaluates non-matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: false }] }); + expect(matcher.evaluate(audience, {})).toBe(false); + }); + + test("evaluates with not operator", () => { + const audience = JSON.stringify({ filter: [{ not: { value: false } }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + test("returns null for invalid JSON", () => { + expect(matcher.evaluate("invalid json", {})).toBe(null); + }); + + test("returns null for missing filter", () => { + expect(matcher.evaluate(JSON.stringify({}), {})).toBe(null); + }); + + test("returns null for null filter", () => { + expect(matcher.evaluate(JSON.stringify({ filter: null }), {})).toBe(null); + }); +}); +``` + +- [ ] **Step 3: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/jsonexpr/jsonexpr.test.ts src/__tests__/matcher.test.ts` + +Expected: FAIL - modules not found + +- [ ] **Step 4: Write src/jsonexpr/jsonexpr.ts** + +```typescript +import { Evaluator } from "./evaluator"; +import { + AndCombinator, + EqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualOperator, + InOperator, + LessThanOperator, + LessThanOrEqualOperator, + MatchOperator, + NotOperator, + NullOperator, + OrCombinator, + SemverEqualsOperator, + SemverGreaterThanOperator, + SemverGreaterThanOrEqualOperator, + SemverLessThanOperator, + SemverLessThanOrEqualOperator, + ValueOperator, + VarOperator, +} from "./operators"; + +const operators = { + and: new AndCombinator(), + or: new OrCombinator(), + value: new ValueOperator(), + var: new VarOperator(), + null: new NullOperator(), + not: new NotOperator(), + in: new InOperator(), + match: new MatchOperator(), + eq: new EqualsOperator(), + gt: new GreaterThanOperator(), + gte: new GreaterThanOrEqualOperator(), + lt: new LessThanOperator(), + lte: new LessThanOrEqualOperator(), + semver_eq: new SemverEqualsOperator(), + semver_gt: new SemverGreaterThanOperator(), + semver_gte: new SemverGreaterThanOrEqualOperator(), + semver_lt: new SemverLessThanOperator(), + semver_lte: new SemverLessThanOrEqualOperator(), +}; + +export class JsonExpr { + evaluateBooleanExpr(expr: unknown, vars: Record): boolean { + const evaluator = new Evaluator(operators, vars); + return evaluator.booleanConvert(evaluator.evaluate(expr)); + } + + evaluateExpr(expr: unknown, vars: Record): unknown { + const evaluator = new Evaluator(operators, vars); + return evaluator.evaluate(expr); + } +} +``` + +- [ ] **Step 5: Write src/matcher.ts** + +```typescript +import { isObject } from "./utils"; +import { JsonExpr } from "./jsonexpr/jsonexpr"; + +export class AudienceMatcher { + private readonly _jsonExpr = new JsonExpr(); + + evaluate(audienceString: string, vars: Record): boolean | null { + try { + const audience = JSON.parse(audienceString); + if (audience && audience.filter) { + if (Array.isArray(audience.filter) || isObject(audience.filter)) { + return this._jsonExpr.evaluateBooleanExpr(audience.filter, vars); + } + } + } catch { + // invalid JSON + } + return null; + } +} +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/jsonexpr/jsonexpr.test.ts src/__tests__/matcher.test.ts` + +Expected: All tests pass + +- [ ] **Step 7: Commit** + +```bash +git add src/jsonexpr/jsonexpr.ts src/matcher.ts src/__tests__/jsonexpr/jsonexpr.test.ts src/__tests__/matcher.test.ts +git commit -m "feat: add JsonExpr facade and AudienceMatcher" +``` + +--- + +## Task 11: HTTP Client + +**Files:** +- Create: `src/client.ts` +- Create: `src/__tests__/client.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; +import { Client } from "../client"; +import type { ClientOptions } from "../types"; + +const defaultOpts: ClientOptions = { + agent: "test-agent", + apiKey: "test-api-key", + application: "test-app", + endpoint: "https://test.absmartly.io/v1", + environment: "test", +}; + +describe("Client", () => { + describe("constructor validation", () => { + test("throws for missing apiKey", () => { + const opts = { ...defaultOpts, apiKey: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'apiKey' in options argument"); + }); + + test("throws for missing endpoint", () => { + const opts = { ...defaultOpts, endpoint: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'endpoint' in options argument"); + }); + + test("throws for missing environment", () => { + const opts = { ...defaultOpts, environment: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'environment' in options argument"); + }); + + test("throws for missing application", () => { + const opts = { ...defaultOpts, application: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'application' in options argument"); + }); + + test("throws for empty apiKey", () => { + const opts = { ...defaultOpts, apiKey: "" }; + expect(() => new Client(opts)).toThrow("Invalid 'apiKey' in options argument"); + }); + + test("accepts ApplicationObject", () => { + const opts = { ...defaultOpts, application: { name: "my-app", version: "1.0.0" } }; + const client = new Client(opts); + expect(client.getApplication()).toEqual({ name: "my-app", version: "1.0.0" }); + }); + + test("converts string application to ApplicationObject", () => { + const client = new Client(defaultOpts); + expect(client.getApplication()).toEqual({ name: "test-app", version: 0 }); + }); + }); + + describe("accessors", () => { + test("getAgent", () => { + const client = new Client(defaultOpts); + expect(client.getAgent()).toBe("test-agent"); + }); + + test("getEnvironment", () => { + const client = new Client(defaultOpts); + expect(client.getEnvironment()).toBe("test"); + }); + }); + + describe("getContext", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("makes GET request to /context", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({ experiments: [] }) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.getContext(); + + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + const [url] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(url).toContain("/context"); + expect(url).toContain("application=test-app"); + expect(url).toContain("environment=test"); + }); + }); + + describe("publish", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("makes PUT request to /context with auth headers", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({}) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.publish({ + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + }); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(opts.method).toBe("PUT"); + expect(opts.headers["X-API-Key"]).toBe("test-api-key"); + expect(opts.headers["X-Agent"]).toBe("test-agent"); + expect(opts.headers["X-Environment"]).toBe("test"); + }); + + test("omits empty goals and exposures arrays", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({}) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.publish({ + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + goals: [], + exposures: [], + }); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + const body = JSON.parse(opts.body); + expect(body.goals).toBeUndefined(); + expect(body.exposures).toBeUndefined(); + }); + }); + + describe("retry logic", () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + test("retries on server error", async () => { + const failResponse = { ok: false, status: 500, statusText: "Server Error", text: () => Promise.resolve("") }; + const successResponse = { ok: true, json: () => Promise.resolve({ data: "ok" }) }; + + (globalThis.fetch as ReturnType) + .mockResolvedValueOnce(failResponse) + .mockResolvedValueOnce(successResponse); + + const client = new Client({ ...defaultOpts, retries: 3, timeout: 10000 }); + const promise = client.getContext(); + + await vi.runAllTimersAsync(); + const result = await promise; + + expect(result).toEqual({ data: "ok" }); + expect(globalThis.fetch).toHaveBeenCalledTimes(2); + }); + + test("does not retry on 4xx error", async () => { + const failResponse = { + ok: false, + status: 400, + statusText: "Bad Request", + text: () => Promise.resolve("bad request"), + }; + + (globalThis.fetch as ReturnType).mockResolvedValue(failResponse); + + const client = new Client({ ...defaultOpts, retries: 3, timeout: 10000 }); + const promise = client.getContext(); + + await vi.runAllTimersAsync(); + await expect(promise).rejects.toThrow("bad request"); + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + }); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/client.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/client.ts** + +```typescript +import { AbortError, RetryError, TimeoutError } from "./errors"; +import type { + ApplicationObject, + ClientOptions, + ClientRequestOptions, + ContextOptions, + ContextParams, + FetchResponse, + NormalizedClientOptions, + PublishParams, +} from "./types"; + +export class Client { + private readonly _opts: NormalizedClientOptions; + private readonly _delay: number; + + constructor(opts: ClientOptions) { + const merged: Record = Object.assign( + { agent: "javascript-client", retries: 5, timeout: 3000, keepalive: true }, + opts, + ); + + for (const key of ["agent", "application", "apiKey", "endpoint", "environment"]) { + if (key in merged && merged[key] !== undefined) { + const value = merged[key]; + if (typeof value !== "string" || (value as string).length === 0) { + if (key === "application") { + if (value !== null && typeof value === "object" && "name" in (value as object)) continue; + } + throw new Error(`Invalid '${key}' in options argument`); + } + } else { + throw new Error(`Missing '${key}' in options argument`); + } + } + + if (typeof merged.application === "string") { + merged.application = { name: merged.application, version: 0 }; + } + + this._opts = merged as unknown as NormalizedClientOptions; + this._delay = 50; + } + + getContext(options?: Partial): Promise { + return this.getUnauthed({ + ...options, + path: "/context", + query: { + application: this._opts.application.name, + environment: this._opts.environment, + }, + }); + } + + createContext(params: ContextParams, options: ContextOptions): Promise { + return this.post({ ...options, path: "/context", body: { units: params.units } }); + } + + publish(params: PublishParams, options?: ClientRequestOptions): Promise { + const body: Record = { + units: params.units, + hashed: params.hashed, + publishedAt: params.publishedAt || Date.now(), + sdkVersion: params.sdkVersion, + }; + + if (Array.isArray(params.goals) && params.goals.length > 0) { + body.goals = params.goals; + } + if (Array.isArray(params.exposures) && params.exposures.length > 0) { + body.exposures = params.exposures; + } + if (Array.isArray(params.attributes) && params.attributes.length > 0) { + body.attributes = params.attributes; + } + + return this.put({ ...options, path: "/context", body }); + } + + request(options: ClientRequestOptions): Promise { + let url = `${this._opts.endpoint}${options.path}`; + if (options.query) { + const keys = Object.keys(options.query); + if (keys.length > 0) { + const encoded = keys + .map((k) => (options.query ? `${k}=${encodeURIComponent(options.query[k]!)}` : null)) + .join("&"); + url = `${url}?${encoded}`; + } + } + + const controller = new AbortController(); + + const tryOnce = (): Promise => { + const opts: RequestInit = { + method: options.method, + body: options.body !== undefined ? JSON.stringify(options.body, null, 0) : undefined, + signal: controller.signal, + keepalive: this._opts.keepalive, + }; + + if (options.auth) { + opts.headers = { + "Content-Type": "application/json", + "X-API-Key": this._opts.apiKey, + "X-Agent": this._opts.agent, + "X-Environment": this._opts.environment, + "X-Application": this._opts.application.name, + "X-Application-Version": String(this._opts.application.version), + }; + } + + return fetch(url, opts).then((response: Response) => { + if (!response.ok) { + const bail = response.status >= 400 && response.status < 500; + return response.text().then((text: string) => { + const error: Error & { _bail?: boolean } = new Error( + text !== null && text.length > 0 ? text : response.statusText, + ); + error._bail = bail; + return Promise.reject(error); + }); + } + return response.json(); + }); + }; + + type WaitFn = ((ms: number) => Promise) & { reject?: (reason: AbortError) => void }; + type TryWithFn = ((retries: number, timeout: number, tries?: number, waited?: number) => Promise) & { + timedout?: boolean; + }; + + const wait: WaitFn = (ms) => + new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + delete wait.reject; + resolve(); + }, ms); + + wait.reject = (reason) => { + clearTimeout(timeoutId); + reject(reason); + }; + }); + + const tryWith: TryWithFn = (retries, timeout, tries = 0, waited = 0) => { + delete tryWith.timedout; + + return tryOnce().catch((reason: Error & { _bail?: boolean }) => { + if (reason._bail || retries <= 0) throw new Error(reason.message); + if (tries >= retries) throw new RetryError(tries, reason, url); + if (waited >= timeout || reason.name === "AbortError") { + if (tryWith.timedout) throw new TimeoutError(timeout); + throw reason; + } + + let delay = (1 << tries) * this._delay + 0.5 * Math.random() * this._delay; + if (waited + delay > timeout) delay = timeout - waited; + + return wait(delay).then(() => tryWith(retries, timeout, tries + 1, waited + delay)); + }); + }; + + const abort = () => { + if (wait.reject) { + wait.reject(new AbortError()); + } else { + controller.abort(); + } + }; + + if (options.signal) { + options.signal.addEventListener("abort", abort); + } + + const timeout = options.timeout || this._opts.timeout || 0; + const timeoutId = + timeout > 0 + ? setTimeout(() => { + tryWith.timedout = true; + abort(); + }, timeout) + : 0; + + const finalCleanUp = () => { + clearTimeout(timeoutId); + if (options.signal) { + options.signal.removeEventListener("abort", abort); + } + }; + + return tryWith(this._opts.retries ?? 5, this._opts.timeout ?? 3000) + .then((value) => { + finalCleanUp(); + return value; + }) + .catch((error: Error) => { + finalCleanUp(); + throw error; + }); + } + + post(options: ClientRequestOptions): Promise { + return this.request({ ...options, auth: true, method: "POST" }); + } + + put(options: ClientRequestOptions): Promise { + return this.request({ ...options, auth: true, method: "PUT" }); + } + + getAgent(): string { + return this._opts.agent; + } + + getApplication(): ApplicationObject { + return this._opts.application; + } + + getEnvironment(): string { + return this._opts.environment; + } + + getUnauthed(options: ClientRequestOptions): Promise { + return this.request({ ...options, method: "GET" }); + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/client.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/client.ts src/__tests__/client.test.ts +git commit -m "feat: add HTTP client with retry logic and exponential backoff" +``` + +--- + +## Task 12: Provider & Publisher + +**Files:** +- Create: `src/provider.ts` +- Create: `src/publisher.ts` +- Create: `src/__tests__/provider.test.ts` +- Create: `src/__tests__/publisher.test.ts` + +- [ ] **Step 1: Write the failing tests for provider** + +```typescript +import { describe, expect, test, vi } from "vitest"; +import { ContextDataProvider } from "../provider"; + +describe("ContextDataProvider", () => { + test("delegates to sdk.getClient().getContext()", async () => { + const mockGetContext = vi.fn().mockResolvedValue({ experiments: [] }); + const mockSdk = { getClient: () => ({ getContext: mockGetContext }) }; + + const provider = new ContextDataProvider(); + const result = await provider.getContextData(mockSdk, { path: "/test" }); + + expect(mockGetContext).toHaveBeenCalledWith({ path: "/test" }); + expect(result).toEqual({ experiments: [] }); + }); +}); +``` + +- [ ] **Step 2: Write the failing tests for publisher** + +```typescript +import { describe, expect, test, vi } from "vitest"; +import { ContextPublisher } from "../publisher"; + +describe("ContextPublisher", () => { + test("delegates to sdk.getClient().publish()", async () => { + const mockPublish = vi.fn().mockResolvedValue({}); + const mockSdk = { getClient: () => ({ publish: mockPublish }) }; + const request = { + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + }; + + const publisher = new ContextPublisher(); + await publisher.publish(request, mockSdk, {}, { path: "/test" }); + + expect(mockPublish).toHaveBeenCalledWith(request, { path: "/test" }); + }); +}); +``` + +- [ ] **Step 3: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/provider.test.ts src/__tests__/publisher.test.ts` + +Expected: FAIL - modules not found + +- [ ] **Step 4: Write src/provider.ts** + +```typescript +import type { ClientRequestOptions, ContextData } from "./types"; + +interface SDKLike { + getClient(): { getContext(options?: Partial): Promise }; +} + +export class ContextDataProvider { + getContextData(sdk: SDKLike, requestOptions?: Partial): Promise { + return sdk.getClient().getContext(requestOptions) as Promise; + } +} +``` + +- [ ] **Step 5: Write src/publisher.ts** + +```typescript +import type { ClientRequestOptions, PublishParams } from "./types"; + +interface SDKLike { + getClient(): { publish(request: PublishParams, options?: ClientRequestOptions): Promise }; +} + +export class ContextPublisher { + publish(request: PublishParams, sdk: SDKLike, _context: unknown, requestOptions?: ClientRequestOptions): Promise { + return sdk.getClient().publish(request, requestOptions); + } +} +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/provider.test.ts src/__tests__/publisher.test.ts` + +Expected: All tests pass + +- [ ] **Step 7: Commit** + +```bash +git add src/provider.ts src/publisher.ts src/__tests__/provider.test.ts src/__tests__/publisher.test.ts +git commit -m "feat: add ContextDataProvider and ContextPublisher" +``` + +--- + +## Task 13: Context Class + +This is the largest and most complex module. The `Context` class manages experiment assignments, exposure tracking, goal tracking, attribute management, and publish/refresh lifecycle. + +**Files:** +- Create: `src/context.ts` +- Create: `src/__tests__/context.test.ts` + +- [ ] **Step 1: Write the failing tests** + +Write comprehensive tests covering all Context functionality. Due to the size of this module, the tests are extensive. The test should cover: + +1. Constructor with immediate data vs promise data +2. `isReady()`, `isFailed()`, `isFinalized()`, `isFinalizing()` state checks +3. `ready()` promise resolution +4. `unit()` / `units()` - setting and validating unit identifiers +5. `attribute()` / `attributes()` - setting user attributes +6. `treatment()` - variant assignment with exposure tracking +7. `peek()` - variant assignment without exposure +8. `track()` - goal tracking +9. `publish()` - flushing pending events +10. `refresh()` - fetching updated experiment data +11. `finalize()` - cleanup and final publish +12. `variableValue()` / `peekVariableValue()` - experiment variable access +13. `override()` / `overrides()` - forced variant assignment +14. `customAssignment()` / `customAssignments()` - custom assignment logic +15. `customFieldValue()` / `customFieldKeys()` - custom field access +16. Auto-publish timeout behavior +17. Refresh interval behavior +18. Error handling and failed state +19. System attributes inclusion + +The test file should use the same experiment fixture data from the original test suite to ensure byte-level compatibility. Create the file with the complete test content from the original `context.test.js` adapted to Vitest syntax (replace `jest.fn()` with `vi.fn()`, `jest.spyOn` with `vi.spyOn`, etc.). + +Run: `npx vitest run src/__tests__/context.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 2: Write src/context.ts** + +Port the Context class from the original codebase with strict TypeScript types. The implementation must: + +- Import types from `./types` instead of declaring locally +- Import `hashUnit` from `./hashing` instead of `./utils` +- Import `VariantAssigner` from `./assigner` +- Import `AudienceMatcher` from `./matcher` +- Import `insertUniqueSorted` from `./algorithm` +- Import `arrayEqualsShallow`, `isObject`, `isPromise` from `./utils` +- Import `SDK_VERSION` from `./version` +- Use native `setTimeout`/`setInterval`/`clearTimeout`/`clearInterval` +- Use `structuredClone` in `_buildAttributes` if cloning is needed +- Maintain identical API: all public methods have the same signatures +- All private methods prefixed with `_` as in original + +The logic must be identical to the original `context.ts` to maintain backward compatibility. + +- [ ] **Step 3: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/context.test.ts` + +Expected: All tests pass + +- [ ] **Step 4: Commit** + +```bash +git add src/context.ts src/__tests__/context.test.ts +git commit -m "feat: add Context class with experiment assignment and lifecycle management" +``` + +--- + +## Task 14: SDK Class + +**Files:** +- Create: `src/sdk.ts` +- Create: `src/__tests__/sdk.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test, vi } from "vitest"; +import { SDK } from "../sdk"; +import type { ClientOptions, ContextParams } from "../types"; + +const defaultOpts: ClientOptions = { + agent: "test-agent", + apiKey: "test-api-key", + application: "test-app", + endpoint: "https://test.absmartly.io/v1", + environment: "test", +}; + +describe("SDK", () => { + test("creates SDK instance", () => { + const sdk = new SDK(defaultOpts); + expect(sdk).toBeInstanceOf(SDK); + }); + + test("getClient returns client", () => { + const sdk = new SDK(defaultOpts); + expect(sdk.getClient()).toBeDefined(); + }); + + test("get/set event logger", () => { + const sdk = new SDK(defaultOpts); + const logger = vi.fn(); + sdk.setEventLogger(logger); + expect(sdk.getEventLogger()).toBe(logger); + }); + + test("default event logger logs errors", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const error = new Error("test"); + SDK.defaultEventLogger({} as never, "error", error); + expect(consoleSpy).toHaveBeenCalledWith(error); + consoleSpy.mockRestore(); + }); + + test("createContext validates unit types", () => { + const sdk = new SDK(defaultOpts); + expect(() => sdk.createContext({ units: { session_id: true as unknown as string } })).toThrow( + "Unit 'session_id' UID is of unsupported type 'boolean'. UID must be one of ['string', 'number']", + ); + }); + + test("createContext validates empty string units", () => { + const sdk = new SDK(defaultOpts); + expect(() => sdk.createContext({ units: { session_id: "" } })).toThrow( + "Unit 'session_id' UID length must be >= 1", + ); + }); + + test("createContext returns Context instance", () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) })); + const sdk = new SDK(defaultOpts); + const context = sdk.createContext({ units: { session_id: "abc" } }); + expect(context).toBeDefined(); + vi.restoreAllMocks(); + }); + + test("createContextWith accepts pre-fetched data", () => { + const sdk = new SDK(defaultOpts); + const context = sdk.createContextWith({ units: { session_id: "abc" } }, { experiments: [] }); + expect(context).toBeDefined(); + expect(context.isReady()).toBe(true); + }); + + test("get/set context publisher", () => { + const sdk = new SDK(defaultOpts); + const publisher = { publish: vi.fn() }; + sdk.setContextPublisher(publisher as never); + expect(sdk.getContextPublisher()).toBe(publisher); + }); + + test("get/set context data provider", () => { + const sdk = new SDK(defaultOpts); + const provider = { getContextData: vi.fn() }; + sdk.setContextDataProvider(provider as never); + expect(sdk.getContextDataProvider()).toBe(provider); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/sdk.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/sdk.ts** + +```typescript +import { Client } from "./client"; +import { Context } from "./context"; +import { ContextPublisher } from "./publisher"; +import { ContextDataProvider } from "./provider"; +import type { + ClientOptions, + ClientRequestOptions, + ContextData, + ContextOptions, + ContextParams, + EventLogger, + EventLoggerData, + EventName, + Exposure, + Goal, + PublishParams, + SDKOptions, +} from "./types"; + +function isLongLivedApp(): boolean { + return (typeof window !== "undefined" && typeof window.document !== "undefined") || + (typeof navigator !== "undefined" && navigator.product === "ReactNative"); +} + +const CLIENT_OPTION_KEYS = ["application", "agent", "apiKey", "endpoint", "keepalive", "environment", "retries", "timeout"]; + +export class SDK { + static defaultEventLogger: EventLogger = (_, eventName, data) => { + if (eventName === "error") console.error(data); + }; + + private _eventLogger: EventLogger; + private _publisher: ContextPublisher; + private _provider: ContextDataProvider; + private readonly _client: Client; + + constructor(options: ClientOptions & SDKOptions) { + const clientOptions = Object.assign( + { agent: "absmartly-javascript-sdk" }, + ...Object.entries(options || {}) + .filter((x) => CLIENT_OPTION_KEYS.indexOf(x[0]) !== -1) + .map((x) => ({ [x[0]]: x[1] })), + ) as ClientOptions; + + this._client = (options.client as Client) || new Client(clientOptions); + this._eventLogger = options.eventLogger || SDK.defaultEventLogger; + this._publisher = (options.publisher as ContextPublisher) || new ContextPublisher(); + this._provider = (options.provider as ContextDataProvider) || new ContextDataProvider(); + } + + getContextData(requestOptions: ClientRequestOptions): Promise { + return this._provider.getContextData(this, requestOptions); + } + + createContext( + params: ContextParams, + options?: Partial, + requestOptions?: Partial, + ): Context { + SDK._validateParams(params); + const fullOptions = SDK._contextOptions(options); + const data = this._provider.getContextData(this, requestOptions); + return new Context(this, fullOptions, params, data); + } + + createContextWith( + params: ContextParams, + data: ContextData | Promise, + options?: Partial, + ): Context { + SDK._validateParams(params); + const fullOptions = SDK._contextOptions(options); + return new Context(this, fullOptions, params, data); + } + + setEventLogger(logger: EventLogger): void { + this._eventLogger = logger; + } + + getEventLogger(): EventLogger { + return this._eventLogger; + } + + setContextPublisher(publisher: ContextPublisher): void { + this._publisher = publisher; + } + + getContextPublisher(): ContextPublisher { + return this._publisher; + } + + setContextDataProvider(provider: ContextDataProvider): void { + this._provider = provider; + } + + getContextDataProvider(): ContextDataProvider { + return this._provider; + } + + getClient(): Client { + return this._client; + } + + private static _contextOptions(options?: Partial): ContextOptions { + return Object.assign( + { publishDelay: isLongLivedApp() ? 100 : -1, refreshPeriod: 0 }, + options || {}, + ) as ContextOptions; + } + + private static _validateParams(params: ContextParams): void { + for (const [key, value] of Object.entries(params.units)) { + const type = typeof value; + if (type !== "string" && type !== "number") { + throw new Error(`Unit '${key}' UID is of unsupported type '${type}'. UID must be one of ['string', 'number']`); + } + if (typeof value === "string" && value.length === 0) { + throw new Error(`Unit '${key}' UID length must be >= 1`); + } + } + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/sdk.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/sdk.ts src/__tests__/sdk.test.ts +git commit -m "feat: add SDK class with context creation and configuration" +``` + +--- + +## Task 15: Config Merge Utility + +**Files:** +- Create: `src/config.ts` +- Create: `src/__tests__/config.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test, vi } from "vitest"; +import { mergeConfig } from "../config"; + +function mockContext(variableKeys: Record, variableValues: Record = {}) { + return { + variableKeys: () => variableKeys, + variableValue: (key: string, defaultValue: unknown) => + key in variableValues ? variableValues[key] : defaultValue, + }; +} + +describe("mergeConfig", () => { + test("returns new object, does not mutate original", () => { + const original = { key: "value" }; + const context = mockContext({}); + const result = mergeConfig(context as never, original); + expect(result).not.toBe(original); + expect(result).toEqual({ key: "value" }); + }); + + test("creates getter for experiment variable", () => { + const context = mockContext( + { "button.color": ["exp_test"] }, + { "button.color": "red" }, + ); + const config = { button: { color: "blue" } }; + const result = mergeConfig(context as never, config); + expect(result.button.color).toBe("red"); + }); + + test("falls back to default when variable not set", () => { + const context = mockContext({ "button.color": ["exp_test"] }); + const config = { button: { color: "blue" } }; + const result = mergeConfig(context as never, config); + expect(result.button.color).toBe("blue"); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/config.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/config.ts** + +```typescript +import { isObject } from "./utils"; + +interface ConfigContext { + variableKeys(): Record; + variableValue(key: string, defaultValue: unknown): unknown; +} + +export function mergeConfig(context: ConfigContext, previousConfig: Record): Record { + const merged = structuredClone(previousConfig); + const keys = context.variableKeys(); + + for (const [variableKey, experimentName] of Object.entries(keys)) { + let target: Record | undefined = merged; + const frags = variableKey.split("."); + + for (let index = 0; index < frags.length; ++index) { + const frag = frags[index]!; + + if (target === undefined) break; + + if (`_${frag}_setter` in target) { + console.error( + `Config key '${frags.slice(0, index + 1).join(".")}' already set by experiment '${target[`_${frag}_setter`]}'.`, + ); + target = undefined; + break; + } + + if (frag in target) { + if (index < frags.length - 1) { + if (!isObject(target[frag])) { + console.warn( + `Config key '${variableKey}' for experiment '${experimentName}' is overriding non-object value at '${frags.slice(0, index + 1).join(".")}' with an object.`, + ); + target[frag] = {}; + target = target[frag] as Record; + } else { + target = target[frag] as Record; + } + } + } + + if (index === frags.length - 1) { + const defaultValue = target[frag]; + + Object.defineProperty(target, `_${frag}_setter`, { value: experimentName, writable: false }); + Object.defineProperty(target, frag, { + get: () => context.variableValue(variableKey, defaultValue), + }); + } + } + } + + return merged; +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/config.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/config.ts src/__tests__/config.test.ts +git commit -m "feat: add mergeConfig utility for experiment variable injection" +``` + +--- + +## Task 16: Public API & Index + +**Files:** +- Create: `src/index.ts` +- Remove: `src/__tests__/placeholder.test.ts` + +- [ ] **Step 1: Write src/index.ts** + +```typescript +export { SDK } from "./sdk"; +export { Context } from "./context"; +export { ContextDataProvider } from "./provider"; +export { ContextPublisher } from "./publisher"; +export { mergeConfig } from "./config"; + +export type { + ApplicationObject, + Attribute, + Assignment, + ClientOptions, + ClientRequestOptions, + ContextData, + ContextOptions, + ContextParams, + CustomFieldValue, + CustomFieldValueType, + EventLogger, + EventLoggerData, + EventName, + Experiment, + ExperimentData, + Exposure, + Goal, + JSONValue, + NormalizedClientOptions, + PublishParams, + SDKOptions, + Unit, + Units, +} from "./types"; +``` + +- [ ] **Step 2: Remove placeholder test** + +```bash +rm -f src/__tests__/placeholder.test.ts +``` + +- [ ] **Step 3: Verify full build** + +```bash +npx tsc --noEmit && npx tsup +``` + +Expected: Build succeeds, `dist/` contains all output files with correct exports + +- [ ] **Step 4: Verify all tests pass** + +```bash +npx vitest run +``` + +Expected: All tests across all modules pass + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "feat: add public API exports and verify complete build" +``` + +--- + +## Task 17: Full Integration & Cleanup + +**Files:** +- Modify: `package.json` (if needed) +- Remove: any leftover old files + +- [ ] **Step 1: Clean up any remaining old files** + +```bash +rm -rf js/ lib/ es/ dist/ types/ +``` + +Verify no old config files remain. + +- [ ] **Step 2: Run full build pipeline** + +```bash +npm run build +``` + +Expected: `dist/` contains: +- `index.js` (ESM) +- `index.cjs` (CommonJS) +- `index.global.js` (browser IIFE) +- `index.d.ts` (TypeScript declarations) +- `index.d.cts` (CTS declarations) + +- [ ] **Step 3: Run full test suite with coverage** + +```bash +npm run test:coverage +``` + +Expected: All tests pass with high coverage + +- [ ] **Step 4: Verify TypeScript strict mode passes** + +```bash +npx tsc --noEmit +``` + +Expected: No type errors + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "chore: final cleanup and verify full build pipeline" +``` diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index b4c18e5..0000000 --- a/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - clearMocks: true, - coverageDirectory: "coverage", - testEnvironment: "node", - testRegex: "/__tests__/.*\\.(test|spec)\\.[t|j]sx?$", - transform: { - "^.+\\.[t|j]sx?$": ["ts-jest"], - }, -}; diff --git a/package-lock.json b/package-lock.json index 163ba5d..e585c7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16592 +1,2900 @@ { - "name": "@absmartly/javascript-sdk", - "version": "1.13.2", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@absmartly/javascript-sdk", - "version": "1.13.2", - "license": "Apache-2.0", - "dependencies": { - "core-js": "^3.20.0", - "node-fetch": "^2.6.7", - "rfdc": "^1.3.0" - }, - "devDependencies": { - "@babel/cli": "^7.17.3", - "@babel/core": "^7.17.4", - "@babel/eslint-parser": "^7.17.0", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-export-default-from": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-throw-expressions": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-runtime": "^7.16.7", - "@babel/preset-env": "^7.16.11", - "@babel/preset-typescript": "^7.18.6", - "@babel/register": "^7.17.0", - "@types/jest": "^29.2.5", - "@types/node-fetch": "^2.6.2", - "@typescript-eslint/eslint-plugin": "^5.48.2", - "@typescript-eslint/parser": "^5.48.2", - "babel-jest": "^29", - "babel-loader": "^8.2.3", - "eslint": "^7.32.0", - "eslint-config-prettier": "^7.1.0", - "jest": "^29.3.1", - "prettier": "^2.4.1", - "terser-webpack-plugin": "^5.3.1", - "ts-jest": "^29.0.5", - "ts-loader": "^9.4.2", - "typescript": "^4.9.4", - "webpack": "^5.60.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.1" - }, - "engines": { - "node": ">=6", - "npm": ">=3" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/cli": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.20.7.tgz", - "integrity": "sha512-WylgcELHB66WwQqItxNILsMlaTd8/SO6SgTTjMp4uCI7P4QyH1r3nqgFmO3BfM4AtfniHgFMH3EpYFj/zynBkQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.8", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - }, - "bin": { - "babel": "bin/babel.js", - "babel-external-helpers": "bin/babel-external-helpers.js" - }, - "engines": { - "node": ">=6.9.0" - }, - "optionalDependencies": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", - "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.7", - "@babel/helpers": "^7.20.7", - "@babel/parser": "^7.20.7", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dev": true, - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": "^7.5.0 || ^8.0.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.4.tgz", - "integrity": "sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.7.tgz", - "integrity": "sha512-LtoWbDXOaidEf50hmdDqn9g8VEzsorMexoWMQdQODbvmqYmaF23pBP5VNPAGIFHsFQCIeKokDiz3CH5Y2jlY6w==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", - "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.2.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", - "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", - "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.10", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", - "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz", - "integrity": "sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz", - "integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", - "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.18.6.tgz", - "integrity": "sha512-WHOrJyhGoGrdtW480L79cF7Iq/gZDZ/z6OqK7mVyFR5I37dTpog/wNgb6hmaM3HYZtULEJl++7VaMWkNZsOcHg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-throw-expressions": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.18.6.tgz", - "integrity": "sha512-rp1CqEZXGv1z1YZ3qYffBH3rhnOxrTwQG8fh2yqulTurwv9zu3Gthfd+niZBLSOi1rY6146TgF+JmVeDXaX4TQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", - "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", - "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", - "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz", - "integrity": "sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.4.tgz", - "integrity": "sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.4", - "@babel/generator": "^7.23.4", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.4", - "@babel/types": "^7.23.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", - "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", - "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/reporters": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.2.0", - "jest-config": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-resolve-dependencies": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "jest-watcher": "^29.3.1", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", - "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", - "dev": true, - "dependencies": { - "expect": "^29.3.1", - "jest-snapshot": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", - "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", - "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", - "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/types": "^29.3.1", - "jest-mock": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", - "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", - "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", - "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", - "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", - "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "dev": true, - "optional": true - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "dependencies": { - "eslint-scope": "5.1.1" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", - "dev": true - }, - "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.20", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", - "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/eslint": { - "version": "8.4.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", - "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.5.tgz", - "integrity": "sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", - "dev": true - }, - "node_modules/@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "dev": true, - "dependencies": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", - "integrity": "sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", - "integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/type-utils": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", - "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", - "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz", - "integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", - "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", - "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz", - "integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", - "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", - "dev": true, - "dependencies": { - "envinfo": "^7.7.3" - }, - "peerDependencies": { - "webpack-cli": "4.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true, - "peerDependencies": { - "webpack-cli": "4.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/babel-jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", - "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.3.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.2.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-loader/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/babel-loader/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", - "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", - "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.2.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001535", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001535.tgz", - "integrity": "sha512-48jLyUkiWFfhm/afF7cQPqPjaUmSraEhK4j+FCTJpgnGGEZHqyLe3hmWH7lIooZdSzXL0ReMvHz0vKDoTBsrwg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "optional": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/core-js": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", - "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.1.tgz", - "integrity": "sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA==", - "dev": true, - "dependencies": { - "browserslist": "^4.21.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", - "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", - "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", - "dev": true, - "dependencies": { - "@jest/core": "^29.3.1", - "@jest/types": "^29.3.1", - "import-local": "^3.0.2", - "jest-cli": "^29.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", - "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-circus": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", - "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "p-limit": "^3.1.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", - "dev": true, - "dependencies": { - "@jest/core": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", - "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.3.1", - "@jest/types": "^29.3.1", - "babel-jest": "^29.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.3.1", - "jest-environment-node": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", - "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", - "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", - "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "jest-util": "^29.3.1", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", - "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", - "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", - "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", - "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", - "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.3.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", - "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", - "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", - "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", - "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.2.0", - "jest-snapshot": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", - "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/environment": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.2.0", - "jest-environment-node": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-leak-detector": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-resolve": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-util": "^29.3.1", - "jest-watcher": "^29.3.1", - "jest-worker": "^29.3.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", - "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/globals": "^29.3.1", - "@jest/source-map": "^29.2.0", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", - "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-haste-map": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "natural-compare": "^1.4.0", - "pretty-format": "^29.3.1", - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", - "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "leven": "^3.1.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", - "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.3.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz", - "integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", - "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", - "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "optional": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", - "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", - "dev": true, - "dependencies": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", - "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-loader/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ts-loader/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", - "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", - "dev": true, - "dependencies": { - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", - "commander": "^7.2.0", - "gzip-size": "^6.0.0", - "lodash": "^4.17.20", - "opener": "^1.5.2", - "sirv": "^1.0.7", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "cross-spawn": "^7.0.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "@webpack-cli/migrate": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/cli": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.20.7.tgz", - "integrity": "sha512-WylgcELHB66WwQqItxNILsMlaTd8/SO6SgTTjMp4uCI7P4QyH1r3nqgFmO3BfM4AtfniHgFMH3EpYFj/zynBkQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.8", - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - } - }, - "@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - } - }, - "@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", - "dev": true - }, - "@babel/core": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", - "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.7", - "@babel/helpers": "^7.20.7", - "@babel/parser": "^7.20.7", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dev": true, - "requires": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.4.tgz", - "integrity": "sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==", - "dev": true, - "requires": { - "@babel/types": "^7.23.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.7.tgz", - "integrity": "sha512-LtoWbDXOaidEf50hmdDqn9g8VEzsorMexoWMQdQODbvmqYmaF23pBP5VNPAGIFHsFQCIeKokDiz3CH5Y2jlY6w==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", - "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.2.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", - "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", - "dev": true, - "requires": { - "@babel/types": "^7.20.7" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", - "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.10", - "@babel/types": "^7.20.7" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, - "requires": { - "@babel/types": "^7.20.2" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "dev": true, - "requires": { - "@babel/types": "^7.20.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" - } - }, - "@babel/helpers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", - "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", - "dev": true, - "requires": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", - "dev": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz", - "integrity": "sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz", - "integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", - "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.18.6.tgz", - "integrity": "sha512-WHOrJyhGoGrdtW480L79cF7Iq/gZDZ/z6OqK7mVyFR5I37dTpog/wNgb6hmaM3HYZtULEJl++7VaMWkNZsOcHg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-throw-expressions": "^7.18.6" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.18.6.tgz", - "integrity": "sha512-rp1CqEZXGv1z1YZ3qYffBH3rhnOxrTwQG8fh2yqulTurwv9zu3Gthfd+niZBLSOi1rY6146TgF+JmVeDXaX4TQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", - "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", - "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", - "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz", - "integrity": "sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - } - }, - "@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - } - }, - "@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/traverse": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.4.tgz", - "integrity": "sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.4", - "@babel/generator": "^7.23.4", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.4", - "@babel/types": "^7.23.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", - "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", - "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/reporters": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.2.0", - "jest-config": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-resolve-dependencies": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "jest-watcher": "^29.3.1", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", - "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1" - } - }, - "@jest/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", - "dev": true, - "requires": { - "expect": "^29.3.1", - "jest-snapshot": "^29.3.1" - } - }, - "@jest/expect-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", - "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0" - } - }, - "@jest/fake-timers": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", - "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "@jest/globals": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", - "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/types": "^29.3.1", - "jest-mock": "^29.3.1" - } - }, - "@jest/reporters": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", - "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", - "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", - "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", - "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", - "dev": true, - "requires": { - "@jest/test-result": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "slash": "^3.0.0" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - } - } - }, - "@jest/transform": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", - "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "dev": true, - "optional": true - }, - "@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "requires": { - "eslint-scope": "5.1.1" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", - "dev": true - }, - "@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/babel__core": { - "version": "7.1.20", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", - "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/eslint": { - "version": "8.4.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", - "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.5.tgz", - "integrity": "sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", - "integrity": "sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", - "integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/type-utils": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/parser": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", - "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", - "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz", - "integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", - "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", - "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz", - "integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", - "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.2", - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - } - } - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true, - "requires": {} - }, - "@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } - }, - "@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true, - "requires": {} - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "babel-jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", - "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", - "dev": true, - "requires": { - "@jest/transform": "^29.3.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.2.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", - "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", - "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.2.0", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001535", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001535.tgz", - "integrity": "sha512-48jLyUkiWFfhm/afF7cQPqPjaUmSraEhK4j+FCTJpgnGGEZHqyLe3hmWH7lIooZdSzXL0ReMvHz0vKDoTBsrwg==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "core-js": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", - "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==" - }, - "core-js-compat": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.1.tgz", - "integrity": "sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA==", - "dev": true, - "requires": { - "browserslist": "^4.21.4" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", - "dev": true - }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", - "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - } - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "requires": { - "duplexer": "^0.1.2" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "dependencies": { - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", - "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", - "dev": true, - "requires": { - "@jest/core": "^29.3.1", - "@jest/types": "^29.3.1", - "import-local": "^3.0.2", - "jest-cli": "^29.3.1" - } - }, - "jest-changed-files": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", - "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } - } - }, - "jest-circus": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", - "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "p-limit": "^3.1.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", - "dev": true, - "requires": { - "@jest/core": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", - "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.3.1", - "@jest/types": "^29.3.1", - "babel-jest": "^29.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.3.1", - "jest-environment-node": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", - "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", - "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", - "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "jest-util": "^29.3.1", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", - "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true - }, - "jest-haste-map": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", - "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", - "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - } - }, - "jest-matcher-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", - "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", - "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.3.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", - "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-util": "^29.3.1" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", - "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", - "dev": true - }, - "jest-resolve": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", - "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", - "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", - "dev": true, - "requires": { - "jest-regex-util": "^29.2.0", - "jest-snapshot": "^29.3.1" - } - }, - "jest-runner": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", - "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/environment": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.2.0", - "jest-environment-node": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-leak-detector": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-resolve": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-util": "^29.3.1", - "jest-watcher": "^29.3.1", - "jest-worker": "^29.3.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", - "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/globals": "^29.3.1", - "@jest/source-map": "^29.2.0", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-snapshot": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", - "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-haste-map": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "natural-compare": "^1.4.0", - "pretty-format": "^29.3.1", - "semver": "^7.3.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", - "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "leven": "^3.1.0", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", - "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", - "dev": true, - "requires": { - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.3.1", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz", - "integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==", - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - } - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", - "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", - "dev": true - }, - "pretty-format": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", - "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "optional": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "requires": { - "resolve": "^1.9.0" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", - "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", - "dev": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "dev": true - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", - "dev": true, - "requires": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - }, - "terser": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", - "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.14", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", - "dev": true - }, - "ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "webpack-bundle-analyzer": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", - "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", - "dev": true, - "requires": { - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", - "commander": "^7.2.0", - "gzip-size": "^6.0.0", - "lodash": "^4.17.20", - "opener": "^1.5.2", - "sirv": "^1.0.7", - "ws": "^7.3.1" - }, - "dependencies": { - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "cross-spawn": "^7.0.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "requires": {} - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } + "name": "@absmartly/javascript-sdk", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@absmartly/javascript-sdk", + "version": "2.0.0", + "license": "Apache-2.0", + "devDependencies": { + "@vitest/coverage-v8": "^3.0.0", + "tsup": "^8.0.0", + "tsx": "^4.0.0", + "typescript": "^5.4.0", + "vitest": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } } diff --git a/package.json b/package.json index 380e9ad..22a304d 100644 --- a/package.json +++ b/package.json @@ -1,93 +1,65 @@ { - "name": "@absmartly/javascript-sdk", - "version": "1.13.4", - "description": "A/B Smartly Javascript SDK", - "homepage": "https://github.com/absmartly/javascript-sdk#README.md", - "bugs": "https://github.com/absmartly/javascript-sdk/issues", - "keywords": [ - "absmartly", - "ab-smartly", - "a/b-smartly", - "ab-testing", - "a/b-testing", - "split-testing", - "ab", - "a/b", - "cro" - ], - "license": "Apache-2.0", - "main": "lib/index.js", - "module": "es/index.js", - "browser": "dist/absmartly.min.js", - "types": "types/index.d.ts", - "engines": { - "npm": ">=3", - "node": ">=6" - }, - "scripts": { - "build-browser": "TARGET=browser webpack --progress --config webpack.config.js && TARGET=browser NODE_ENV=production webpack --progress --config webpack.config.js", - "build-cjs": "TARGET=cjs babel js --delete-dir-on-start --ignore 'browser.js' -d lib", - "build-es": "TARGET=es babel js --delete-dir-on-start --ignore 'browser.js' -d es", - "build": "npm run -s format && npm run -s lint && npm run -s generate-version && npm run -s compile && npm run -s test && npm run -s build-es && npm run -s build-cjs && npm run -s build-browser", - "lint": "eslint -f stylish 'src/**/*.{js,mjs,jsx,ts,mts,tsx}'", - "format": "prettier --write '**/*.{js,mjs,jsx,json,ts,mts,tsx}'", - "test": "jest --coverage", - "prepack": "npm run -s build", - "compile": "tsc", - "generate-version": "node scripts/generate-version.js" - }, - "dependencies": { - "node-fetch": "^2.6.7", - "rfdc": "^1.3.0", - "core-js": "^3.20.0" - }, - "devDependencies": { - "@babel/cli": "^7.17.3", - "@babel/core": "^7.17.4", - "@babel/eslint-parser": "^7.17.0", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-export-default-from": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-throw-expressions": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-runtime": "^7.16.7", - "@babel/preset-env": "^7.16.11", - "@babel/preset-typescript": "^7.18.6", - "@babel/register": "^7.17.0", - "@types/jest": "^29.2.5", - "@types/node-fetch": "^2.6.2", - "@typescript-eslint/eslint-plugin": "^5.48.2", - "@typescript-eslint/parser": "^5.48.2", - "babel-jest": "^29", - "babel-loader": "^8.2.3", - "eslint": "^7.32.0", - "eslint-config-prettier": "^7.1.0", - "jest": "^29.3.1", - "prettier": "^2.4.1", - "terser-webpack-plugin": "^5.3.1", - "ts-jest": "^29.0.5", - "ts-loader": "^9.4.2", - "typescript": "^4.9.4", - "webpack": "^5.60.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.1" - }, - "publishConfig": { - "access": "public" - }, - "files": [ - "README.md", - "CONTRIBUTING.md", - "LICENSE", - "package.json", - "dist/", - "es/", - "lib/", - "types/" - ] + "name": "@absmartly/javascript-sdk", + "version": "2.0.0", + "description": "A/B Smartly Javascript SDK", + "homepage": "https://github.com/absmartly/javascript-sdk#README.md", + "bugs": "https://github.com/absmartly/javascript-sdk/issues", + "keywords": [ + "absmartly", + "ab-smartly", + "a/b-smartly", + "ab-testing", + "a/b-testing", + "split-testing", + "ab", + "a/b", + "cro" + ], + "license": "Apache-2.0", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "browser": "dist/index.global.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "engines": { + "node": ">=14" + }, + "scripts": { + "build": "npm run generate-version && tsup", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit", + "generate-version": "tsx scripts/generate-version.ts", + "prepack": "npm run build" + }, + "devDependencies": { + "tsup": "^8.0.0", + "typescript": "^5.4.0", + "vitest": "^3.0.0", + "tsx": "^4.0.0", + "@vitest/coverage-v8": "^3.0.0" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "README.md", + "LICENSE", + "package.json", + "dist/" + ] } diff --git a/scripts/generate-version.js b/scripts/generate-version.js deleted file mode 100644 index 8824f3c..0000000 --- a/scripts/generate-version.js +++ /dev/null @@ -1,7 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -const pkg = require(path.resolve(__dirname, "../package.json")); -const versionFile = path.resolve(__dirname, "../src/version.ts"); - -fs.writeFileSync(versionFile, `export const SDK_VERSION = "${pkg.version}";\n`); diff --git a/scripts/generate-version.ts b/scripts/generate-version.ts new file mode 100644 index 0000000..ef29a41 --- /dev/null +++ b/scripts/generate-version.ts @@ -0,0 +1,4 @@ +import { readFileSync, writeFileSync } from "node:fs"; + +const pkg = JSON.parse(readFileSync("package.json", "utf-8")); +writeFileSync("src/version.ts", `export const SDK_VERSION = "${pkg.version}";\n`); diff --git a/src/__tests__/abort-controller-shim.test.js b/src/__tests__/abort-controller-shim.test.js deleted file mode 100644 index fdc3ee0..0000000 --- a/src/__tests__/abort-controller-shim.test.js +++ /dev/null @@ -1,91 +0,0 @@ -// eslint-disable-next-line no-shadow -import { AbortController, AbortSignal } from "../abort-controller-shim"; - -describe("AbortSignal", () => { - const expectedEvent = expect.objectContaining({ - type: expect.any(String), - cancelable: false, - bubbles: false, - }); - - describe("dispatchEvent", () => { - it("calls listeners", async () => { - const aborter = new AbortController(); - const signal = aborter.signal; - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); - signal.onabort = listener3; - signal.addEventListener("abort", listener1); - signal.addEventListener("abort", listener2); - signal.dispatchEvent({ type: "abort", cancelable: false, bubbles: false }); - - expect(listener1).toHaveBeenCalledTimes(1); - expect(listener1).toHaveBeenCalledWith(expectedEvent); - - expect(listener2).toHaveBeenCalledTimes(1); - expect(listener2).toHaveBeenCalledWith(expectedEvent); - - expect(listener3).toHaveBeenCalledTimes(1); - expect(listener3).toHaveBeenCalledWith(expectedEvent); - - signal.removeEventListener("abort", listener1); - signal.removeEventListener("abort", listener2); - - listener1.mockClear(); - listener2.mockClear(); - listener3.mockClear(); - - signal.dispatchEvent({ type: "abort", cancelable: false, bubbles: false }); - - expect(listener1).not.toHaveBeenCalled(); - expect(listener2).not.toHaveBeenCalled(); - expect(listener3).toHaveBeenCalledTimes(1); - }); - }); - - it("toString() returns [object AbortSignal]", async () => { - const aborter = new AbortSignal(); - - expect(aborter.toString()).toEqual("[object AbortSignal]"); - }); - - it("toStringTag is set to AbortSignal", async () => { - const aborter = new AbortSignal(); - - expect(aborter[Symbol.toStringTag]).toEqual("AbortSignal"); - }); -}); - -describe("AbortController", () => { - it("creates abort signal", async () => { - const aborter = new AbortController(); - expect(aborter.signal).toBeInstanceOf(AbortSignal); - expect(aborter.signal.aborted).toBe(false); - }); - - it("abort dispatches event on signal and aborted is set", async () => { - const aborter = new AbortController(); - jest.spyOn(aborter.signal, "dispatchEvent").mockImplementation(() => {}); - - aborter.abort(); - - expect(aborter.signal.aborted).toBe(true); - expect(aborter.signal.dispatchEvent).toHaveBeenCalledTimes(1); - expect(aborter.signal.dispatchEvent).toHaveBeenCalledWith( - expect.objectContaining({ type: expect.any(String) }) - ); - }); - - it("toString() returns [object AbortController]", async () => { - const aborter = new AbortController(); - - expect(aborter.toString()).toEqual("[object AbortController]"); - }); - - it("toStringTag is set to AbortController", async () => { - const aborter = new AbortController(); - - expect(aborter[Symbol.toStringTag]).toEqual("AbortController"); - }); -}); diff --git a/src/__tests__/algorithm.test.js b/src/__tests__/algorithm.test.js deleted file mode 100644 index f4ab39d..0000000 --- a/src/__tests__/algorithm.test.js +++ /dev/null @@ -1,43 +0,0 @@ -import { insertUniqueSorted } from "../algorithm"; - -describe("insertUniqueSorted", () => { - it("should insert a number into the center of an array", (done) => { - const arr = [0, 1, 3, 5, 8]; - - insertUniqueSorted(arr, 2, (a, b) => a < b); - - expect(arr).toEqual([0, 1, 2, 3, 5, 8]); - - done(); - }); - - it("should not insert a duplicate value", (done) => { - const arr = [0, 1, 2, 3]; - - insertUniqueSorted(arr, 2, (a, b) => a < b); - - expect(arr).toEqual([0, 1, 2, 3]); - - done(); - }); - - it("should insert the highest value at the end of an array", (done) => { - const arr = [0, 1, 2, 3]; - - insertUniqueSorted(arr, 100, (a, b) => a < b); - - expect(arr).toEqual([0, 1, 2, 3, 100]); - - done(); - }); - - it("should insert the lowest value at the beginning of an array", (done) => { - const arr = [1, 2, 3]; - - insertUniqueSorted(arr, 0, (a, b) => a < b); - - expect(arr).toEqual([0, 1, 2, 3]); - - done(); - }); -}); diff --git a/src/__tests__/algorithm.test.ts b/src/__tests__/algorithm.test.ts new file mode 100644 index 0000000..9fce62c --- /dev/null +++ b/src/__tests__/algorithm.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from "vitest"; +import { insertUniqueSorted } from "../algorithm"; + +describe("insertUniqueSorted", () => { + test("inserts into empty array", () => { + const arr: number[] = []; + insertUniqueSorted(arr, 5, (a, b) => a < b); + expect(arr).toEqual([5]); + }); + + test("inserts in sorted order", () => { + const arr = [1, 3, 5]; + insertUniqueSorted(arr, 2, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 5]); + }); + + test("inserts at beginning", () => { + const arr = [2, 3, 4]; + insertUniqueSorted(arr, 1, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 4]); + }); + + test("inserts at end", () => { + const arr = [1, 2, 3]; + insertUniqueSorted(arr, 4, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 4]); + }); + + test("does not insert duplicate", () => { + const arr = [1, 2, 3]; + insertUniqueSorted(arr, 2, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3]); + }); +}); diff --git a/src/__tests__/assigner.test.js b/src/__tests__/assigner.test.js deleted file mode 100644 index bdd2b63..0000000 --- a/src/__tests__/assigner.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import { VariantAssigner } from "../assigner"; -import { hashUnit } from "../utils"; - -describe("VariantAssigner", () => { - it("assign() should be deterministic", (done) => { - const testCases = { - "bleh@absmartly.com": [ - [[0.5, 0.5], 0x00000000, 0x00000000, 0], - [[0.5, 0.5], 0x00000000, 0x00000001, 1], - [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 0], - [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 0], - [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 0], - [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], - [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 1], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 0], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 2], - [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 0], - [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 0], - [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 0], - [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 1], - [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 1], - ], - 123456789: [ - [[0.5, 0.5], 0x00000000, 0x00000000, 1], - [[0.5, 0.5], 0x00000000, 0x00000001, 0], - [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], - [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 1], - [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 1], - [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], - [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 0], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 1], - [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], - [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 2], - [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 2], - [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 0], - [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 0], - ], - e791e240fcd3df7d238cfc285f475e8152fcc0ec: [ - [[0.5, 0.5], 0x00000000, 0x00000000, 1], - [[0.5, 0.5], 0x00000000, 0x00000001, 0], - [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], - [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 1], - [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 0], - [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], - [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 0], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 0], - [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], - [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 1], - [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 0], - [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 0], - [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 1], - ], - }; - - for (const [unit, tests] of Object.entries(testCases)) { - const assigner = new VariantAssigner(hashUnit(unit)); - for (const testCase of tests) { - const variant = assigner.assign(testCase[0], testCase[1], testCase[2]); - expect(variant).toBe(testCase[3]); - } - } - done(); - }); -}); diff --git a/src/__tests__/assigner.test.ts b/src/__tests__/assigner.test.ts new file mode 100644 index 0000000..c71d135 --- /dev/null +++ b/src/__tests__/assigner.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, test } from "vitest"; +import { VariantAssigner } from "../assigner"; +import { hashUnit } from "../hashing"; + +describe("VariantAssigner", () => { + const testCases: Record = { + "bleh@absmartly.com": [ + [[0.5, 0.5], 0x00000000, 0x00000000, 0], + [[0.5, 0.5], 0x00000000, 0x00000001, 1], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 0], + [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 0], + [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 0], + [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], + [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 1], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 2], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 0], + [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 0], + [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 0], + [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 1], + [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 1], + ], + "123456789": [ + [[0.5, 0.5], 0x00000000, 0x00000000, 1], + [[0.5, 0.5], 0x00000000, 0x00000001, 0], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], + [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 1], + [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 1], + [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], + [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 1], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], + [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 2], + [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 2], + [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 0], + [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 0], + ], + e791e240fcd3df7d238cfc285f475e8152fcc0ec: [ + [[0.5, 0.5], 0x00000000, 0x00000000, 1], + [[0.5, 0.5], 0x00000000, 0x00000001, 0], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], + [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 1], + [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 0], + [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], + [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 0], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], + [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 1], + [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 0], + [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 0], + [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 1], + ], + }; + + for (const [unit, cases] of Object.entries(testCases)) { + describe(`unit: "${unit}"`, () => { + const assigner = new VariantAssigner(hashUnit(unit)); + for (const [split, seedHi, seedLo, expected] of cases) { + test(`assign([${split}], 0x${seedHi.toString(16)}, 0x${seedLo.toString(16)}) == ${expected}`, () => { + expect(assigner.assign(split, seedHi, seedLo)).toBe(expected); + }); + } + }); + } +}); diff --git a/src/__tests__/client.test.js b/src/__tests__/client.test.js deleted file mode 100644 index 7834ff4..0000000 --- a/src/__tests__/client.test.js +++ /dev/null @@ -1,1071 +0,0 @@ -import Client from "../client"; -// eslint-disable-next-line no-shadow -import fetch from "../fetch"; -// eslint-disable-next-line no-shadow -import { AbortController } from "../abort"; -import { AbortError, RetryError, TimeoutError } from "../errors"; //eslint-disable-line no-shadow -import { SDK_VERSION } from "../version"; - -jest.mock("../fetch"); - -describe("Client", () => { - beforeEach(() => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setTimeout"); - }); - - afterEach(() => { - fetch.mockReset(); - }); - - function advanceFakeTimers() { - return new Promise((resolve) => { - let iterations = 0; - - const advance = () => { - jest.advanceTimersByTime(100); - - if (++iterations <= 50) { - Promise.resolve().then(advance); - } else { - resolve(); - } - }; - - Promise.resolve().then(advance); - }); - } - - const endpoint = "test.absmartly.com:8080/v1"; - const apiKey = "5ebf06d8cb5d8137290c4abb64155584fbdb64d8"; - const agent = "javascript-client"; - const environment = "test"; - const application = { - name: "test_app", - version: 1_000_000, - }; - - const units = { - session_id: "dca367dcda209b5197f5f83aee862c7bfb09dc68", - }; - - const clientOptions = { - endpoint, - agent, - environment, - apiKey, - application, - keepalive: true, - timeout: 5000, - retries: 3, - }; - - const defaultMockResponse = { - units, - }; - - const goals = [ - { - name: "goal1", - value: [123], - achievedAt: 123456789, - }, - ]; - - const exposures = [ - { - name: "exp_test", - variant: 1, - exposedAt: 123456789, - assigned: true, - }, - ]; - - const attributes = [ - { - name: "exp_test", - value: "1", - setAt: 123456789, - }, - ]; - - const publishedAt = 1234567890; - - function responseMock(statusCode, statusText, response) { - return { - ok: statusCode >= 200 && statusCode <= 299, - status: statusCode, - statusText, - text: () => Promise.resolve(response), - json: () => Promise.resolve(JSON.parse(JSON.stringify(response))), - }; - } - - function mockFetch(delay, response) { - return (url, opts) => { - if (delay > 0) { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - resolve(response); - }, delay); - - if (opts.signal) { - opts.signal.onabort = () => { - clearTimeout(timeout); - reject(new AbortError()); - }; - } - }); - } - - return new Promise.resolve(response); - }; - } - - it("constructor() should validate options", (done) => { - const deleteOption = (options, key) => { - const result = Object.assign({}, options); - delete result[key]; - - return result; - }; - - const emptyOption = (options, key) => { - const result = Object.assign({}, options); - result[key] = ""; - - return result; - }; - - for (const key of ["apiKey", "application", "endpoint", "environment"]) { - expect(() => new Client(deleteOption(clientOptions, key))).toThrow(); - expect(() => new Client(emptyOption(clientOptions, key))).toThrow(); - } - expect(() => new Client(emptyOption(clientOptions, "agent"))).toThrow(); - - done(); - }); - - it("constructor() should accept string application", (done) => { - const options = Object.assign({}, clientOptions, { application: "website" }); - - expect(() => new Client(options)).not.toThrow(); - - done(); - }); - - it("createContext() calls endpoint", (done) => { - fetch.mockResolvedValue(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .createContext({ - units, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({ - units, - }), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toStrictEqual(defaultMockResponse); - - done(); - }); - }); - - it("getContext() calls endpoint with correct query", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client.getContext().then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context?application=test_app&environment=test`, { - method: "GET", - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() retries on connection error", (done) => { - fetch - .mockRejectedValueOnce(new Error("error 1")) - .mockRejectedValueOnce(new Error("error 2")) - .mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(3); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() stops retrying after options.retries", (done) => { - fetch - .mockRejectedValueOnce(new Error("error 1")) - .mockRejectedValueOnce(new Error("error 2")) - .mockRejectedValueOnce(new Error("error 3")) - .mockRejectedValueOnce(new Error("error 4")) - .mockRejectedValueOnce(new Error("error 5")) - .mockRejectedValueOnce(new Error("error 6")) - .mockRejectedValueOnce(new Error("error 7")); - - jest.spyOn(Math, "random"); - Math.random.mockReturnValue(0.0); - - const options = Object.assign({}, clientOptions, { retries: 5, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .catch((error) => { - expect(fetch).toHaveBeenCalledTimes(6); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(error).toBeInstanceOf(RetryError); - expect(setTimeout).toHaveBeenCalledTimes(6); - expect(setTimeout.mock.calls.map((x) => x[1]).reduce((x, y) => x + y)).toBeLessThanOrEqual(5000 + 1675); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() does not retry with options.retries == 0", (done) => { - fetch.mockRejectedValueOnce(new Error("error 1")); - - jest.spyOn(Math, "random"); - Math.random.mockReturnValue(0.0); - - const options = Object.assign({}, clientOptions, { retries: 0, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .catch((error) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(error.message).toEqual("error 1"); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout.mock.calls.map((x) => x[1]).reduce((x, y) => x + y)).toBe(5000); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() stops retrying after options.timeout", (done) => { - fetch - .mockRejectedValueOnce(new Error("error 1")) - .mockRejectedValueOnce(new Error("error 2")) - .mockRejectedValueOnce(new Error("error 3")) - .mockRejectedValueOnce(new Error("error 4")) - .mockRejectedValueOnce(new Error("error 5")) - .mockRejectedValueOnce(new Error("error 6")) - .mockRejectedValueOnce(new Error("error 7")); - - jest.spyOn(Math, "random"); - Math.random.mockReturnValue(1.0); - - const options = Object.assign({}, clientOptions, { retries: 5, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .catch((error) => { - expect(fetch).toHaveBeenCalledTimes(6); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(error).toBeInstanceOf(RetryError); - expect(setTimeout).toHaveBeenCalledTimes(6); - expect(setTimeout.mock.calls.map((x) => x[1]).reduce((x, y) => x + y)).toBeCloseTo(5000 + 1675, 3); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() does not abort before options.timeout", (done) => { - fetch.mockImplementation(mockFetch(1000, responseMock(200, "OK", defaultMockResponse))); - - const options = Object.assign({}, clientOptions, { timeout: 2000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .then((response) => { - expect(response).toStrictEqual(defaultMockResponse); - - done(); - }) - .catch((error) => { - done(error); - }); - - advanceFakeTimers(); - }); - - it("request() aborts after options.timeout", (done) => { - fetch.mockImplementation(mockFetch(2000, responseMock(200, "OK", defaultMockResponse))); - - const options = Object.assign({}, clientOptions, { timeout: 1000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .then(() => { - done("unexpected"); - }) - .catch((error) => { - expect(error).toBeInstanceOf(TimeoutError); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() aborts when abort() is called", (done) => { - fetch.mockImplementation(mockFetch(3000, responseMock(200, "OK", defaultMockResponse))); - - const aborter = new AbortController(); - const options = Object.assign({}, clientOptions, { timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - signal: aborter.signal, - }) - .then(() => { - done("unexpected"); - }) - .catch((error) => { - expect(error).toBeInstanceOf(AbortError); - - done(); - }); - - setTimeout(() => { - aborter.abort(); - }, 500); - - advanceFakeTimers(); - }); - - it("request() aborts when abort() is called during a retry wait", (done) => { - fetch - .mockRejectedValueOnce(new Error("error 1")) - .mockRejectedValueOnce(new Error("error 2")) - .mockRejectedValueOnce(new Error("error 3")) - .mockRejectedValueOnce(new Error("error 4")) - .mockRejectedValueOnce(new Error("error 5")) - .mockRejectedValueOnce(new Error("error 6")) - .mockRejectedValueOnce(new Error("error 7")); - - jest.spyOn(Math, "random"); - Math.random.mockReturnValue(1.0); - - const aborter = new AbortController(); - const options = Object.assign({}, clientOptions, { retries: 7, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - signal: aborter.signal, - }) - .then(() => { - done("unexpected"); - }) - .catch((error) => { - expect(error).toBeInstanceOf(AbortError); - - done(); - }); - - setTimeout(() => { - aborter.abort(); - }, 500); - - advanceFakeTimers(); - }); - - it("request() cleans up signal event listener on error", (done) => { - fetch.mockRejectedValueOnce(new Error("error 1")); - - const aborter = new AbortController(); - jest.spyOn(aborter.signal, "addEventListener"); - jest.spyOn(aborter.signal, "removeEventListener"); - - const options = Object.assign({}, clientOptions, { retries: 0, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - signal: aborter.signal, - }) - .then(() => { - done("unexpected"); - }) - .catch(() => { - expect(aborter.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(aborter.signal.removeEventListener).toHaveBeenCalledTimes(1); - - done(); - }); - }); - - it("request() cleans up signal event listener on success", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const aborter = new AbortController(); - jest.spyOn(aborter.signal, "addEventListener"); - jest.spyOn(aborter.signal, "removeEventListener"); - - const options = Object.assign({}, clientOptions, { retries: 0, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - signal: aborter.signal, - }) - .then(() => { - expect(aborter.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(aborter.signal.removeEventListener).toHaveBeenCalledTimes(1); - - done(); - }) - .catch((error) => { - done(error); - }); - }); - - it("request() stops retrying on bad request", (done) => { - fetch.mockResolvedValueOnce(responseMock(400, "bad request", "bad request error text")); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "POST", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .catch((error) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(error.message).toEqual("bad request error text"); - - done(); - }); - }); - - it("request() retries on server errors", (done) => { - fetch - .mockResolvedValueOnce(responseMock(500, "server error", "server error text")) - .mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "POST", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(2); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() should encode url query parameters", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1, b: "ã=á" }, - body: {}, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1&b=%C3%A3%3D%C3%A1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() should omit query parameters if dict empty", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: {}, - body: {}, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() should call fetch with an empty body if not specified", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: undefined, - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() should set applications headers for string application", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(Object.assign({}, clientOptions, { application: "website" })); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "website", - "X-Application-Version": 0, - }, - keepalive: true, - body: undefined, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() should not send headers when auth argument is false", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(Object.assign({}, clientOptions, { application: "website" })); - - client - .request({ - auth: false, - method: "PUT", - path: "/context", - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context`, { - method: "PUT", - body: undefined, - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("publish() calls endpoint", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .publish({ - units, - publishedAt, - sdkVersion: SDK_VERSION, - goals, - exposures, - attributes, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - keepalive: true, - body: JSON.stringify({ - units, - publishedAt, - sdkVersion: SDK_VERSION, - goals, - exposures, - attributes, - }), - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("publish() should omit empty arrays", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .publish({ - units, - publishedAt, - sdkVersion: SDK_VERSION, - goals: [], - exposures: [], - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - keepalive: true, - body: JSON.stringify({ - units, - publishedAt, - sdkVersion: SDK_VERSION, - }), - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("publish() should set publishedAt if not present", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - jest.spyOn(Date, "now").mockReturnValue(publishedAt + 100); - - const client = new Client(clientOptions); - - client - .publish({ - units, - sdkVersion: SDK_VERSION, - goals: [], - exposures: [], - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - keepalive: true, - body: JSON.stringify({ - units, - publishedAt: publishedAt + 100, - sdkVersion: SDK_VERSION, - }), - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("constructor() should accept custom agent string", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const customAgent = "custom-agent"; - const client = new Client({ ...clientOptions, agent: customAgent }); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - }) - .then(() => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": customAgent, - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: undefined, - keepalive: true, - signal: expect.any(Object), - }); - - done(); - }); - }); - - it("publish() should include sdkVersion in body", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .publish({ - units, - publishedAt, - sdkVersion: "1.2.3", - goals: [], - exposures: [], - }) - .then(() => { - const body = JSON.parse(fetch.mock.calls[0][1].body); - expect(body.sdkVersion).toEqual("1.2.3"); - - done(); - }); - }); - - it("getAgent() should return default agent when not specified", () => { - const { agent: _, ...optionsWithoutAgent } = clientOptions; - const client = new Client(optionsWithoutAgent); - expect(client.getAgent()).toEqual("javascript-client"); - }); - - it("getAgent() should return custom agent when specified", () => { - const client = new Client({ ...clientOptions, agent: "custom-sdk" }); - expect(client.getAgent()).toEqual("custom-sdk"); - }); - - it("getApplication() should return normalized application object", () => { - const client = new Client(clientOptions); - expect(client.getApplication()).toEqual({ name: "test_app", version: 1000000 }); - }); - - it("getApplication() should normalize string application to object", () => { - const client = new Client({ ...clientOptions, application: "website" }); - expect(client.getApplication()).toEqual({ name: "website", version: 0 }); - }); - - it("getApplication() should accept semver string version", () => { - const client = new Client({ ...clientOptions, application: { name: "website", version: "1.2.3" } }); - expect(client.getApplication()).toEqual({ name: "website", version: "1.2.3" }); - }); - - it("getEnvironment() should return the environment", () => { - const client = new Client(clientOptions); - expect(client.getEnvironment()).toEqual(environment); - }); - - it("publish() should not have the keepalive flag if specified", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client({ ...clientOptions, keepalive: false }); - - client - .publish({ - units, - publishedAt, - goals: [], - exposures: [], - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - keepalive: false, - body: JSON.stringify({ - units, - publishedAt, - }), - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); -}); diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts new file mode 100644 index 0000000..6947093 --- /dev/null +++ b/src/__tests__/client.test.ts @@ -0,0 +1,221 @@ +import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; +import { DefaultClient } from "../client"; +import type { ClientOptions } from "../interfaces"; + +const defaultOpts: ClientOptions = { + agent: "test-agent", + apiKey: "test-api-key", + application: "test-app", + endpoint: "https://test.absmartly.io/v1", + environment: "test", +}; + +describe("Client", () => { + describe("constructor validation", () => { + test("throws for missing apiKey", () => { + const opts = { ...defaultOpts, apiKey: undefined } as unknown as ClientOptions; + expect(() => new DefaultClient(opts)).toThrow("Missing 'apiKey' in options argument"); + }); + + test("uses injected fetch implementation", async () => { + const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000, fetchImpl }); + await client.getContext(); + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); + + test("uses injected AbortController implementation", async () => { + const abort = vi.fn(); + class FakeAbortController { + signal = { addEventListener: vi.fn(), removeEventListener: vi.fn() } as unknown as AbortSignal; + abort = abort; + } + const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); + const client = new DefaultClient({ + ...defaultOpts, + retries: 0, + timeout: 1000, + fetchImpl, + AbortControllerImpl: FakeAbortController as unknown as typeof AbortController, + }); + await client.getContext(); + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); + + test("throws for missing endpoint", () => { + const opts = { ...defaultOpts, endpoint: undefined } as unknown as ClientOptions; + expect(() => new DefaultClient(opts)).toThrow("Missing 'endpoint' in options argument"); + }); + + test("throws for missing environment", () => { + const opts = { ...defaultOpts, environment: undefined } as unknown as ClientOptions; + expect(() => new DefaultClient(opts)).toThrow("Missing 'environment' in options argument"); + }); + + test("throws for missing application", () => { + const opts = { ...defaultOpts, application: undefined } as unknown as ClientOptions; + expect(() => new DefaultClient(opts)).toThrow("Missing 'application' in options argument"); + }); + + test("throws for empty apiKey", () => { + const opts = { ...defaultOpts, apiKey: "" }; + expect(() => new DefaultClient(opts)).toThrow("Invalid 'apiKey' in options argument"); + }); + + test("accepts ApplicationObject", () => { + const opts = { ...defaultOpts, application: { name: "my-app", version: "1.0.0" } }; + const client = new DefaultClient(opts); + expect(client.getApplication()).toEqual({ name: "my-app", version: "1.0.0" }); + }); + + test("converts string application to ApplicationObject", () => { + const client = new DefaultClient(defaultOpts); + expect(client.getApplication()).toEqual({ name: "test-app", version: 0 }); + }); + }); + + describe("accessors", () => { + test("getAgent", () => { + const client = new DefaultClient(defaultOpts); + expect(client.getAgent()).toBe("test-agent"); + }); + + test("getEnvironment", () => { + const client = new DefaultClient(defaultOpts); + expect(client.getEnvironment()).toBe("test"); + }); + }); + + describe("getContext", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("makes GET request to /context", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({ experiments: [] }) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.getContext(); + + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + const [url] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(url).toContain("/context"); + expect(url).toContain("application=test-app"); + expect(url).toContain("environment=test"); + }); + + test("GET /context does NOT include auth headers", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({ experiments: [] }) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.getContext(); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(opts.headers).toBeUndefined(); + }); + }); + + describe("publish", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("makes PUT request to /context with auth headers", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({}) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.publish({ + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + }); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(opts.method).toBe("PUT"); + expect(opts.headers["X-API-Key"]).toBe("test-api-key"); + expect(opts.headers["X-Agent"]).toBe("test-agent"); + expect(opts.headers["X-Environment"]).toBe("test"); + }); + + test("omits empty goals and exposures arrays", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({}) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.publish({ + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + goals: [], + exposures: [], + }); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + const body = JSON.parse(opts.body); + expect(body.goals).toBeUndefined(); + expect(body.exposures).toBeUndefined(); + }); + }); + + describe("retry logic", () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + test("retries on server error", async () => { + const failResponse = { ok: false, status: 500, statusText: "Server Error", text: () => Promise.resolve("") }; + const successResponse = { ok: true, json: () => Promise.resolve({ data: "ok" }) }; + + (globalThis.fetch as ReturnType) + .mockResolvedValueOnce(failResponse) + .mockResolvedValueOnce(successResponse); + + const client = new DefaultClient({ ...defaultOpts, retries: 3, timeout: 10000 }); + const promise = client.getContext(); + + await vi.runAllTimersAsync(); + const result = await promise; + + expect(result).toEqual({ data: "ok" }); + expect(globalThis.fetch).toHaveBeenCalledTimes(2); + }); + + test("does not retry on 4xx error", async () => { + const failResponse = { + ok: false, + status: 400, + statusText: "Bad Request", + text: () => Promise.resolve("bad request"), + }; + + (globalThis.fetch as ReturnType).mockResolvedValue(failResponse); + + const client = new DefaultClient({ ...defaultOpts, retries: 3, timeout: 10000 }); + const promise = client.getContext(); + + await expect(promise).rejects.toThrow("bad request"); + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + + await vi.runAllTimersAsync(); + }); + }); +}); diff --git a/src/__tests__/config.test.js b/src/__tests__/config.test.js deleted file mode 100644 index 5be597c..0000000 --- a/src/__tests__/config.test.js +++ /dev/null @@ -1,236 +0,0 @@ -import { mergeConfig } from "../config"; -import Context from "../context"; - -jest.mock("../context"); - -describe("Config", () => { - describe("mergeConfig()", () => { - it("should create getters that call context.variable()", (done) => { - const context = new Context(); - - const variableKeys = { - button: "exp_test_abc", - "banner.border": "exp_test_ab", - "banner.size": "exp_test_ab", - "home.arrow.direction": "exp_test_arrow", - }; - - const expectedValues = { - button: true, - "banner.border": 10, - "banner.size": 812, - "home.arrow.direction": "up", - }; - - context.variableKeys.mockReturnValue(variableKeys); - context.variableValue.mockImplementation((key) => expectedValues[key]); - - const previousConfig = { - button: false, - banner: { - size: 420, - border: 0, - }, - home: { - arrow: { - direction: "down", - }, - }, - other: "unused", - }; - - const expectedConfig = { - button: true, - banner: { - size: 812, - border: 10, - }, - home: { - arrow: { - direction: "up", - }, - }, - other: "unused", - }; - - const actual = mergeConfig(context, previousConfig); - expect(actual).not.toBe(previousConfig); // should be a clone and new properties are not values, but have accessors - expect(actual).toMatchObject(expectedConfig); - expect(context.variableValue).toHaveBeenCalledTimes(4); // called during equality check above - context.variableValue.mockClear(); - - expect(actual.button).toEqual(expectedConfig.button); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("button", previousConfig.button); - context.variableValue.mockClear(); - - expect(actual.banner.border).toEqual(expectedConfig.banner.border); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("banner.border", previousConfig.banner.border); - context.variableValue.mockClear(); - - expect(actual.banner.size).toEqual(expectedConfig.banner.size); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("banner.size", previousConfig.banner.size); - context.variableValue.mockClear(); - - expect(actual.home.arrow.direction).toEqual(expectedConfig.home.arrow.direction); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith( - "home.arrow.direction", - previousConfig.home.arrow.direction - ); - context.variableValue.mockClear(); - - expect(actual.other).toEqual(expectedConfig.other); - expect(context.variableValue).not.toHaveBeenCalled(); - - done(); - }); - - it("should warn about mismatching object merges", (done) => { - jest.spyOn(console, "warn").mockImplementation(() => {}); - - const context = new Context(); - - const variableKeys = { - "button.active": "exp_test_abc", - "banner.border": "exp_test_ab", - "banner.size": "exp_test_ab", - "home.arrow.direction": "exp_test_arrow", - }; - - const expectedValues = { - "button.active": true, - "banner.border": 10, - "banner.size": 812, - "home.arrow.direction": "up", - }; - - context.variableKeys.mockReturnValue(variableKeys); - context.variableValue.mockImplementation((key) => expectedValues[key]); - - const previousConfig = { - button: true, - banner: { - size: 420, - border: 0, - }, - home: { - arrow: "down", - }, - other: "unused", - }; - - const expectedConfig = { - button: { - active: true, - }, - banner: { - size: 812, - border: 10, - }, - home: { - arrow: { - direction: "up", - }, - }, - other: "unused", - }; - - const actual = mergeConfig(context, previousConfig); - expect(actual).not.toBe(previousConfig); // should be a clone and new properties are not values, but have accessors - expect(actual).toMatchObject(expectedConfig); - expect(context.variableValue).toHaveBeenCalledTimes(4); // called during equality check above - context.variableValue.mockClear(); - - expect(console.warn).toHaveBeenCalledTimes(2); - expect(console.warn).toHaveBeenCalledWith( - "Config key 'button.active' for experiment 'exp_test_abc' is overriding non-object value at 'button' with an object." - ); - expect(console.warn).toHaveBeenCalledWith( - "Config key 'home.arrow.direction' for experiment 'exp_test_arrow' is overriding non-object value at 'home.arrow' with an object." - ); - - expect(actual.button.active).toEqual(expectedConfig.button.active); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("button.active", undefined); - context.variableValue.mockClear(); - - expect(actual.home.arrow.direction).toEqual(expectedConfig.home.arrow.direction); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("home.arrow.direction", undefined); - context.variableValue.mockClear(); - - done(); - }); - - it("should error with multiple experiments overriding key", (done) => { - jest.spyOn(console, "error").mockImplementation(() => {}); - - jest.spyOn(console, "warn").mockImplementation(() => {}); - - const context = new Context(); - - const variableKeys = { - "button.active": true, - "banner.border": "exp_test_ab", - "banner.size": "exp_test_abc", - "home.arrow": "exp_test_arrow", - "home.arrow.direction": "exp_test_arrow_direction", - }; - - const expectedValues = { - "button.active": true, - "banner.border": 10, - "banner.size": 812, - "home.arrow": "up", - "home.arrow.direction": "up", - }; - - context.variableKeys.mockReturnValue(variableKeys); - context.variableValue.mockImplementation((key) => expectedValues[key]); - - const previousConfig = { - button: { - active: false, - }, - banner: { - size: 420, - border: 0, - }, - home: { - arrow: "down", - }, - other: "unused", - }; - - const expectedConfig = { - button: { - active: true, - }, - banner: { - size: 812, - border: 10, - }, - home: { - arrow: "up", - }, - other: "unused", - }; - - const actual = mergeConfig(context, previousConfig); - expect(actual).not.toBe(previousConfig); // should be a clone and new properties are not values, but have accessors - expect(actual).toMatchObject(expectedConfig); - expect(context.variableValue).toHaveBeenCalledTimes(4); // called during equality check above - context.variableValue.mockClear(); - - expect(console.error).toHaveBeenCalledTimes(1); - expect(console.error).toHaveBeenCalledWith( - "Config key 'home.arrow' already set by experiment 'exp_test_arrow'." - ); - - done(); - }); - }); -}); diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts new file mode 100644 index 0000000..0f9ae9b --- /dev/null +++ b/src/__tests__/config.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test, vi } from "vitest"; +import { mergeConfig } from "../config"; + +function mockContext( + variableKeys: Record, + variableValues: Record = {}, +) { + return { + variableKeys: () => variableKeys, + variableValue: (key: string, defaultValue: unknown) => + key in variableValues ? variableValues[key] : defaultValue, + }; +} + +describe("mergeConfig", () => { + test("returns new object, does not mutate original", () => { + const original = { key: "value" }; + const context = mockContext({}); + const result = mergeConfig(context as never, original); + expect(result).not.toBe(original); + expect(result).toEqual({ key: "value" }); + }); + + test("creates getter for experiment variable", () => { + const context = mockContext({ "button.color": ["exp_test"] }, { "button.color": "red" }); + const config = { button: { color: "blue" } }; + const result = mergeConfig(context as never, config); + expect(result.button).toBeDefined(); + expect((result.button as Record).color).toBe("red"); + }); + + test("falls back to default when variable not set", () => { + const context = mockContext({ "button.color": ["exp_test"] }); + const config = { button: { color: "blue" } }; + const result = mergeConfig(context as never, config); + expect((result.button as Record).color).toBe("blue"); + }); + + test("warns when overriding non-object value with object", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const context = mockContext({ "button.active.color": ["exp_test"] }); + const config = { button: { active: "yes" } }; + mergeConfig(context as never, config); + expect(warnSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + test("errors when key already set by another experiment", () => { + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const context = mockContext({ + "button.color": ["exp_test_1"], + "button.color.shade": ["exp_test_2"], + }); + const config = { button: { color: "blue" } }; + mergeConfig(context as never, config); + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); +}); diff --git a/src/__tests__/context.test.js b/src/__tests__/context.test.js deleted file mode 100644 index bac768c..0000000 --- a/src/__tests__/context.test.js +++ /dev/null @@ -1,4178 +0,0 @@ -import Client from "../client"; -import SDK from "../sdk"; -import Context from "../context"; -import { hashUnit } from "../utils"; -import clone from "rfdc/default"; -import { ContextPublisher } from "../publisher"; -import { ContextDataProvider } from "../provider"; -import { SDK_VERSION } from "../version"; - -jest.mock("../client"); -jest.mock("../sdk"); -jest.mock("../provider"); -jest.mock("../publisher"); - -describe("Context", () => { - const contextParams = { - units: { - session_id: "e791e240fcd3df7d238cfc285f475e8152fcc0ec", - user_id: 12317303, - }, - }; - - const publishUnits = Object.entries(contextParams.units).map((x) => ({ type: x[0], uid: hashUnit(x[1]) })); - - const units = { - session_id: "e791e240fcd3df7d238cfc285f475e8152fcc0ec", - user_id: "123456789", - email: "bleh@absmartly.com", - }; - - const getContextResponse = { - experiments: [ - { - id: 1, - name: "exp_test_ab", - iteration: 1, - unitType: "session_id", - seedHi: 3603515, - seedLo: 233373850, - split: [0.5, 0.5], - trafficSeedHi: 449867249, - trafficSeedLo: 455443629, - trafficSplit: [0.0, 1.0], - fullOnVariant: 0, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"banner.border":1,"banner.size":"large"}', - }, - ], - audience: null, - customFieldValues: null, - }, - { - id: 2, - name: "exp_test_abc", - iteration: 1, - unitType: "session_id", - seedHi: 55006150, - seedLo: 47189152, - split: [0.34, 0.33, 0.33], - trafficSeedHi: 705671872, - trafficSeedLo: 212903484, - trafficSplit: [0.0, 1.0], - fullOnVariant: 0, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"button.color":"blue"}', - }, - { - name: "C", - config: '{"button.color":"red"}', - }, - ], - audience: "", - customFieldValues: [ - { - name: "country", - value: "US,PT,ES,DE,FR", - type: "string", - }, - { - name: "json_object", - value: '{"123":1,"456":0}', - type: "json", - }, - { - name: "json_array", - value: '["hello", "world"]', - type: "json", - }, - { - name: "json_number", - value: "123", - type: "json", - }, - { - name: "json_string", - value: '"hello"', - type: "json", - }, - { - name: "json_boolean", - value: "true", - type: "json", - }, - { - name: "json_null", - value: "null", - type: "json", - }, - { - name: "json_invalid", - value: "invalid", - type: "json", - }, - ], - }, - { - id: 3, - name: "exp_test_not_eligible", - iteration: 1, - unitType: "user_id", - seedHi: 503266407, - seedLo: 144942754, - split: [0.34, 0.33, 0.33], - trafficSeedHi: 87768905, - trafficSeedLo: 511357582, - trafficSplit: [0.99, 0.01], - fullOnVariant: 0, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"card.width":"80%"}', - }, - { - name: "C", - config: '{"card.width":"75%"}', - }, - ], - audience: "{}", - customFieldValues: null, - }, - { - id: 4, - name: "exp_test_fullon", - iteration: 1, - unitType: "session_id", - seedHi: 856061641, - seedLo: 990838475, - split: [0.25, 0.25, 0.25, 0.25], - trafficSeedHi: 360868579, - trafficSeedLo: 330937933, - trafficSplit: [0.0, 1.0], - fullOnVariant: 2, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"submit.color":"red","submit.shape":"circle"}', - }, - { - name: "C", - config: '{"submit.color":"blue","submit.shape":"rect"}', - }, - { - name: "D", - config: '{"submit.color":"green","submit.shape":"square"}', - }, - ], - audience: "null", - customFieldValues: null, - }, - { - id: 5, - name: "exp_test_custom_fields", - iteration: 1, - unitType: "session_id", - seedHi: 9372617, - seedLo: 121364805, - split: [0.5, 0.5], - trafficSeedHi: 318746944, - trafficSeedLo: 359812364, - trafficSplit: [0.0, 1.0], - fullOnVariant: 0, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"submit.size":"sm"}', - }, - ], - audience: null, - customFieldValues: [ - { - name: "country", - value: "US,PT,ES", - type: "string", - }, - { - name: "languages", - value: "en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX", - type: "string", - }, - { - name: "text_field", - value: "hello text", - type: "text", - }, - { - name: "string_field", - value: "hello string", - type: "string", - }, - { - name: "number_field", - value: "123", - type: "number", - }, - { - name: "boolean_field", - value: "true", - type: "boolean", - }, - { - name: "false_boolean_field", - value: "false", - type: "boolean", - }, - { - name: "invalid_type_field", - value: "invalid", - type: "invalid", - }, - ], - }, - ], - }; - - const refreshContextResponse = Object.assign({}, getContextResponse, { - experiments: [ - { - id: 6, - name: "exp_test_new", - iteration: 2, - unitType: "session_id", - seedHi: 934590467, - seedLo: 714771373, - split: [0.5, 0.5], - trafficSeedHi: 940553836, - trafficSeedLo: 270705624, - trafficSplit: [0.0, 1.0], - fullOnVariant: 1, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"show-modal":true}', - }, - ], - }, - ].concat(getContextResponse.experiments), - }); - - const audienceContextResponse = { - ...getContextResponse, - experiments: getContextResponse.experiments.map((x) => { - if (x.name === "exp_test_ab") { - return { - ...x, - audience: JSON.stringify({ - filter: [{ gte: [{ var: "age" }, { value: 20 }] }], - }), - }; - } - return x; - }), - }; - - const audienceStrictContextResponse = { - ...audienceContextResponse, - experiments: audienceContextResponse.experiments.map((x) => { - if (x.name === "exp_test_ab") { - return { - ...x, - audienceStrict: true, - variants: x.variants.map((v) => { - if (v.name === "A") { - return { name: "A", config: '{"banner.size":"tiny"}' }; - } - return v; - }), - }; - } - return x; - }), - }; - - const expectedVariants = { - exp_test_ab: 1, - exp_test_abc: 2, - exp_test_not_eligible: 0, - exp_test_fullon: 2, - exp_test_new: 1, - exp_test_custom_fields: 1, - }; - - const lowestIdConflictingKeyContextResponse = { - ...getContextResponse, - experiments: getContextResponse.experiments.map((e) => { - if (e.name === "exp_test_ab") { - return { - ...e, - id: 99, - variants: e.variants.map((v, i) => { - if (i === expectedVariants[e.name]) { - return { - ...v, - config: JSON.stringify({ - icon: "arrow", - }), - }; - } - return v; - }), - }; - } - if (e.name === "exp_test_abc") { - return { - ...e, - id: 1, - variants: e.variants.map((v, i) => { - if (i === expectedVariants[e.name]) { - return { - ...v, - config: JSON.stringify({ icon: "circle" }), - }; - } - return v; - }), - }; - } - return e; - }), - }; - - const disjointedContextResponse = { - ...getContextResponse, - experiments: getContextResponse.experiments.map((exp) => { - if (exp.name === "exp_test_ab") { - return { - ...exp, - audienceStrict: true, - audience: JSON.stringify({ - filter: [{ gte: [{ var: "age" }, { value: 20 }] }], - }), - variants: exp.variants.map((v, i) => { - if (i === expectedVariants[exp.name]) { - return { - ...v, - config: JSON.stringify({ - icon: "arrow", - }), - }; - } - return v; - }), - }; - } - if (exp.name === "exp_test_abc") { - return { - ...exp, - audienceStrict: true, - audience: JSON.stringify({ - filter: [{ lt: [{ var: "age" }, { value: 20 }] }], - }), - variants: exp.variants.map((variant, i) => { - if (i === expectedVariants[exp.name]) { - return { - ...variant, - config: JSON.stringify({ - icon: "circle", - }), - }; - } - return variant; - }), - }; - } - return exp; - }), - }; - - const expectedVariables = { - "banner.border": 1, - "banner.size": "large", - "button.color": "red", - "submit.color": "blue", - "submit.shape": "rect", - "show-modal": true, - "submit.size": "sm", - }; - - const variableExperiments = { - "banner.border": ["exp_test_ab"], - "banner.size": ["exp_test_ab"], - "button.color": ["exp_test_abc"], - "card.width": ["exp_test_not_eligible"], - "submit.color": ["exp_test_fullon"], - "submit.shape": ["exp_test_fullon"], - "submit.size": ["exp_test_custom_fields"], - "show-modal": ["exp_test_new"], - }; - - const sdk = new SDK(); - const client = new Client(); - const publisher = new ContextPublisher(); - const provider = new ContextDataProvider(); - - sdk.getContextDataProvider.mockReturnValue(provider); - sdk.getContextPublisher.mockReturnValue(publisher); - sdk.getClient.mockReturnValue(client); - sdk.getEventLogger.mockReturnValue(SDK.defaultEventLogger); - - client.getAgent.mockReturnValue("absmartly-javascript-sdk"); - client.getApplication.mockReturnValue({ name: "website", version: 0 }); - client.getEnvironment.mockReturnValue("production"); - - const contextOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - const timeOrigin = 1611141535729; - - beforeEach(() => { - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin); - }); - - describe("Context", () => { - it("should be ready with data", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - expect(context.eventLogger()).toBe(SDK.defaultEventLogger); - expect(context.provider()).toBe(provider); - expect(context.publisher()).toBe(publisher); - - done(); - }); - }); - - it("should use custom publisher, dataProvider and eventLogger", (done) => { - const customPublisher = new ContextPublisher(); - const customDataProvider = new ContextDataProvider(); - const customEventLogger = jest.fn(); - - const context = new Context( - sdk, - { - ...contextOptions, - publisher: customPublisher, - dataProvider: customDataProvider, - eventLogger: customEventLogger, - }, - contextParams, - getContextResponse - ); - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - expect(context.eventLogger()).toBe(customEventLogger); - expect(context.provider()).toBe(customDataProvider); - expect(context.publisher()).toBe(customPublisher); - - done(); - }); - }); - - it("should become ready and call handler", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - expect(context.eventLogger()).toBe(SDK.defaultEventLogger); - expect(context.provider()).toBe(provider); - expect(context.publisher()).toBe(publisher); - - done(); - }); - }); - - it("should become ready and failed, and call handler on failure", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(true); - expect(context.data()).toStrictEqual({}); - expect(context.eventLogger()).toBe(SDK.defaultEventLogger); - expect(context.provider()).toBe(provider); - expect(context.publisher()).toBe(publisher); - - done(); - }); - }); - - it("should call event logger on error", (done) => { - SDK.defaultEventLogger.mockClear(); - - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - context.ready().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "error", "bad request error text"); - - done(); - }); - }); - - it("should call event logger on success", (done) => { - SDK.defaultEventLogger.mockClear(); - - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - context.ready().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "ready", getContextResponse); - - done(); - }); - }); - - it("should call event logger on pre-fetched experiment data", (done) => { - SDK.defaultEventLogger.mockClear(); - - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - context.ready().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "ready", getContextResponse); - - done(); - }); - }); - - it("should throw when not ready", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - expect(() => context.data()).toThrow(); - expect(() => context.treatment("test")).toThrow(); - expect(() => context.peek("test")).toThrow(); - expect(() => context.experiments()).toThrow(); - expect(() => context.variableKeys()).toThrow(); - expect(() => context.variableValue("a", 17)).toThrow(); - expect(() => context.peekVariableValue("a", 17)).toThrow(); - - done(); - }); - - it("should load experiment data", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - expect(context.experiments()).toEqual(getContextResponse.experiments.map((x) => x.name)); - for (const experiment of getContextResponse.experiments) { - expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name]); - expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); - } - expect(context.data()).toEqual(getContextResponse); - - done(); - }); - - it("should start refresh timer after ready", (done) => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setInterval"); - - const refreshPeriod = 1000; - const context = new Context( - sdk, - Object.assign(contextOptions, { refreshPeriod }), - contextParams, - Promise.resolve(getContextResponse) - ); - - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - - expect(setInterval).not.toHaveBeenCalled(); - - context.ready().then(() => { - expect(setInterval).toHaveBeenCalledTimes(1); - expect(setInterval).toHaveBeenCalledWith(expect.anything(), refreshPeriod); - setInterval.mockClear(); - - jest.advanceTimersByTime(refreshPeriod - 1); - - expect(provider.getContextData).not.toHaveBeenCalled(); - - const getContextPromise = Promise.resolve(refreshContextResponse); - provider.getContextData.mockReturnValue(getContextPromise); - - jest.advanceTimersByTime(refreshPeriod); - - getContextPromise.then(() => { - expect(setInterval).not.toHaveBeenCalled(); - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); - provider.getContextData.mockClear(); - - // test another interval - const nextGetContextPromise = Promise.resolve(refreshContextResponse); - provider.getContextData.mockReturnValue(nextGetContextPromise); - - jest.advanceTimersByTime(refreshPeriod); - nextGetContextPromise.then(() => { - expect(setInterval).not.toHaveBeenCalled(); - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - done(); - }); - }); - }); - }); - }); - - describe("unit()", () => { - it("should set a unit", (done) => { - const context = new Context(sdk, contextOptions, { units: {} }, getContextResponse); - - context.units(units); - - for (const [key, value] of Object.entries(units)) { - expect(context.getUnit(key)).toEqual(value); - } - - expect(context.getUnits()).toEqual(units); - - done(); - }); - it("should throw on duplicate unit type set", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.isReady()).toEqual(true); - - expect(() => context.unit("session_id", "new_id")).toThrow(); - - // should not throw if set to the same value - expect(() => context.unit("session_id", "e791e240fcd3df7d238cfc285f475e8152fcc0ec")).not.toThrow(); - - done(); - }); - - it("should throw on invalid uid", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.isReady()).toEqual(true); - - expect(() => context.unit("session_id", "")).toThrow(); - expect(() => context.unit("session_id", null)).toThrow(); - expect(() => context.unit("session_id", undefined)).toThrow(); - expect(() => context.unit("session_id", true)).toThrow(); - expect(() => context.unit("session_id", {})).toThrow(); - expect(() => context.unit("session_id", [])).toThrow(); - - done(); - }); - - it("should be callable before ready()", (done) => { - const context = new Context(sdk, contextOptions, {}, Promise.resolve(getContextResponse)); - - context.units(contextParams.units); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.unit("test", "test")).toThrow(); // finalizing - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.unit("test", "test")).toThrow(); // finalizing - }); - }); - - describe("getAttribute()", () => { - it("should get the last set attribute", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.attribute("attr1", "value1"); - context.attribute("attr1", "value2"); - - expect(context.getAttribute("attr1")).toEqual("value2"); - - done(); - }); - }); - - describe("attribute()", () => { - it("should set an attribute", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.attribute("attr1", "value1"); - context.attributes({ - attr2: "value2", - attr3: 15, - }); - - expect(context.getAttribute("attr1")).toEqual("value1"); - expect(context.getAttributes()).toEqual({ - attr1: "value1", - attr2: "value2", - attr3: 15, - }); - - done(); - }); - - it("should be callable before ready()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - context.attribute("attr1", "value1"); - context.attributes({ - attr2: "value2", - attr3: 3, - }); - - expect(context.getAttribute("attr1")).toEqual("value1"); - expect(context.getAttributes()).toEqual({ - attr1: "value1", - attr2: "value2", - attr3: 3, - }); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - attributes: [ - { - name: "attr1", - setAt: 1611141535729, - value: "value1", - }, - { - name: "attr2", - setAt: 1611141535729, - value: "value2", - }, - { - name: "attr3", - setAt: 1611141535729, - value: 3, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - }); - - describe("refresh()", () => { - it("should call client and load new data", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.refresh().then(() => { - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(context.experiments()).toEqual(refreshContextResponse.experiments.map((x) => x.name)); - for (const experiment of refreshContextResponse.experiments) { - expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); - } - expect(context.data()).toEqual(refreshContextResponse); - - done(); - }); - }); - - it("should pass through request options", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.refresh({ timeout: 1234 }).then(() => { - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, { timeout: 1234 }); - - expect(context.experiments()).toEqual(refreshContextResponse.experiments.map((x) => x.name)); - for (const experiment of refreshContextResponse.experiments) { - expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); - } - expect(context.data()).toEqual(refreshContextResponse); - - done(); - }); - }); - - it("should reject promise on error", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValueOnce(Promise.reject(new Error("test error"))); - - context.refresh().catch((error) => { - expect(error.message).toEqual("test error"); - done(); - }); - }); - - it("should not re-queue exposures after refresh when not changed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.refresh().then(() => { - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - for (const experiment of refreshContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(refreshContextResponse.experiments.length); - - done(); - }); - }); - - it("should not re-queue when not changed on audience mismatch", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.treatment("exp_test_ab")).toEqual(0); - - expect(context.pending()).toEqual(1); - - provider.getContextData.mockReturnValue(Promise.resolve(audienceStrictContextResponse)); - - context.refresh().then(() => { - expect(context.treatment("exp_test_ab")).toEqual(0); - - expect(context.pending()).toEqual(1); - - done(); - }); - }); - - it("should not re-queue when not changed with override", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - context.override("exp_test_ab", 3); - expect(context.treatment("exp_test_ab")).toEqual(3); - - expect(context.pending()).toEqual(1); - - provider.getContextData.mockReturnValue(Promise.resolve(audienceStrictContextResponse)); - - context.refresh().then(() => { - expect(context.treatment("exp_test_ab")).toEqual(3); - - expect(context.pending()).toEqual(1); - - done(); - }); - }); - - it("should not call client publish when failed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - - context.ready().then(() => { - context.refresh().then(() => { - expect(provider.getContextData).not.toHaveBeenCalled(); - - done(); - }); - }); - }); - - it("should call event logger when failed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - context.ready().then(() => { - provider.getContextData.mockReturnValueOnce(Promise.reject(new Error("test error"))); - - SDK.defaultEventLogger.mockClear(); - context.refresh().catch((error) => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "error", error); - - done(); - }); - }); - }); - - it("should call event logger on success", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - - provider.getContextData.mockReturnValueOnce(Promise.resolve(refreshContextResponse)); - - context.ready().then(() => { - SDK.defaultEventLogger.mockClear(); - context.refresh().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "refresh", refreshContextResponse); - - done(); - }); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.refresh()).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.refresh()).toThrow(); // finalizing - }); - - it("should keep overrides", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.override("not_found", 3); - expect(context.peek("not_found")).toEqual(3); - - context.refresh().then(() => { - expect(context.peek("not_found")).toEqual(3); - - done(); - }); - }); - - it("should keep custom assignments", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.customAssignment("exp_test_ab", 3); - - expect(context.peek("exp_test_ab")).toEqual(3); - - context.refresh().then(() => { - expect(context.peek("exp_test_ab")).toEqual(3); - - done(); - }); - }); - - it("should pick up changes in experiment stopped", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_abc"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const stoppedRefreshContextResponse = clone(getContextResponse); - stoppedRefreshContextResponse.experiments = stoppedRefreshContextResponse.experiments.filter( - (x) => x.name !== experimentName - ); - - provider.getContextData.mockReturnValue(Promise.resolve(stoppedRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(0); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after stopped - - done(); - }); - }); - - it("should pick up changes in experiment started", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_new"; - - expect(context.treatment(experimentName)).toEqual(0); - expect(context.pending()).toEqual(1); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(1); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after stopped - - done(); - }); - }); - - it("should pick up changes in experiment fullon", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_abc"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const fullOnRefreshContextResponse = clone(getContextResponse); - for (const experiment of fullOnRefreshContextResponse.experiments) { - if (experiment.name === experimentName) { - expect(experiment.fullOnVariant).toEqual(0); - experiment.fullOnVariant = 1; - expect(expectedVariants[experimentName]).not.toEqual(experiment.fullOnVariant); - } - } - - provider.getContextData.mockReturnValue(Promise.resolve(fullOnRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(1); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after fullon - - done(); - }); - }); - - it("should pick up changes in experiment traffic split", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_not_eligible"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const scaledUpRefreshContextResponse = clone(getContextResponse); - for (const experiment of scaledUpRefreshContextResponse.experiments) { - if (experiment.name === experimentName) { - experiment.trafficSplit = [0.0, 1.0]; - } - } - - provider.getContextData.mockReturnValue(Promise.resolve(scaledUpRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(2); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after fullon - - done(); - }); - }); - - it("should pick up changes in experiment iteration", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_abc"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const iteratedRefreshContextResponse = clone(getContextResponse); - for (const experiment of iteratedRefreshContextResponse.experiments) { - if (experiment.name === experimentName) { - experiment.iteration = 2; - experiment.trafficSeedHi = 54870830; - experiment.trafficSeedHi = 398724581; - experiment.seedHi = 77498863; - experiment.seedHi = 34737352; - } - } - - provider.getContextData.mockReturnValue(Promise.resolve(iteratedRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(1); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after fullon - - done(); - }); - }); - - it("should pick up changes in experiment id", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_abc"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const iteratedRefreshContextResponse = clone(getContextResponse); - for (const experiment of iteratedRefreshContextResponse.experiments) { - if (experiment.name === experimentName) { - experiment.id = 11; - experiment.trafficSeedHi = 54870830; - experiment.trafficSeedHi = 398724581; - experiment.seedHi = 77498863; - experiment.seedHi = 34737352; - } - } - - provider.getContextData.mockReturnValue(Promise.resolve(iteratedRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(1); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after fullon - - done(); - }); - }); - }); - - describe("peek()", () => { - it("should not queue exposures", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name]); - } - - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should return override variant", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.override(experiment.name, expectedVariants[experiment.name] + 11); - } - context.override("not_found", 3); - - for (const experiment of getContextResponse.experiments) { - expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name] + 11); - } - - expect(context.peek("not_found")).toEqual(3); - - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should return assigned variant on audience mismatch in non-strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - expect(context.peek("exp_test_ab")).toEqual(1); - - done(); - }); - - it("should return control variant on audience mismatch in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.peek("exp_test_ab")).toEqual(0); - - done(); - }); - - it("should re-evaluate audience expression when attributes change in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // First peek call without matching attribute should return control (0) - expect(context.peek("exp_test_ab")).toEqual(0); - - // Set attribute that matches the audience filter (age >= 20) - context.attribute("age", 25); - - // Second peek call should re-evaluate and return assigned variant (1) - expect(context.peek("exp_test_ab")).toEqual(1); - - // peek() should not queue exposures - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should re-evaluate audience expression when attributes change in non-strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - // First peek call without matching attribute should return assigned variant (1) - expect(context.peek("exp_test_ab")).toEqual(1); - - // Set attribute that matches the audience filter (age >= 20) - context.attribute("age", 25); - - // Second peek call should re-evaluate and still return 1 - expect(context.peek("exp_test_ab")).toEqual(1); - - // peek() should not queue exposures - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should not re-evaluate audience when no new attributes set", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute first - context.attribute("age", 15); - - // First peek call with non-matching attribute should return control (0) - expect(context.peek("exp_test_ab")).toEqual(0); - - // Second peek call without adding new attributes should use cached assignment - expect(context.peek("exp_test_ab")).toEqual(0); - - // peek() should not queue exposures - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - describe("treatment()", () => { - it("should queue exposures", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 2, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_abc", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 3, - assigned: true, - eligible: false, - exposedAt: 1611141535729, - name: "exp_test_not_eligible", - overridden: false, - unit: "user_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 4, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_fullon", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: true, - custom: false, - audienceMismatch: false, - }, - { - id: 5, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_custom_fields", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposures after peek()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.peek(experiment.name); - } - - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - done(); - }); - - it("should queue exposures only once", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - done(); - }); - - it("should call event logger", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - for (const experiment of getContextResponse.experiments) { - SDK.defaultEventLogger.mockClear(); - context.treatment(experiment.name); - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "exposure", { - exposedAt: timeOrigin, - eligible: experiment.name !== "exp_test_not_eligible", - assigned: true, - overridden: false, - id: experiment.id, - name: experiment.name, - unit: experiment.unitType, - variant: expectedVariants[experiment.name], - fullOn: experiment.name === "exp_test_fullon", - custom: false, - audienceMismatch: false, - }); - } - - // check it calls logger only once - for (const experiment of getContextResponse.experiments) { - SDK.defaultEventLogger.mockClear(); - context.treatment(experiment.name); - expect(SDK.defaultEventLogger).not.toHaveBeenCalled(); - } - - done(); - }); - - it("should queue exposure with base variant on unknown/stopped experiment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.treatment("not_found")).toEqual(0); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 0, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "not_found", - overridden: false, - unit: null, - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch true on audience match", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - context.attribute("age", 21); - - expect(context.treatment("exp_test_ab")).toEqual(1); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - attributes: [ - { - name: "age", - setAt: 1611141535729, - value: 21, - }, - ], - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch false on audience mismatch", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - expect(context.treatment("exp_test_ab")).toEqual(1); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: true, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch false and control variant on audience mismatch in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.treatment("exp_test_ab")).toEqual(0); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: true, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should not re-queue exposure on unknown experiment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - expect(context.pending()).toEqual(0); - - expect(context.treatment("not_found")).toEqual(0); - - expect(context.pending()).toEqual(1); - - expect(context.treatment("not_found")).toEqual(0); - - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should queue exposure with override variant", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.override("exp_test_ab", 5); - context.override("not_found", 3); - - expect(context.treatment("exp_test_ab")).toEqual(5); - expect(context.treatment("not_found")).toEqual(3); - - expect(context.pending()).toEqual(2); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: true, - unit: "session_id", - variant: 5, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 0, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "not_found", - overridden: true, - unit: null, - variant: 3, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.treatment("exp_test_ab")).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.treatment("exp_test_ab")).toThrow(); - }); - - it("should re-evaluate audience expression when attributes change in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // First treatment call without matching attribute should return control (0) - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set attribute that matches the audience filter (age >= 20) - context.attribute("age", 25); - - // Second treatment call should re-evaluate and return assigned variant (1) - expect(context.treatment("exp_test_ab")).toEqual(1); - - // Should queue another exposure - expect(context.pending()).toEqual(2); - - done(); - }); - - it("should re-evaluate audience expression when attributes change in non-strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - // First treatment call without matching attribute should return assigned variant (1) - // but with audienceMismatch = true - expect(context.treatment("exp_test_ab")).toEqual(1); - expect(context.pending()).toEqual(1); - - // Set attribute that matches the audience filter (age >= 20) - context.attribute("age", 25); - - // Second treatment call should re-evaluate - // The variant stays 1, but audienceMismatch should now be false - expect(context.treatment("exp_test_ab")).toEqual(1); - - // Should queue another exposure since audience result changed - expect(context.pending()).toEqual(2); - - done(); - }); - - it("should not re-evaluate audience when no new attributes set", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute first - context.attribute("age", 15); - - // First treatment call with non-matching attribute should return control (0) - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Second treatment call without adding new attributes should use cached assignment - expect(context.treatment("exp_test_ab")).toEqual(0); - - // Should not queue another exposure (uses cached assignment) - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should not re-evaluate audience for experiments without audience filter", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - // First treatment call - expect(context.treatment("exp_test_abc")).toEqual(2); - expect(context.pending()).toEqual(1); - - // Set an attribute - context.attribute("age", 25); - - // Second treatment call should use cached assignment since no audience filter - expect(context.treatment("exp_test_abc")).toEqual(2); - - // Should not queue another exposure - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should re-evaluate from audience mismatch to match in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // First treatment call without attribute - audience mismatch, returns 0 - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - // Verify first exposure had audienceMismatch = true - expect(publisher.publish).toHaveBeenCalledWith( - expect.objectContaining({ - exposures: [ - expect.objectContaining({ - name: "exp_test_ab", - variant: 0, - audienceMismatch: true, - assigned: false, - }), - ], - }), - sdk, - context, - undefined - ); - - // Set matching attribute - context.attribute("age", 30); - - // Second treatment should re-evaluate and return 1 - expect(context.treatment("exp_test_ab")).toEqual(1); - expect(context.pending()).toEqual(1); - - context.publish().then(() => { - // Verify second exposure had audienceMismatch = false - expect(publisher.publish).toHaveBeenCalledWith( - expect.objectContaining({ - exposures: [ - expect.objectContaining({ - name: "exp_test_ab", - variant: 1, - audienceMismatch: false, - assigned: true, - }), - ], - }), - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - - it("should not re-evaluate when attribute set before assignment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute before treatment call - context.attribute("age", 25); - - // First treatment call - attribute was already set, included in assignment - expect(context.treatment("exp_test_ab")).toEqual(1); - expect(context.pending()).toEqual(1); - - // Second treatment call should use cached assignment - expect(context.treatment("exp_test_ab")).toEqual(1); - - // Should not queue another exposure - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should re-evaluate when attribute set after assignment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // First treatment call without attribute - audience mismatch, returns 0 - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set attribute AFTER assignment was computed - context.attribute("age", 25); - - // Second treatment call should re-evaluate because attrsSeq increased - expect(context.treatment("exp_test_ab")).toEqual(1); - - // Should queue another exposure - expect(context.pending()).toEqual(2); - - done(); - }); - - it("should not invalidate cache when audience result unchanged after attribute change", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute that doesn't match (age < 20) - context.attribute("age", 15); - - // First treatment call - audience mismatch, returns 0 - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set another attribute that still doesn't match (age < 20) - context.attribute("age", 18); - - // Second treatment call - audience result unchanged (still mismatch), should use cache - expect(context.treatment("exp_test_ab")).toEqual(0); - - // Should NOT queue another exposure since audience result didn't change - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should update attrsSeq after checking unchanged audience to avoid repeated evaluation", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute that doesn't match (age < 20) - context.attribute("age", 15); - - // First treatment call - audience mismatch, returns 0 - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set another attribute that still doesn't match - context.attribute("age", 16); - - // Second treatment - evaluates audience but result unchanged, updates attrsSeq - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set yet another attribute that still doesn't match - context.attribute("age", 17); - - // Third treatment - evaluates audience but result unchanged, updates attrsSeq - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // No new attributes set - // Fourth treatment - should use cache without re-evaluating - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - done(); - }); - }); - - describe("variableValue()", () => { - it("should not return variable values when unassigned", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.pending()).toEqual(0); - - expect(context.variableValue("banner.size", 17)).toEqual(17); - - done(); - }); - it("should return variable values when overridden", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.pending()).toEqual(0); - - context.override("exp_test_ab", 0); - - expect(context.variableValue("banner.size", 17)).toEqual("tiny"); - - done(); - }); - it("conflicting key disjoint audiences", (done) => { - const context1 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse); - const context2 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse); - - expect(context1.pending()).toEqual(0); - expect(context2.pending()).toEqual(0); - - expect(expectedVariants["exp_test_ab"]).not.toEqual(0); - expect(expectedVariants["exp_test_abc"]).not.toEqual(0); - - context1.attribute("age", 20); - expect(context1.variableValue("icon", "square")).toEqual("arrow"); - - context2.attribute("age", 19); - expect(context2.variableValue("icon", "square")).toEqual("circle"); - - done(); - }); - it("should queue exposures", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - const experiments = context.experiments(); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.variableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 2, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_abc", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 3, - assigned: true, - eligible: false, - exposedAt: 1611141535729, - name: "exp_test_not_eligible", - overridden: false, - unit: "user_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 4, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_fullon", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: true, - custom: false, - audienceMismatch: false, - }, - { - id: 5, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_custom_fields", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposures after peekVariable()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - const experiments = context.experiments(); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.peekVariableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(0); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.variableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - done(); - }); - - it("should queue exposures only once", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - const experiments = context.experiments(); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.variableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.variableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - done(); - }); - - it("should call event logger", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experiments = context.experiments(); - const exposed = {}; - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - SDK.defaultEventLogger.mockClear(); - context.variableValue(key, 17); - - if (experiments.indexOf(experimentName) !== -1) { - const experiment = getContextResponse.experiments.filter((x) => x.name === experimentName)[0]; - if (!(experimentName in exposed)) { - exposed[experimentName] = true; - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "exposure", { - exposedAt: timeOrigin, - eligible: experiment.name !== "exp_test_not_eligible", - assigned: true, - overridden: false, - id: experiment.id, - name: experiment.name, - unit: experiment.unitType, - variant: expectedVariants[experiment.name], - fullOn: experiment.name === "exp_test_fullon", - custom: false, - audienceMismatch: false, - }); - } else { - expect(SDK.defaultEventLogger).not.toHaveBeenCalled(); - } - } - } - - // check it calls logger only once - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - SDK.defaultEventLogger.mockClear(); - context.variableValue(key, 17); - if (experiments.indexOf(experimentName) !== -1) { - expect(SDK.defaultEventLogger).not.toHaveBeenCalled(); - } - } - - done(); - }); - - it("should return defaultValue on unknown variable", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.variableValue("not.found", 17)).toBe(17); - - done(); - }); - - it("should queue exposure with audienceMatch true on audience match", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - context.attribute("age", 21); - - expect(context.variableValue("banner.size", "small")).toEqual("large"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - attributes: [ - { - name: "age", - setAt: 1611141535729, - value: 21, - }, - ], - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch false on audience mismatch", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - expect(context.variableValue("banner.size", "small")).toEqual("large"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: true, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch false and control variant on audience mismatch in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.variableValue("banner.size", "small")).toEqual("small"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: true, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.variableValue("button.color", 17); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.variableValue("button.color", 17)).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.variableValue("button.color", 17)).toThrow(); - }); - }); - - describe("peekVariableValue()", () => { - it("should not return variable values when unassigned", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.pending()).toEqual(0); - - expect(context.peekVariableValue("banner.size", 17)).toEqual(17); - - done(); - }); - it("should return variable values when overridden", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.pending()).toEqual(0); - - context.override("exp_test_ab", 0); - - expect(context.peekVariableValue("banner.size", 17)).toEqual("tiny"); - - done(); - }); - it("conflicting key disjoint audiences", (done) => { - const context1 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse); - const context2 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse); - - expect(context1.pending()).toEqual(0); - expect(context2.pending()).toEqual(0); - - expect(expectedVariants["exp_test_ab"]).not.toEqual(0); - expect(expectedVariants["exp_test_abc"]).not.toEqual(0); - - context1.attribute("age", 20); - expect(context1.peekVariableValue("icon", "square")).toEqual("arrow"); - - context2.attribute("age", 19); - expect(context2.peekVariableValue("icon", "square")).toEqual("circle"); - - expect(context1.pending()).toEqual(0); - expect(context2.pending()).toEqual(0); - - done(); - }); - - it("should pick lowest experiment id on conflicting key", (done) => { - const context = new Context(sdk, contextOptions, contextParams, lowestIdConflictingKeyContextResponse); - - expect(context.pending()).toEqual(0); - - expect(expectedVariants["exp_test_ab"]).not.toEqual(0); - expect(expectedVariants["exp_test_abc"]).not.toEqual(0); - - expect(context.peekVariableValue("icon", "square")).toEqual("circle"); - - done(); - }); - - it("should not queue exposures", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - const experiments = context.experiments(); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.peekVariableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should return defaultValue on unknown override variant", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.override(experiment.name, expectedVariants[experiment.name] + 11); - } - context.override("not_found", 3); - - for (const key of Object.keys(variableExperiments)) { - const actual = context.peekVariableValue(key, 17); - expect(actual).toBe(17); - } - - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should return assigned variant on audience mismatch in non-strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - expect(context.peekVariableValue("banner.size", "small")).toEqual("large"); - - done(); - }); - - it("should return control variant on audience mismatch in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.peekVariableValue("banner.size", "small")).toEqual("small"); - - done(); - }); - }); - - describe("variableKeys()", () => { - it("should return all active keys", (done) => { - const context = new Context(sdk, contextOptions, contextParams, refreshContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.variableKeys()).toMatchObject(variableExperiments); - - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - describe("track()", () => { - it("should queue goals", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.track("goal1", { amount: 125, hours: 245 }); - context.track("goal2", { tries: 7 }); - - expect(context.pending()).toEqual(2); - - context.track("goal2", { tests: 12 }); - - expect(context.pending()).toEqual(3); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - achievedAt: 1611141535729, - name: "goal1", - properties: { amount: 125, hours: 245 }, - }, - { - achievedAt: 1611141535729, - name: "goal2", - properties: { tries: 7 }, - }, - { - achievedAt: 1611141535729, - name: "goal2", - properties: { tests: 12 }, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should call event logger", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - SDK.defaultEventLogger.mockClear(); - context.track("goal1", { amount: 125, hours: 245 }); - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "goal", { - achievedAt: timeOrigin, - name: "goal1", - properties: { amount: 125, hours: 245 }, - }); - - done(); - }); - - it("should not throw when goal property values are numbers or objects with number values", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(() => context.track("goal1", { test: { flt: 1.5, int: 2 } })).not.toThrowError(); - expect(() => context.track("goal1", { test: {} })).not.toThrowError(); - expect(() => context.track("goal1", { test: null })).not.toThrowError(); - - expect(context.pending()).toEqual(3); - - done(); - }); - - it("should not throw when goal properties is null or undefined", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(() => context.track("goal1")).not.toThrowError(); - expect(() => context.track("goal1", null)).not.toThrowError(); - expect(() => context.track("goal1", undefined)).not.toThrowError(); - - expect(context.pending()).toEqual(3); - - done(); - }); - - it("should throw when goal properties not object", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(() => context.track("goal1", 125.0)).toThrowError("Goal 'goal1' properties must be of type object."); - expect(() => context.track("goal1", true)).toThrowError("Goal 'goal1' properties must be of type object."); - expect(() => context.track("goal1", "testy")).toThrowError( - "Goal 'goal1' properties must be of type object." - ); - expect(() => context.track("goal1", [])).toThrowError("Goal 'goal1' properties must be of type object."); - - done(); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.track("payment", { amount: 125 })).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.track("payment", { amount: 125 })).toThrow(); - }); - - it("should queue when not ready", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.pending()).toEqual(0); - expect(context.isReady()).toEqual(false); - - context.track("goal1", { amount: 125 }); - - expect(context.pending()).toEqual(1); - expect(context.isReady()).toEqual(false); - - context.ready().then(() => { - expect(context.pending()).toEqual(1); - - done(); - }); - }); - - it("should start timeout after ready if queue is not empty", (done) => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setTimeout"); - - const publishDelay = 100; - const context = new Context( - sdk, - Object.assign(contextOptions, { publishDelay }), - contextParams, - Promise.resolve(getContextResponse) - ); - - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin); - - context.track("goal1", { amount: 125 }); - - expect(context.pending()).toEqual(1); - - context.ready().then(() => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - jest.advanceTimersByTime(publishDelay - 1); - - expect(publisher.publish).not.toHaveBeenCalled(); - - publisher.publish.mockReturnValue(Promise.resolve({})); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - jest.advanceTimersByTime(2); - - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - name: "goal1", - achievedAt: 1611141535729, - properties: { amount: 125 }, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - - describe("publish()", () => { - it("should not call client publish when queue is empty", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).not.toHaveBeenCalled(); - done(); - }); - }); - - it("should propagate client error message", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.track("goal1", { amount: 125 }); - - publisher.publish.mockReturnValue(Promise.reject("test")); - - context.publish().catch((e) => { - expect(e).toEqual("test"); - - done(); - }); - }); - - it("should call client publish", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - context.treatment("exp_test_not_eligible"); - - Date.now.mockImplementation(() => timeOrigin + 1); // ensure that time is kept separately per event - context.track("goal1", { amount: 125, hours: 245 }); - - Date.now.mockImplementation(() => timeOrigin + 2); - context.attribute("attr1", "value1"); - - Date.now.mockImplementation(() => timeOrigin + 3); - context.attributes({ - attr2: "value2", - attr3: 3, - attr4: 5.0, - attr5: true, - attr6: [1, 2, 3, 4], - attr7: null, - attr8: [], - attr9: [null, 1, 2], - attr10: ["one", null, "two"], - attr11: [null, null], - }); - - expect(context.pending()).toEqual(3); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 3, - name: "exp_test_not_eligible", - unit: "user_id", - exposedAt: 1611141535729, - variant: 0, - assigned: true, - eligible: false, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - goals: [ - { - name: "goal1", - achievedAt: 1611141535730, - properties: { amount: 125, hours: 245 }, - }, - ], - attributes: [ - { - name: "attr1", - setAt: 1611141535731, - value: "value1", - }, - { - name: "attr2", - setAt: 1611141535732, - value: "value2", - }, - { - name: "attr3", - setAt: 1611141535732, - value: 3, - }, - { - name: "attr4", - setAt: 1611141535732, - value: 5.0, - }, - { - name: "attr5", - setAt: 1611141535732, - value: true, - }, - { - name: "attr6", - setAt: 1611141535732, - value: [1, 2, 3, 4], - }, - { - name: "attr7", - setAt: 1611141535732, - value: null, - }, - { - name: "attr8", - setAt: 1611141535732, - value: [], - }, - { - name: "attr9", - setAt: 1611141535732, - value: [null, 1, 2], - }, - { - name: "attr10", - setAt: 1611141535732, - value: ["one", null, "two"], - }, - - { - name: "attr11", - setAt: 1611141535732, - value: [null, null], - }, - ], - }, - sdk, - context, - undefined - ); - - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - it("should pass through request options", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.track("goal1", { amount: 125, hours: 245 }); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish({ timeout: 1234 }).then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - name: "goal1", - achievedAt: 1611141535729, - properties: { amount: 125, hours: 245 }, - }, - ], - }, - sdk, - context, - { - timeout: 1234, - } - ); - - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - it("should call event logger on error", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.track("goal1", { amount: 125, hours: 245 }); - - publisher.publish.mockReturnValue(Promise.reject("test error")); - - SDK.defaultEventLogger.mockClear(); - context.publish().catch((error) => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "error", error); - - done(); - }); - }); - - it("should call event logger on success", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.track("goal1", { amount: 125, hours: 245 }); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - SDK.defaultEventLogger.mockClear(); - context.publish().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "publish", { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - achievedAt: 1611141535729, - name: "goal1", - properties: { amount: 125, hours: 245 }, - }, - ], - }); - - done(); - }); - }); - - it("should not call client publish when failed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - context.ready().then(() => { - context.treatment("exp_test_ab"); - - Date.now.mockImplementation(() => timeOrigin + 1); // ensure that time is kept separately per event - context.track("goal1", { amount: 125, hours: 245 }); - - expect(context.pending()).toEqual(2); - - context.publish().then(() => { - expect(publisher.publish).not.toHaveBeenCalled(); - expect(context.pending()).toEqual(0); - - done(); - }); - }); - }); - - it("should reset internal queues and keep attributes overrides and custom assignments", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - context.track("goal1", { amount: 125, hours: 245 }); - context.attribute("attr1", "value1"); - - context.override("not_found", 3); - expect(context.treatment("not_found")).toEqual(3); - - context.customAssignment("exp_test_abc", 3); - expect(context.treatment("exp_test_abc")).toEqual(3); - - expect(context.pending()).toEqual(4); - - publisher.publish.mockReturnValue(Promise.resolve({})); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context - .publish() - .then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 0, - name: "not_found", - unit: null, - exposedAt: 1611141535729, - variant: 3, - assigned: false, - eligible: true, - overridden: true, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 2, - name: "exp_test_abc", - unit: "session_id", - exposedAt: 1611141535729, - variant: 3, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: true, - audienceMismatch: false, - }, - ], - goals: [ - { - name: "goal1", - achievedAt: 1611141535729, - properties: { amount: 125, hours: 245 }, - }, - ], - attributes: [ - { - name: "attr1", - setAt: 1611141535729, - value: "value1", - }, - ], - }, - sdk, - context, - undefined - ); - - expect(context.pending()).toEqual(0); - - publisher.publish.mockClear(); - }) - .then(() => { - context.track("goal2", { test: 999 }); - - return context.publish(); - }) - .then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - name: "goal2", - achievedAt: 1611141535829, - properties: { test: 999 }, - }, - ], - attributes: [ - { - name: "attr1", - setAt: 1611141535729, - value: "value1", - }, - ], - }, - sdk, - context, - undefined - ); - - expect(context.pending()).toEqual(0); - - expect(context.treatment("exp_test_abc")).toEqual(3); - expect(context.treatment("not_found")).toEqual(3); - - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - it("should be called options.publishDelay ms after an exposure being queued", () => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setTimeout"); - - const publishDelay = 100; - const context = new Context( - sdk, - Object.assign(contextOptions, { publishDelay }), - contextParams, - getContextResponse - ); - - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - context.track("goal1", { amount: 125 }); - - expect(context.pending()).toEqual(2); - expect(setTimeout).toHaveBeenCalledTimes(1); // no new calls - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - jest.advanceTimersByTime(publishDelay - 1); - - expect(publisher.publish).not.toHaveBeenCalled(); - - publisher.publish.mockReturnValue(Promise.resolve({})); - - jest.advanceTimersByTime(2); - - expect(publisher.publish).toHaveBeenCalledTimes(1); - }); - - it("should be called options.publishDelay ms after a goal being queued", () => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setTimeout"); - - const publishDelay = 100; - const context = new Context( - sdk, - Object.assign(contextOptions, { publishDelay }), - contextParams, - getContextResponse - ); - - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - context.track("goal1", { amount: 125 }); - - expect(context.pending()).toEqual(1); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(2); - expect(setTimeout).toHaveBeenCalledTimes(1); // no new calls - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - jest.advanceTimersByTime(publishDelay - 1); - - expect(publisher.publish).not.toHaveBeenCalled(); - - publisher.publish.mockReturnValue(Promise.resolve({})); - - jest.advanceTimersByTime(2); - - expect(publisher.publish).toHaveBeenCalledTimes(1); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.publish()).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.publish()).toThrow(); - }); - }); - - describe("finalize()", () => { - it("should not call client publish when queue is empty", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.finalize().then(() => { - expect(publisher.publish).not.toHaveBeenCalled(); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - done(); - }); - - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - }); - - it("should propagate client error message", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.reject("test")); - - context.finalize().catch((e) => { - expect(e).toEqual("test"); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should call client publish", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.finalize().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - expect(context.pending()).toEqual(0); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should pass through request options", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.finalize({ timeout: 1234 }).then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - { timeout: 1234 } - ); - - expect(context.pending()).toEqual(0); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should call event logger on error", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.reject("test error")); - - SDK.defaultEventLogger.mockClear(); - context.finalize().catch((error) => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "error", error); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should call event logger on success", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - SDK.defaultEventLogger.mockClear(); - context.finalize().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(2); - expect(SDK.defaultEventLogger).toHaveBeenLastCalledWith(context, "finalize", undefined); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should not call client publish when failed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - context.ready().then(() => { - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(publisher.publish).not.toHaveBeenCalled(); - expect(context.pending()).toEqual(0); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - }); - }); - - it("should return current promise when called twice", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - const firstPromise = context.finalize(); - const secondPromise = context.finalize(); - - expect(secondPromise).toBe(firstPromise); - - secondPromise.then(() => { - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should return completed promise when already finalized", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.finalize().then(() => { - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - context.finalize().then(() => { - done(); - }); - }); - }); - - it("should cancel refresh timer", (done) => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setInterval"); - jest.spyOn(global, "clearInterval"); - - const refreshPeriod = 1000; - const context = new Context( - sdk, - Object.assign(contextOptions, { refreshPeriod }), - contextParams, - getContextResponse - ); - - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - expect(setInterval).toHaveBeenCalledTimes(1); - expect(setInterval).toHaveBeenCalledWith(expect.anything(), refreshPeriod); - const timerId = setInterval.mock.results[0].value; - - context.finalize().then(() => { - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - expect(clearInterval).toHaveBeenCalledTimes(1); - expect(clearInterval).toHaveBeenCalledWith(timerId); - - done(); - }); - }); - }); - - describe("override()", () => { - it("should be callable before ready()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - context.override("exp_test_ab", 1); - context.overrides({ - exp_test_ab: 2, - exp_test_abc: 2, - not_found: 3, - }); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - - context.treatment("exp_test_ab"); - context.treatment("exp_test_abc"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 2, - assigned: false, - eligible: true, - overridden: true, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 2, - name: "exp_test_abc", - unit: "session_id", - exposedAt: 1611141535729, - variant: 2, - assigned: false, - eligible: true, - overridden: true, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - }); - - describe("customAssignment()", () => { - it("should override natural assignment and set custom flag", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.customAssignment("exp_test_abc", 11); - - expect(context.pending()).toEqual(0); // should not queue exposures - - context.treatment("exp_test_abc"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 2, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_abc", - overridden: false, - unit: "session_id", - variant: 11, - fullOn: false, - custom: true, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should not override full-on or non-eligible assignment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.customAssignment("exp_test_not_eligible", 11); - context.customAssignment("exp_test_fullon", 11); - - expect(context.pending()).toEqual(0); // should not queue exposures - - context.treatment("exp_test_not_eligible"); - context.treatment("exp_test_fullon"); - - expect(context.pending()).toEqual(2); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 3, - assigned: true, - eligible: false, - exposedAt: 1611141535729, - name: "exp_test_not_eligible", - overridden: false, - unit: "user_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 4, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_fullon", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: true, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should be callable before ready()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - context.customAssignment("exp_test_ab", 1); - context.customAssignments({ - exp_test_ab: 2, - exp_test_abc: 2, - not_found: 3, - }); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - - context.treatment("exp_test_ab"); - context.treatment("exp_test_abc"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 2, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: true, - audienceMismatch: false, - }, - { - id: 2, - name: "exp_test_abc", - unit: "session_id", - exposedAt: 1611141535729, - variant: 2, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: true, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.customAssignment("exp_test_ab", 3)).toThrow(); // finalizing - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.customAssignment("exp_test_ab", 3)).toThrow(); // finalizing - }); - }); - describe("customFieldKeys()", () => { - it("should return custom field keys", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - const keys = context.customFieldKeys(); - - expect(context.isReady()).toEqual(true); - expect(keys).toEqual([ - "country", - "json_object", - "json_array", - "json_number", - "json_string", - "json_boolean", - "json_null", - "json_invalid", - "languages", - "text_field", - "string_field", - "number_field", - "boolean_field", - "false_boolean_field", - "invalid_type_field", - ]); - }); - }); - - describe("customFieldValue()", () => { - it("should return custom field value", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - const value = context.customFieldValue("exp_test_custom_fields", "country"); - - expect(context.isReady()).toEqual(true); - expect(value).toEqual("US,PT,ES"); - }); - - it("should return parsed JSON fields", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_abc", "json_object")).toEqual({ 123: 1, 456: 0 }); - expect(context.customFieldValue("exp_test_abc", "json_array")).toEqual(["hello", "world"]); - expect(context.customFieldValue("exp_test_abc", "json_number")).toEqual(123); - expect(context.customFieldValue("exp_test_abc", "json_string")).toEqual("hello"); - expect(context.customFieldValue("exp_test_abc", "json_boolean")).toEqual(true); - expect(context.customFieldValue("exp_test_abc", "json_null")).toEqual(null); - }); - - it("should return string and text fields", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_custom_fields", "text_field")).toEqual("hello text"); - expect(context.customFieldValue("exp_test_custom_fields", "string_field")).toEqual("hello string"); - }); - - it("should return parsed number fields", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_custom_fields", "number_field")).toEqual(123); - }); - - it("should return parsed boolean fields", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_custom_fields", "boolean_field")).toEqual(true); - expect(context.customFieldValue("exp_test_custom_fields", "false_boolean_field")).toEqual(false); - }); - - it("should console an error when JSON cannot be parsed", () => { - const errorSpy = jest.spyOn(console, "error"); - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_abc", "json_invalid")).toEqual(null); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy).toHaveBeenCalledWith( - "Failed to parse JSON custom field value 'json_invalid' for experiment 'exp_test_abc'" - ); - }); - - it("should console an error when a field type is invalid", () => { - const errorSpy = jest.spyOn(console, "error"); - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_custom_fields", "invalid_type_field")).toEqual(null); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy).toHaveBeenCalledWith( - "Unknown custom field type 'invalid' for experiment 'exp_test_custom_fields' and key 'invalid_type_field' - you may need to upgrade to the latest SDK version" - ); - }); - }); - - describe("customFieldValueType()", () => { - it("should return custom field value type", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - const value = context.customFieldValueType("exp_test_custom_fields", "country"); - - expect(context.isReady()).toEqual(true); - expect(value).toEqual("string"); - }); - }); - - describe("includeSystemAttributes", () => { - it("should not include system attributes by default", (done) => { - const defaultOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - const context = new Context(sdk, defaultOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - expect(request.attributes).toBeUndefined(); - - done(); - }); - }); - - it("should include system attributes when includeSystemAttributes is true", (done) => { - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - expect(request.attributes).toBeDefined(); - expect(request.attributes.length).toBeGreaterThanOrEqual(4); - - const sdkNameAttr = request.attributes.find((a) => a.name === "sdk_name"); - const sdkVersionAttr = request.attributes.find((a) => a.name === "sdk_version"); - const applicationAttr = request.attributes.find((a) => a.name === "application"); - const environmentAttr = request.attributes.find((a) => a.name === "environment"); - - expect(sdkNameAttr).toBeDefined(); - expect(sdkNameAttr.value).toEqual("absmartly-javascript-sdk"); - expect(sdkNameAttr.setAt).toEqual(expect.any(Number)); - - expect(sdkVersionAttr).toBeDefined(); - expect(sdkVersionAttr.value).toEqual(SDK_VERSION); - expect(sdkVersionAttr.setAt).toEqual(expect.any(Number)); - - expect(applicationAttr).toBeDefined(); - expect(applicationAttr.value).toEqual("website"); - expect(applicationAttr.setAt).toEqual(expect.any(Number)); - - expect(environmentAttr).toBeDefined(); - expect(environmentAttr.value).toEqual("production"); - expect(environmentAttr.setAt).toEqual(expect.any(Number)); - - done(); - }); - }); - - it("should prepend system attributes before user attributes", (done) => { - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.attribute("custom_attr", "custom_value"); - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - expect(request.attributes[0].name).toEqual("sdk_name"); - expect(request.attributes[1].name).toEqual("sdk_version"); - expect(request.attributes[2].name).toEqual("application"); - expect(request.attributes[3].name).toEqual("environment"); - expect(request.attributes[4].name).toEqual("custom_attr"); - expect(request.attributes[4].value).toEqual("custom_value"); - - done(); - }); - }); - - it("should include app_version when application version is set", (done) => { - client.getApplication.mockReturnValueOnce({ name: "website", version: 3 }); - - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); - expect(appVersionAttr).toBeDefined(); - expect(appVersionAttr.value).toEqual(3); - - done(); - }); - }); - - it("should not include app_version when application version is 0", (done) => { - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); - expect(appVersionAttr).toBeUndefined(); - - done(); - }); - }); - - it("should include app_version when application version is a semver string", (done) => { - client.getApplication.mockReturnValueOnce({ name: "website", version: "1.2.3" }); - - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); - expect(appVersionAttr).toBeDefined(); - expect(appVersionAttr.value).toEqual("1.2.3"); - - done(); - }); - }); - - it("should include app_version with application as plain string", (done) => { - client.getApplication.mockReturnValueOnce({ name: "website", version: 0 }); - - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - const applicationAttr = request.attributes.find((a) => a.name === "application"); - expect(applicationAttr).toBeDefined(); - expect(applicationAttr.value).toEqual("website"); - - const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); - expect(appVersionAttr).toBeUndefined(); - - done(); - }); - }); - - it("should only include user attributes when includeSystemAttributes is not set", (done) => { - const defaultOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - const context = new Context(sdk, defaultOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.attribute("custom_attr", "custom_value"); - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - expect(request.attributes).toEqual([ - { name: "custom_attr", value: "custom_value", setAt: expect.any(Number) }, - ]); - - done(); - }); - }); - }); -}); diff --git a/src/__tests__/context.test.ts b/src/__tests__/context.test.ts new file mode 100644 index 0000000..4af3f1f --- /dev/null +++ b/src/__tests__/context.test.ts @@ -0,0 +1,3246 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { Context } from "../context"; +import { DefaultContextPublisher } from "../publisher"; +import { DefaultContextDataProvider } from "../provider"; +import { hashUnit } from "../hashing"; +import { SDK_VERSION } from "../version"; +import type { ContextPublisher, ContextDataProvider } from "../interfaces"; + +function clone(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} + +describe("Context", () => { + const contextParams = { + units: { + session_id: "e791e240fcd3df7d238cfc285f475e8152fcc0ec", + user_id: 12317303, + }, + }; + + const publishUnits = Object.entries(contextParams.units).map((x) => ({ type: x[0], uid: hashUnit(x[1]) })); + + const units = { + session_id: "e791e240fcd3df7d238cfc285f475e8152fcc0ec", + user_id: "123456789", + email: "bleh@absmartly.com", + }; + + const getContextResponse = { + experiments: [ + { + id: 1, + name: "exp_test_ab", + iteration: 1, + unitType: "session_id", + seedHi: 3603515, + seedLo: 233373850, + split: [0.5, 0.5], + trafficSeedHi: 449867249, + trafficSeedLo: 455443629, + trafficSplit: [0.0, 1.0], + fullOnVariant: 0, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"banner.border":1,"banner.size":"large"}' }, + ], + audience: null, + audienceStrict: false, + customFieldValues: null, + }, + { + id: 2, + name: "exp_test_abc", + iteration: 1, + unitType: "session_id", + seedHi: 55006150, + seedLo: 47189152, + split: [0.34, 0.33, 0.33], + trafficSeedHi: 705671872, + trafficSeedLo: 212903484, + trafficSplit: [0.0, 1.0], + fullOnVariant: 0, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"button.color":"blue"}' }, + { name: "C", config: '{"button.color":"red"}' }, + ], + audience: "", + audienceStrict: false, + customFieldValues: [ + { name: "country", value: "US,PT,ES,DE,FR", type: "string" }, + { name: "json_object", value: '{"123":1,"456":0}', type: "json" }, + { name: "json_array", value: '["hello", "world"]', type: "json" }, + { name: "json_number", value: "123", type: "json" }, + { name: "json_string", value: '"hello"', type: "json" }, + { name: "json_boolean", value: "true", type: "json" }, + { name: "json_null", value: "null", type: "json" }, + { name: "json_invalid", value: "invalid", type: "json" }, + ], + }, + { + id: 3, + name: "exp_test_not_eligible", + iteration: 1, + unitType: "user_id", + seedHi: 503266407, + seedLo: 144942754, + split: [0.34, 0.33, 0.33], + trafficSeedHi: 87768905, + trafficSeedLo: 511357582, + trafficSplit: [0.99, 0.01], + fullOnVariant: 0, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"card.width":"80%"}' }, + { name: "C", config: '{"card.width":"75%"}' }, + ], + audience: "{}", + audienceStrict: false, + customFieldValues: null, + }, + { + id: 4, + name: "exp_test_fullon", + iteration: 1, + unitType: "session_id", + seedHi: 856061641, + seedLo: 990838475, + split: [0.25, 0.25, 0.25, 0.25], + trafficSeedHi: 360868579, + trafficSeedLo: 330937933, + trafficSplit: [0.0, 1.0], + fullOnVariant: 2, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"submit.color":"red","submit.shape":"circle"}' }, + { name: "C", config: '{"submit.color":"blue","submit.shape":"rect"}' }, + { name: "D", config: '{"submit.color":"green","submit.shape":"square"}' }, + ], + audience: "null", + audienceStrict: false, + customFieldValues: null, + }, + { + id: 5, + name: "exp_test_custom_fields", + iteration: 1, + unitType: "session_id", + seedHi: 9372617, + seedLo: 121364805, + split: [0.5, 0.5], + trafficSeedHi: 318746944, + trafficSeedLo: 359812364, + trafficSplit: [0.0, 1.0], + fullOnVariant: 0, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"submit.size":"sm"}' }, + ], + audience: null, + audienceStrict: false, + customFieldValues: [ + { name: "country", value: "US,PT,ES", type: "string" }, + { name: "languages", value: "en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX", type: "string" }, + { name: "text_field", value: "hello text", type: "text" }, + { name: "string_field", value: "hello string", type: "string" }, + { name: "number_field", value: "123", type: "number" }, + { name: "boolean_field", value: "true", type: "boolean" }, + { name: "false_boolean_field", value: "false", type: "boolean" }, + { name: "invalid_type_field", value: "invalid", type: "invalid" }, + ], + }, + ], + }; + + const refreshContextResponse = { + ...getContextResponse, + experiments: [ + { + id: 6, + name: "exp_test_new", + iteration: 2, + unitType: "session_id", + seedHi: 934590467, + seedLo: 714771373, + split: [0.5, 0.5], + trafficSeedHi: 940553836, + trafficSeedLo: 270705624, + trafficSplit: [0.0, 1.0], + fullOnVariant: 1, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"show-modal":true}' }, + ], + audience: null, + audienceStrict: false, + customFieldValues: null, + }, + ...getContextResponse.experiments, + ], + }; + + const audienceContextResponse = { + ...getContextResponse, + experiments: getContextResponse.experiments.map((x) => { + if (x.name === "exp_test_ab") { + return { + ...x, + audience: JSON.stringify({ + filter: [{ gte: [{ var: "age" }, { value: 20 }] }], + }), + }; + } + return x; + }), + }; + + const audienceStrictContextResponse = { + ...audienceContextResponse, + experiments: audienceContextResponse.experiments.map((x) => { + if (x.name === "exp_test_ab") { + return { + ...x, + audienceStrict: true, + variants: x.variants.map((v) => { + if (v.name === "A") { + return { name: "A", config: '{"banner.size":"tiny"}' }; + } + return v; + }), + }; + } + return x; + }), + }; + + const expectedVariants: Record = { + exp_test_ab: 1, + exp_test_abc: 2, + exp_test_not_eligible: 0, + exp_test_fullon: 2, + exp_test_new: 1, + exp_test_custom_fields: 1, + }; + + const lowestIdConflictingKeyContextResponse = { + ...getContextResponse, + experiments: getContextResponse.experiments.map((e) => { + if (e.name === "exp_test_ab") { + return { + ...e, + id: 99, + variants: e.variants.map((v, i) => { + if (i === expectedVariants[e.name]) { + return { ...v, config: JSON.stringify({ icon: "arrow" }) }; + } + return v; + }), + }; + } + if (e.name === "exp_test_abc") { + return { + ...e, + id: 1, + variants: e.variants.map((v, i) => { + if (i === expectedVariants[e.name]) { + return { ...v, config: JSON.stringify({ icon: "circle" }) }; + } + return v; + }), + }; + } + return e; + }), + }; + + const disjointedContextResponse = { + ...getContextResponse, + experiments: getContextResponse.experiments.map((exp) => { + if (exp.name === "exp_test_ab") { + return { + ...exp, + audienceStrict: true, + audience: JSON.stringify({ + filter: [{ gte: [{ var: "age" }, { value: 20 }] }], + }), + variants: exp.variants.map((v, i) => { + if (i === expectedVariants[exp.name]) { + return { ...v, config: JSON.stringify({ icon: "arrow" }) }; + } + return v; + }), + }; + } + if (exp.name === "exp_test_abc") { + return { + ...exp, + audienceStrict: true, + audience: JSON.stringify({ + filter: [{ lt: [{ var: "age" }, { value: 20 }] }], + }), + variants: exp.variants.map((variant, i) => { + if (i === expectedVariants[exp.name]) { + return { ...variant, config: JSON.stringify({ icon: "circle" }) }; + } + return variant; + }), + }; + } + return exp; + }), + }; + + const expectedVariables: Record = { + "banner.border": 1, + "banner.size": "large", + "button.color": "red", + "submit.color": "blue", + "submit.shape": "rect", + "show-modal": true, + "submit.size": "sm", + }; + + const variableExperiments: Record = { + "banner.border": ["exp_test_ab"], + "banner.size": ["exp_test_ab"], + "button.color": ["exp_test_abc"], + "card.width": ["exp_test_not_eligible"], + "submit.color": ["exp_test_fullon"], + "submit.shape": ["exp_test_fullon"], + "submit.size": ["exp_test_custom_fields"], + "show-modal": ["exp_test_new"], + }; + + const defaultEventLogger = vi.fn(); + + const publisher = { + publish: vi.fn(), + } as unknown as ContextPublisher & { publish: ReturnType }; + + const provider = { + getContextData: vi.fn(), + } as unknown as ContextDataProvider & { getContextData: ReturnType }; + + const client = { + getAgent: vi.fn().mockReturnValue("absmartly-javascript-sdk"), + getApplication: vi.fn().mockReturnValue({ name: "website", version: 0 }), + getEnvironment: vi.fn().mockReturnValue("production"), + }; + + const sdk = { + getContextPublisher: vi.fn().mockReturnValue(publisher), + getContextDataProvider: vi.fn().mockReturnValue(provider), + getClient: vi.fn().mockReturnValue(client), + getEventLogger: vi.fn().mockReturnValue(defaultEventLogger), + }; + + const contextOptions = { + publishDelay: -1, + refreshPeriod: 0, + }; + + const timeOrigin = 1611141535729; + + beforeEach(() => { + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin); + vi.clearAllMocks(); + sdk.getContextPublisher.mockReturnValue(publisher); + sdk.getContextDataProvider.mockReturnValue(provider); + sdk.getClient.mockReturnValue(client); + sdk.getEventLogger.mockReturnValue(defaultEventLogger); + client.getAgent.mockReturnValue("absmartly-javascript-sdk"); + client.getApplication.mockReturnValue({ name: "website", version: 0 }); + client.getEnvironment.mockReturnValue("production"); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("Context", () => { + it("should be ready with data", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.isReady()).toEqual(true); + expect(context.isFailed()).toEqual(false); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + expect(context.eventLogger()).toBe(defaultEventLogger); + expect(context.provider()).toBe(provider); + expect(context.publisher()).toBe(publisher); + }); + + it("should use custom publisher, dataProvider and eventLogger", async () => { + const customPublisher = { publish: vi.fn() } as unknown as ContextPublisher; + const customDataProvider = { getContextData: vi.fn() } as unknown as ContextDataProvider; + const customEventLogger = vi.fn(); + + const context = new Context( + sdk, + { + ...contextOptions, + publisher: customPublisher, + dataProvider: customDataProvider, + eventLogger: customEventLogger, + }, + contextParams, + getContextResponse as any + ); + expect(context.isReady()).toEqual(true); + expect(context.isFailed()).toEqual(false); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + expect(context.eventLogger()).toBe(customEventLogger); + expect(context.provider()).toBe(customDataProvider); + expect(context.publisher()).toBe(customPublisher); + }); + + it("should become ready and call handler", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + expect(context.eventLogger()).toBe(defaultEventLogger); + expect(context.provider()).toBe(provider); + expect(context.publisher()).toBe(publisher); + }); + + it("should become ready and failed, and call handler on failure", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.isFailed()).toEqual(true); + expect(context.data()).toStrictEqual({}); + expect(context.eventLogger()).toBe(defaultEventLogger); + expect(context.provider()).toBe(provider); + expect(context.publisher()).toBe(publisher); + }); + + it("ready() should resolve true even when context fails", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject(new Error("network error")) as any); + const result = await context.ready(); + expect(result).toBe(true); + expect(context.isFailed()).toBe(true); + expect(context.isReady()).toBe(true); + }); + + it("should return control variant (0) for all experiments after failure", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject(new Error("server error")) as any); + await context.ready(); + expect(context.isFailed()).toBe(true); + + expect(context.treatment("exp_test_ab")).toBe(0); + expect(context.treatment("exp_test_abc")).toBe(0); + expect(context.treatment("any_unknown_experiment")).toBe(0); + expect(context.peek("exp_test_ab")).toBe(0); + }); + + it("ready() should resolve true on success", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + const result = await context.ready(); + expect(result).toBe(true); + expect(context.isFailed()).toBe(false); + }); + + it("should call event logger on error", async () => { + defaultEventLogger.mockClear(); + + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + await context.ready(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "error", "bad request error text"); + }); + + it("should call event logger on success", async () => { + defaultEventLogger.mockClear(); + + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + await context.ready(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "ready", getContextResponse); + }); + + it("should call event logger on pre-fetched experiment data", async () => { + defaultEventLogger.mockClear(); + + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + await context.ready(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "ready", getContextResponse); + }); + + it("should throw when not ready", () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + + expect(() => context.data()).toThrow(); + expect(() => context.treatment("test")).toThrow(); + expect(() => context.peek("test")).toThrow(); + expect(() => context.experiments()).toThrow(); + expect(() => context.variableKeys()).toThrow(); + expect(() => context.variableValue("a", "17")).toThrow(); + expect(() => context.peekVariableValue("a", "17")).toThrow(); + }); + + it("should load experiment data", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + expect(context.experiments()).toEqual(getContextResponse.experiments.map((x) => x.name)); + for (const experiment of getContextResponse.experiments) { + expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name]); + expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); + } + expect(context.data()).toEqual(getContextResponse); + }); + }); + + describe("unit()", () => { + it("should set a unit", () => { + const context = new Context(sdk, contextOptions, { units: {} }, getContextResponse as any); + + context.units(units); + + for (const [key, value] of Object.entries(units)) { + expect(context.getUnit(key)).toEqual(value); + } + + expect(context.getUnits()).toEqual(units); + }); + + it("should throw on duplicate unit type set", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.isReady()).toEqual(true); + + expect(() => context.unit("session_id", "new_id")).toThrow(); + expect(() => context.unit("session_id", "e791e240fcd3df7d238cfc285f475e8152fcc0ec")).not.toThrow(); + }); + + it("should throw on invalid uid", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.isReady()).toEqual(true); + + expect(() => context.unit("session_id", "")).toThrow(); + expect(() => context.unit("session_id", null as any)).toThrow(); + expect(() => context.unit("session_id", undefined as any)).toThrow(); + expect(() => context.unit("session_id", true as any)).toThrow(); + expect(() => context.unit("session_id", {} as any)).toThrow(); + expect(() => context.unit("session_id", [] as any)).toThrow(); + }); + + it("should be callable before ready()", async () => { + const context = new Context(sdk, contextOptions, { units: {} } as any, Promise.resolve(getContextResponse as any)); + + context.units(contextParams.units); + + await context.ready(); + expect(context.isReady()).toEqual(true); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.unit("test", "test")).toThrow(); + + await finalizePromise; + + expect(() => context.unit("test", "test")).toThrow(); + }); + }); + + describe("getAttribute()", () => { + it("should get the last set attribute", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.attribute("attr1", "value1"); + context.attribute("attr1", "value2"); + + expect(context.getAttribute("attr1")).toEqual("value2"); + }); + }); + + describe("attribute()", () => { + it("should set an attribute", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.attribute("attr1", "value1"); + context.attributes({ + attr2: "value2", + attr3: 15, + }); + + expect(context.getAttribute("attr1")).toEqual("value1"); + expect(context.getAttributes()).toEqual({ + attr1: "value1", + attr2: "value2", + attr3: 15, + }); + }); + + it("should be callable before ready()", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + + context.attribute("attr1", "value1"); + context.attributes({ + attr2: "value2", + attr3: 3, + }); + + expect(context.getAttribute("attr1")).toEqual("value1"); + expect(context.getAttributes()).toEqual({ + attr1: "value1", + attr2: "value2", + attr3: 3, + }); + + await context.ready(); + expect(context.isReady()).toEqual(true); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + attributes: [ + { name: "attr1", setAt: 1611141535729, value: "value1" }, + { name: "attr2", setAt: 1611141535729, value: "value2" }, + { name: "attr3", setAt: 1611141535729, value: 3 }, + ], + }, + sdk, + context, + undefined + ); + }); + }); + + describe("refresh()", () => { + it("should call client and load new data", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + await context.refresh(); + expect(provider.getContextData).toHaveBeenCalledTimes(1); + expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); + + expect(context.experiments()).toEqual(refreshContextResponse.experiments.map((x) => x.name)); + for (const experiment of refreshContextResponse.experiments) { + expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); + } + expect(context.data()).toEqual(refreshContextResponse); + }); + + it("should pass through request options", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + await context.refresh({ timeout: 1234 } as any); + expect(provider.getContextData).toHaveBeenCalledTimes(1); + expect(provider.getContextData).toHaveBeenCalledWith(sdk, { timeout: 1234 }); + + expect(context.experiments()).toEqual(refreshContextResponse.experiments.map((x) => x.name)); + for (const experiment of refreshContextResponse.experiments) { + expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); + } + expect(context.data()).toEqual(refreshContextResponse); + }); + + it("should reject promise on error", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValueOnce(Promise.reject(new Error("test error"))); + + await expect(context.refresh()).rejects.toThrow("test error"); + }); + + it("should not re-queue exposures after refresh when not changed", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + await context.refresh(); + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + expect(provider.getContextData).toHaveBeenCalledTimes(1); + expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + for (const experiment of refreshContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(refreshContextResponse.experiments.length); + }); + + it("should not re-queue when not changed on audience mismatch", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(audienceStrictContextResponse)); + + await context.refresh(); + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + }); + + it("should not re-queue when not changed with override", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + context.override("exp_test_ab", 3); + expect(context.treatment("exp_test_ab")).toEqual(3); + expect(context.pending()).toEqual(1); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(audienceStrictContextResponse)); + + await context.refresh(); + expect(context.treatment("exp_test_ab")).toEqual(3); + expect(context.pending()).toEqual(1); + }); + + it("should not call client publish when failed", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + + await context.ready(); + await context.refresh(); + expect(provider.getContextData).not.toHaveBeenCalled(); + }); + + it("should call event logger when failed", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + await context.ready(); + (provider.getContextData as ReturnType).mockReturnValueOnce(Promise.reject(new Error("test error"))); + + defaultEventLogger.mockClear(); + await expect(context.refresh()).rejects.toThrow("test error"); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "error", expect.any(Error)); + }); + + it("should call event logger on success", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + + (provider.getContextData as ReturnType).mockReturnValueOnce(Promise.resolve(refreshContextResponse)); + + await context.ready(); + defaultEventLogger.mockClear(); + await context.refresh(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "refresh", refreshContextResponse); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.refresh()).toThrow(); + + await finalizePromise; + + expect(() => context.refresh()).toThrow(); + }); + + it("should keep overrides", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + context.override("not_found", 3); + expect(context.peek("not_found")).toEqual(3); + + await context.refresh(); + expect(context.peek("not_found")).toEqual(3); + }); + + it("should keep custom assignments", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + context.customAssignment("exp_test_ab", 3); + expect(context.peek("exp_test_ab")).toEqual(3); + + await context.refresh(); + expect(context.peek("exp_test_ab")).toEqual(3); + }); + + it("should pick up changes in experiment stopped", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_abc"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const stoppedRefreshContextResponse = clone(getContextResponse); + stoppedRefreshContextResponse.experiments = stoppedRefreshContextResponse.experiments.filter( + (x) => x.name !== experimentName + ); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(stoppedRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(0); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment started", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_new"; + + expect(context.treatment(experimentName)).toEqual(0); + expect(context.pending()).toEqual(1); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment fullon", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_abc"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const fullOnRefreshContextResponse = clone(getContextResponse); + for (const experiment of fullOnRefreshContextResponse.experiments) { + if (experiment.name === experimentName) { + expect(experiment.fullOnVariant).toEqual(0); + experiment.fullOnVariant = 1; + expect(expectedVariants[experimentName]).not.toEqual(experiment.fullOnVariant); + } + } + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(fullOnRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment traffic split", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_not_eligible"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const scaledUpRefreshContextResponse = clone(getContextResponse); + for (const experiment of scaledUpRefreshContextResponse.experiments) { + if (experiment.name === experimentName) { + experiment.trafficSplit = [0.0, 1.0]; + } + } + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(scaledUpRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(2); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment iteration", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_abc"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const iteratedRefreshContextResponse = clone(getContextResponse); + for (const experiment of iteratedRefreshContextResponse.experiments) { + if (experiment.name === experimentName) { + experiment.iteration = 2; + experiment.trafficSeedHi = 398724581; + experiment.seedHi = 34737352; + } + } + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(iteratedRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment id", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_abc"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const iteratedRefreshContextResponse = clone(getContextResponse); + for (const experiment of iteratedRefreshContextResponse.experiments) { + if (experiment.name === experimentName) { + experiment.id = 11; + experiment.trafficSeedHi = 398724581; + experiment.seedHi = 34737352; + } + } + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(iteratedRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(1); + expect(context.pending()).toEqual(2); + }); + }); + + describe("peek()", () => { + it("should not queue exposures", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + for (const experiment of getContextResponse.experiments) { + expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name]); + } + + expect(context.pending()).toEqual(0); + }); + + it("should return override variant", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + for (const experiment of getContextResponse.experiments) { + context.override(experiment.name, expectedVariants[experiment.name] + 11); + } + context.override("not_found", 3); + + for (const experiment of getContextResponse.experiments) { + expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name] + 11); + } + + expect(context.peek("not_found")).toEqual(3); + expect(context.pending()).toEqual(0); + }); + + it("should return assigned variant on audience mismatch in non-strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + expect(context.peek("exp_test_ab")).toEqual(1); + }); + + it("should return control variant on audience mismatch in strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.peek("exp_test_ab")).toEqual(0); + }); + + it("should re-evaluate audience expression when attributes change in strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + expect(context.peek("exp_test_ab")).toEqual(0); + + context.attribute("age", 25); + + expect(context.peek("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(0); + }); + + it("should re-evaluate audience expression when attributes change in non-strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + + expect(context.peek("exp_test_ab")).toEqual(1); + + context.attribute("age", 25); + + expect(context.peek("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(0); + }); + + it("should not re-evaluate audience when no new attributes set", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + context.attribute("age", 15); + + expect(context.peek("exp_test_ab")).toEqual(0); + expect(context.peek("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(0); + }); + }); + + describe("treatment()", () => { + it("should queue exposures", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 2, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_abc", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 3, + assigned: true, + eligible: false, + exposedAt: 1611141535729, + name: "exp_test_not_eligible", + overridden: false, + unit: "user_id", + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: true, + }, + { + id: 4, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_fullon", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: true, + custom: false, + audienceMismatch: true, + }, + { + id: 5, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_custom_fields", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should queue exposures only once", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + }); + + it("should call event logger", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + for (const experiment of getContextResponse.experiments) { + defaultEventLogger.mockClear(); + context.treatment(experiment.name); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "exposure", { + exposedAt: timeOrigin, + eligible: experiment.name !== "exp_test_not_eligible", + assigned: true, + overridden: false, + id: experiment.id, + name: experiment.name, + unit: experiment.unitType, + variant: expectedVariants[experiment.name], + fullOn: experiment.name === "exp_test_fullon", + custom: false, + audienceMismatch: experiment.name === "exp_test_not_eligible" || experiment.name === "exp_test_fullon", + }); + } + + for (const experiment of getContextResponse.experiments) { + defaultEventLogger.mockClear(); + context.treatment(experiment.name); + expect(defaultEventLogger).not.toHaveBeenCalled(); + } + }); + + it("should queue exposure with base variant on unknown/stopped experiment", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.treatment("not_found")).toEqual(0); + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 0, + assigned: false, + eligible: true, + exposedAt: 1611141535729, + name: "not_found", + overridden: false, + unit: null, + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should queue exposure with audienceMatch true on audience match", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + context.attribute("age", 21); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + attributes: [{ name: "age", setAt: 1611141535729, value: 21 }], + exposures: [ + { + id: 1, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should queue exposure with audienceMatch false on audience mismatch", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: true, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should queue exposure with audienceMatch false and control variant on audience mismatch in strict mode", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: false, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: true, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should not re-queue exposure on unknown experiment", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + expect(context.pending()).toEqual(0); + expect(context.treatment("not_found")).toEqual(0); + expect(context.pending()).toEqual(1); + expect(context.treatment("not_found")).toEqual(0); + expect(context.pending()).toEqual(1); + }); + + it("should queue exposure with override variant", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.override("exp_test_ab", 5); + context.override("not_found", 3); + + expect(context.treatment("exp_test_ab")).toEqual(5); + expect(context.treatment("not_found")).toEqual(3); + + expect(context.pending()).toEqual(2); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: false, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: true, + unit: "session_id", + variant: 5, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 0, + assigned: false, + eligible: true, + exposedAt: 1611141535729, + name: "not_found", + overridden: true, + unit: null, + variant: 3, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.treatment("exp_test_ab")).toThrow(); + + await finalizePromise; + + expect(() => context.treatment("exp_test_ab")).toThrow(); + }); + + it("should re-evaluate audience expression when attributes change in strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + context.attribute("age", 25); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should re-evaluate audience expression when attributes change in non-strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(1); + + context.attribute("age", 25); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should not re-evaluate audience when no new attributes set", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + context.attribute("age", 15); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + }); + + it("should not invalidate cache when audience result unchanged after attribute change", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + context.attribute("age", 15); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + context.attribute("age", 18); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + }); + }); + + describe("variableValue()", () => { + it("should not return variable values when unassigned", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.pending()).toEqual(0); + expect(context.variableValue("banner.size", "17")).toEqual("17"); + }); + + it("should return variable values when overridden", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.pending()).toEqual(0); + context.override("exp_test_ab", 0); + expect(context.variableValue("banner.size", "17")).toEqual("tiny"); + }); + + it("conflicting key disjoint audiences", () => { + const context1 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse as any); + const context2 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse as any); + + expect(context1.pending()).toEqual(0); + expect(context2.pending()).toEqual(0); + + expect(expectedVariants["exp_test_ab"]).not.toEqual(0); + expect(expectedVariants["exp_test_abc"]).not.toEqual(0); + + context1.attribute("age", 20); + expect(context1.variableValue("icon", "square")).toEqual("arrow"); + + context2.attribute("age", 19); + expect(context2.variableValue("icon", "square")).toEqual("circle"); + }); + + it("should queue exposures", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + const experiments = context.experiments()!; + + for (const [key, experimentNames] of Object.entries(variableExperiments)) { + const experimentName = experimentNames[0]; + const actual = context.variableValue(key, "17"); + const eligible = experimentName !== "exp_test_not_eligible"; + + if (eligible && experiments.indexOf(experimentName) !== -1) { + expect(actual).toEqual(expectedVariables[key]); + } else { + expect(actual).toBe("17"); + } + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 2, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_abc", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 3, + assigned: true, + eligible: false, + exposedAt: 1611141535729, + name: "exp_test_not_eligible", + overridden: false, + unit: "user_id", + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: true, + }, + { + id: 4, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_fullon", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: true, + custom: false, + audienceMismatch: true, + }, + { + id: 5, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_custom_fields", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should return defaultValue on unknown variable", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + expect(context.variableValue("not.found", "17")).toBe("17"); + }); + }); + + describe("peekVariableValue()", () => { + it("should not return variable values when unassigned", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.pending()).toEqual(0); + expect(context.peekVariableValue("banner.size", "17")).toEqual("17"); + }); + + it("should return variable values when overridden", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.pending()).toEqual(0); + context.override("exp_test_ab", 0); + expect(context.peekVariableValue("banner.size", "17")).toEqual("tiny"); + }); + + it("conflicting key disjoint audiences", () => { + const context1 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse as any); + const context2 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse as any); + + expect(context1.pending()).toEqual(0); + expect(context2.pending()).toEqual(0); + + context1.attribute("age", 20); + expect(context1.peekVariableValue("icon", "square")).toEqual("arrow"); + + context2.attribute("age", 19); + expect(context2.peekVariableValue("icon", "square")).toEqual("circle"); + + expect(context1.pending()).toEqual(0); + expect(context2.pending()).toEqual(0); + }); + + it("should pick lowest experiment id on conflicting key", () => { + const context = new Context(sdk, contextOptions, contextParams, lowestIdConflictingKeyContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(expectedVariants["exp_test_ab"]).not.toEqual(0); + expect(expectedVariants["exp_test_abc"]).not.toEqual(0); + + expect(context.peekVariableValue("icon", "square")).toEqual("circle"); + }); + + it("should not queue exposures", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + const experiments = context.experiments()!; + + for (const [key, experimentNames] of Object.entries(variableExperiments)) { + const experimentName = experimentNames[0]; + const actual = context.peekVariableValue(key, "17"); + const eligible = experimentName !== "exp_test_not_eligible"; + + if (eligible && experiments.indexOf(experimentName) !== -1) { + expect(actual).toEqual(expectedVariables[key]); + } else { + expect(actual).toBe("17"); + } + } + + expect(context.pending()).toEqual(0); + }); + }); + + describe("variableKeys()", () => { + it("should return all active keys", () => { + const context = new Context(sdk, contextOptions, contextParams, refreshContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.variableKeys()).toMatchObject(variableExperiments); + expect(context.pending()).toEqual(0); + }); + }); + + describe("track()", () => { + it("should queue goals", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.track("goal1", { amount: 125, hours: 245 }); + context.track("goal2", { tries: 7 }); + + expect(context.pending()).toEqual(2); + + context.track("goal2", { tests: 12 }); + + expect(context.pending()).toEqual(3); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + goals: [ + { achievedAt: 1611141535729, name: "goal1", properties: { amount: 125, hours: 245 } }, + { achievedAt: 1611141535729, name: "goal2", properties: { tries: 7 } }, + { achievedAt: 1611141535729, name: "goal2", properties: { tests: 12 } }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should call event logger", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + defaultEventLogger.mockClear(); + context.track("goal1", { amount: 125, hours: 245 }); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "goal", { + achievedAt: timeOrigin, + name: "goal1", + properties: { amount: 125, hours: 245 }, + }); + }); + + it("should not throw when goal property values are numbers or objects with number values", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(() => context.track("goal1", { test: { flt: 1.5, int: 2 } })).not.toThrowError(); + expect(() => context.track("goal1", { test: {} })).not.toThrowError(); + expect(() => context.track("goal1", { test: null })).not.toThrowError(); + + expect(context.pending()).toEqual(3); + }); + + it("should not throw when goal properties is null or undefined", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(() => context.track("goal1")).not.toThrowError(); + expect(() => context.track("goal1", null as any)).not.toThrowError(); + expect(() => context.track("goal1", undefined)).not.toThrowError(); + + expect(context.pending()).toEqual(3); + }); + + it("should throw when goal properties not object", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(() => context.track("goal1", 125.0 as any)).toThrowError("Goal 'goal1' properties must be of type object."); + expect(() => context.track("goal1", true as any)).toThrowError("Goal 'goal1' properties must be of type object."); + expect(() => context.track("goal1", "testy" as any)).toThrowError("Goal 'goal1' properties must be of type object."); + expect(() => context.track("goal1", [] as any)).toThrowError("Goal 'goal1' properties must be of type object."); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.track("payment", { amount: 125 })).toThrow(); + + await finalizePromise; + + expect(() => context.track("payment", { amount: 125 })).toThrow(); + }); + + it("should queue when not ready", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.pending()).toEqual(0); + expect(context.isReady()).toEqual(false); + + context.track("goal1", { amount: 125 }); + + expect(context.pending()).toEqual(1); + expect(context.isReady()).toEqual(false); + + await context.ready(); + expect(context.pending()).toEqual(1); + }); + }); + + describe("publish()", () => { + it("should not call client publish when queue is empty", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).not.toHaveBeenCalled(); + }); + + it("should propagate client error message", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.track("goal1", { amount: 125 }); + + (publisher.publish as ReturnType).mockReturnValue(Promise.reject("test")); + + await expect(context.publish()).rejects.toEqual("test"); + }); + + it("should call client publish", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + context.treatment("exp_test_not_eligible"); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 1); + context.track("goal1", { amount: 125, hours: 245 }); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 2); + context.attribute("attr1", "value1"); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 3); + context.attributes({ + attr2: "value2", + attr3: 3, + attr4: 5.0, + attr5: true, + attr6: [1, 2, 3, 4], + attr7: null, + attr8: [], + attr9: [null, 1, 2], + attr10: ["one", null, "two"], + attr11: [null, null], + }); + + expect(context.pending()).toEqual(3); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 3, + name: "exp_test_not_eligible", + unit: "user_id", + exposedAt: 1611141535729, + variant: 0, + assigned: true, + eligible: false, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: true, + }, + ], + goals: [ + { + name: "goal1", + achievedAt: 1611141535730, + properties: { amount: 125, hours: 245 }, + }, + ], + attributes: [ + { name: "attr1", setAt: 1611141535731, value: "value1" }, + { name: "attr2", setAt: 1611141535732, value: "value2" }, + { name: "attr3", setAt: 1611141535732, value: 3 }, + { name: "attr4", setAt: 1611141535732, value: 5.0 }, + { name: "attr5", setAt: 1611141535732, value: true }, + { name: "attr6", setAt: 1611141535732, value: [1, 2, 3, 4] }, + { name: "attr7", setAt: 1611141535732, value: null }, + { name: "attr8", setAt: 1611141535732, value: [] }, + { name: "attr9", setAt: 1611141535732, value: [null, 1, 2] }, + { name: "attr10", setAt: 1611141535732, value: ["one", null, "two"] }, + { name: "attr11", setAt: 1611141535732, value: [null, null] }, + ], + }, + sdk, + context, + undefined + ); + + expect(context.pending()).toEqual(0); + }); + + it("should pass through request options", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.track("goal1", { amount: 125, hours: 245 }); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish({ timeout: 1234 } as any); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + goals: [ + { + name: "goal1", + achievedAt: 1611141535729, + properties: { amount: 125, hours: 245 }, + }, + ], + }, + sdk, + context, + { timeout: 1234 } + ); + + expect(context.pending()).toEqual(0); + }); + + it("should call event logger on error", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.track("goal1", { amount: 125, hours: 245 }); + + (publisher.publish as ReturnType).mockReturnValue(Promise.reject("test error")); + + defaultEventLogger.mockClear(); + await expect(context.publish()).rejects.toEqual("test error"); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "error", "test error"); + }); + + it("should call event logger on success", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.track("goal1", { amount: 125, hours: 245 }); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + defaultEventLogger.mockClear(); + await context.publish(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "publish", { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + goals: [ + { + achievedAt: 1611141535729, + name: "goal1", + properties: { amount: 125, hours: 245 }, + }, + ], + }); + }); + + it("should not call client publish when failed", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + await context.ready(); + context.treatment("exp_test_ab"); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 1); + context.track("goal1", { amount: 125, hours: 245 }); + + expect(context.pending()).toEqual(2); + + await context.publish(); + expect(publisher.publish).not.toHaveBeenCalled(); + expect(context.pending()).toEqual(0); + }); + + it("should reset internal queues and keep attributes overrides and custom assignments", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + context.track("goal1", { amount: 125, hours: 245 }); + context.attribute("attr1", "value1"); + + context.override("not_found", 3); + expect(context.treatment("not_found")).toEqual(3); + + context.customAssignment("exp_test_abc", 3); + expect(context.treatment("exp_test_abc")).toEqual(3); + + expect(context.pending()).toEqual(4); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve({})); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 0, + name: "not_found", + unit: null, + exposedAt: 1611141535729, + variant: 3, + assigned: false, + eligible: true, + overridden: true, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 2, + name: "exp_test_abc", + unit: "session_id", + exposedAt: 1611141535729, + variant: 3, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: true, + audienceMismatch: false, + }, + ], + goals: [ + { + name: "goal1", + achievedAt: 1611141535729, + properties: { amount: 125, hours: 245 }, + }, + ], + attributes: [ + { name: "attr1", setAt: 1611141535729, value: "value1" }, + ], + }, + sdk, + context, + undefined + ); + + expect(context.pending()).toEqual(0); + + (publisher.publish as ReturnType).mockClear(); + + context.track("goal2", { test: 999 }); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + goals: [ + { + name: "goal2", + achievedAt: 1611141535829, + properties: { test: 999 }, + }, + ], + attributes: [ + { name: "attr1", setAt: 1611141535729, value: "value1" }, + ], + }, + sdk, + context, + undefined + ); + + expect(context.pending()).toEqual(0); + + expect(context.treatment("exp_test_abc")).toEqual(3); + expect(context.treatment("not_found")).toEqual(3); + + expect(context.pending()).toEqual(0); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.publish()).toThrow(); + + await finalizePromise; + + expect(() => context.publish()).toThrow(); + }); + }); + + describe("finalize()", () => { + it("should not call client publish when queue is empty", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + expect(context.isFinalizing()).toEqual(false); + + await context.finalize(); + expect(publisher.publish).not.toHaveBeenCalled(); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should propagate client error message", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.reject("test")); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await expect(finalizePromise).rejects.toEqual("test"); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + }); + + it("should call client publish", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await finalizePromise; + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + + expect(context.pending()).toEqual(0); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should pass through request options", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + const finalizePromise = context.finalize({ timeout: 1234 } as any); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await finalizePromise; + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + { timeout: 1234 } + ); + + expect(context.pending()).toEqual(0); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should call event logger on error", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.reject("test error")); + + defaultEventLogger.mockClear(); + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await expect(finalizePromise).rejects.toEqual("test error"); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "error", "test error"); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + }); + + it("should call event logger on success", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + defaultEventLogger.mockClear(); + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await finalizePromise; + expect(defaultEventLogger).toHaveBeenCalledTimes(2); + expect(defaultEventLogger).toHaveBeenLastCalledWith(context, "finalize", undefined); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should not call client publish when failed", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + await context.ready(); + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + expect(context.isFinalizing()).toEqual(false); + + await context.finalize(); + expect(publisher.publish).not.toHaveBeenCalled(); + expect(context.pending()).toEqual(0); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should return current promise when called twice", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + const firstPromise = context.finalize(); + const secondPromise = context.finalize(); + + expect(secondPromise).toBe(firstPromise); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await secondPromise; + + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should return completed promise when already finalized", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.finalize(); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + + await context.finalize(); + }); + }); + + describe("override()", () => { + it("should be callable before ready()", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + + context.override("exp_test_ab", 1); + context.overrides({ + exp_test_ab: 2, + exp_test_abc: 2, + not_found: 3, + }); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + + context.treatment("exp_test_ab"); + context.treatment("exp_test_abc"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 2, + assigned: false, + eligible: true, + overridden: true, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 2, + name: "exp_test_abc", + unit: "session_id", + exposedAt: 1611141535729, + variant: 2, + assigned: false, + eligible: true, + overridden: true, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + }); + + describe("customAssignment()", () => { + it("should override natural assignment and set custom flag", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.customAssignment("exp_test_abc", 11); + + expect(context.pending()).toEqual(0); + + context.treatment("exp_test_abc"); + + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 2, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_abc", + overridden: false, + unit: "session_id", + variant: 11, + fullOn: false, + custom: true, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should not override full-on or non-eligible assignment", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.customAssignment("exp_test_not_eligible", 11); + context.customAssignment("exp_test_fullon", 11); + + expect(context.pending()).toEqual(0); + + context.treatment("exp_test_not_eligible"); + context.treatment("exp_test_fullon"); + + expect(context.pending()).toEqual(2); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 3, + assigned: true, + eligible: false, + exposedAt: 1611141535729, + name: "exp_test_not_eligible", + overridden: false, + unit: "user_id", + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: true, + }, + { + id: 4, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_fullon", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: true, + custom: false, + audienceMismatch: true, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should be callable before ready()", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + + context.customAssignment("exp_test_ab", 1); + context.customAssignments({ + exp_test_ab: 2, + exp_test_abc: 2, + not_found: 3, + }); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + + context.treatment("exp_test_ab"); + context.treatment("exp_test_abc"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 2, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: true, + audienceMismatch: false, + }, + { + id: 2, + name: "exp_test_abc", + unit: "session_id", + exposedAt: 1611141535729, + variant: 2, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: true, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.customAssignment("exp_test_ab", 3)).toThrow(); + + await finalizePromise; + + expect(() => context.customAssignment("exp_test_ab", 3)).toThrow(); + }); + }); + + describe("customFieldKeys()", () => { + it("should return custom field keys", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + const keys = context.customFieldKeys(); + + expect(context.isReady()).toEqual(true); + expect(keys).toEqual([ + "country", + "json_object", + "json_array", + "json_number", + "json_string", + "json_boolean", + "json_null", + "json_invalid", + "languages", + "text_field", + "string_field", + "number_field", + "boolean_field", + "false_boolean_field", + "invalid_type_field", + ]); + }); + }); + + describe("customFieldValue()", () => { + it("should return custom field value", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + const value = context.customFieldValue("exp_test_custom_fields", "country"); + + expect(context.isReady()).toEqual(true); + expect(value).toEqual("US,PT,ES"); + }); + + it("should return parsed JSON fields", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_abc", "json_object")).toEqual({ 123: 1, 456: 0 }); + expect(context.customFieldValue("exp_test_abc", "json_array")).toEqual(["hello", "world"]); + expect(context.customFieldValue("exp_test_abc", "json_number")).toEqual(123); + expect(context.customFieldValue("exp_test_abc", "json_string")).toEqual("hello"); + expect(context.customFieldValue("exp_test_abc", "json_boolean")).toEqual(true); + expect(context.customFieldValue("exp_test_abc", "json_null")).toEqual(null); + }); + + it("should return string and text fields", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_custom_fields", "text_field")).toEqual("hello text"); + expect(context.customFieldValue("exp_test_custom_fields", "string_field")).toEqual("hello string"); + }); + + it("should return parsed number fields", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_custom_fields", "number_field")).toEqual(123); + }); + + it("should return parsed boolean fields", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_custom_fields", "boolean_field")).toEqual(true); + expect(context.customFieldValue("exp_test_custom_fields", "false_boolean_field")).toEqual(false); + }); + + it("should log an error when JSON cannot be parsed", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_abc", "json_invalid")).toEqual(null); + expect(defaultEventLogger).toHaveBeenCalledWith( + context, + "error", + expect.objectContaining({ + message: expect.stringContaining("Failed to parse JSON custom field value 'json_invalid' for experiment 'exp_test_abc'") + }) + ); + }); + + it("should log an error when a field type is invalid", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_custom_fields", "invalid_type_field")).toEqual(null); + expect(defaultEventLogger).toHaveBeenCalledWith( + context, + "error", + expect.objectContaining({ + message: "Unknown custom field type 'invalid' for experiment 'exp_test_custom_fields' and key 'invalid_type_field' - you may need to upgrade to the latest SDK version" + }) + ); + }); + }); + + describe("customFieldValueType()", () => { + it("should return custom field value type", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + const value = context.customFieldValueType("exp_test_custom_fields", "country"); + + expect(context.isReady()).toEqual(true); + expect(value).toEqual("string"); + }); + }); + + describe("includeSystemAttributes", () => { + it("should not include system attributes by default", async () => { + const defaultOptions = { publishDelay: -1, refreshPeriod: 0 }; + + const context = new Context(sdk, defaultOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toBeUndefined(); + }); + + it("should include system attributes when includeSystemAttributes is true", async () => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toBeDefined(); + expect(request.attributes.length).toBeGreaterThanOrEqual(4); + + const sdkNameAttr = request.attributes.find((a: any) => a.name === "sdk_name"); + const sdkVersionAttr = request.attributes.find((a: any) => a.name === "sdk_version"); + const applicationAttr = request.attributes.find((a: any) => a.name === "application"); + const environmentAttr = request.attributes.find((a: any) => a.name === "environment"); + + expect(sdkNameAttr).toBeDefined(); + expect(sdkNameAttr.value).toEqual("absmartly-javascript-sdk"); + expect(sdkNameAttr.setAt).toEqual(expect.any(Number)); + + expect(sdkVersionAttr).toBeDefined(); + expect(sdkVersionAttr.value).toEqual(SDK_VERSION); + expect(sdkVersionAttr.setAt).toEqual(expect.any(Number)); + + expect(applicationAttr).toBeDefined(); + expect(applicationAttr.value).toEqual("website"); + expect(applicationAttr.setAt).toEqual(expect.any(Number)); + + expect(environmentAttr).toBeDefined(); + expect(environmentAttr.value).toEqual("production"); + expect(environmentAttr.setAt).toEqual(expect.any(Number)); + }); + + it("should prepend system attributes before user attributes", async () => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.attribute("custom_attr", "custom_value"); + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + expect(request.attributes[0].name).toEqual("sdk_name"); + expect(request.attributes[1].name).toEqual("sdk_version"); + expect(request.attributes[2].name).toEqual("application"); + expect(request.attributes[3].name).toEqual("environment"); + expect(request.attributes[4].name).toEqual("custom_attr"); + expect(request.attributes[4].value).toEqual("custom_value"); + }); + + it("should include app_version when application version is set", async () => { + client.getApplication.mockReturnValueOnce({ name: "website", version: 3 }); + + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a: any) => a.name === "app_version"); + expect(appVersionAttr).toBeDefined(); + expect(appVersionAttr.value).toEqual(3); + }); + + it("should not include app_version when application version is 0", async () => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a: any) => a.name === "app_version"); + expect(appVersionAttr).toBeUndefined(); + }); + + it("should include app_version when application version is a semver string", async () => { + client.getApplication.mockReturnValueOnce({ name: "website", version: "1.2.3" }); + + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a: any) => a.name === "app_version"); + expect(appVersionAttr).toBeDefined(); + expect(appVersionAttr.value).toEqual("1.2.3"); + }); + + it("should only include user attributes when includeSystemAttributes is not set", async () => { + const defaultOptions = { publishDelay: -1, refreshPeriod: 0 }; + + const context = new Context(sdk, defaultOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.attribute("custom_attr", "custom_value"); + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toEqual([ + { name: "custom_attr", value: "custom_value", setAt: expect.any(Number) }, + ]); + }); + }); + + describe("publishDelay auto-flush", () => { + beforeEach(() => { + vi.useFakeTimers({ shouldAdvanceTime: false }); + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should auto-flush after publishDelay ms when treatment is called", async () => { + const options = { publishDelay: 100, refreshPeriod: 0 }; + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + expect(publisher.publish).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(100); + expect(publisher.publish).toHaveBeenCalledTimes(1); + }); + + it("should auto-flush after publishDelay ms when track is called", async () => { + const options = { publishDelay: 100, refreshPeriod: 0 }; + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + context.track("goal1", { amount: 100 }); + expect(publisher.publish).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(100); + expect(publisher.publish).toHaveBeenCalledTimes(1); + }); + + it("should not create multiple timers for multiple queued events", async () => { + const options = { publishDelay: 100, refreshPeriod: 0 }; + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + context.treatment("exp_test_abc"); + context.track("goal1"); + + await vi.advanceTimersByTimeAsync(100); + expect(publisher.publish).toHaveBeenCalledTimes(1); + }); + + it("should start auto-flush timer after ready if events queued before ready", async () => { + const options = { publishDelay: 100, refreshPeriod: 0 }; + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + let resolveData: (data: any) => void; + const dataPromise = new Promise((resolve) => { resolveData = resolve; }); + + const context = new Context(sdk, options, contextParams, dataPromise); + expect(context.isReady()).toBe(false); + + // Resolve the promise to make context ready + resolveData!(getContextResponse); + await context.ready(); + expect(context.isReady()).toBe(true); + + // Queue events after ready + context.treatment("exp_test_ab"); + + // Timer should fire after publishDelay + await vi.advanceTimersByTimeAsync(100); + expect(publisher.publish).toHaveBeenCalledTimes(1); + }); + }); + + describe("refreshPeriod auto-refresh", () => { + beforeEach(() => { + vi.useFakeTimers({ shouldAdvanceTime: false }); + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should start refresh timer when refreshPeriod > 0", async () => { + const options = { publishDelay: -1, refreshPeriod: 1000 }; + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(getContextResponse)); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + expect(context.isReady()).toBe(true); + + await vi.advanceTimersByTimeAsync(1000); + expect(provider.getContextData).toHaveBeenCalledTimes(1); + }); + + it("should call refresh multiple times on interval", async () => { + const options = { publishDelay: -1, refreshPeriod: 1000 }; + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(getContextResponse)); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + + await vi.advanceTimersByTimeAsync(3000); + expect(provider.getContextData).toHaveBeenCalledTimes(3); + }); + + it("should stop refresh timer on finalize", async () => { + const options = { publishDelay: -1, refreshPeriod: 1000 }; + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(getContextResponse)); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + + await context.finalize(); + + await vi.advanceTimersByTimeAsync(3000); + expect(provider.getContextData).not.toHaveBeenCalled(); + }); + }); + + describe("event logger resilience", () => { + it("should not crash when event logger throws on treatment", () => { + const throwingLogger = vi.fn().mockImplementation(() => { throw new Error("logger broken"); }); + const context = new Context(sdk, { ...contextOptions, eventLogger: throwingLogger }, contextParams, getContextResponse as any); + expect(() => context.treatment("exp_test_ab")).not.toThrow(); + }); + + it("should not crash when event logger throws on track", () => { + const throwingLogger = vi.fn().mockImplementation(() => { throw new Error("logger broken"); }); + const context = new Context(sdk, { ...contextOptions, eventLogger: throwingLogger }, contextParams, getContextResponse as any); + expect(() => context.track("goal1", { amount: 1 })).not.toThrow(); + }); + + it("should not crash when event logger throws on publish", async () => { + const throwingLogger = vi.fn().mockImplementation(() => { throw new Error("logger broken"); }); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + const context = new Context(sdk, { ...contextOptions, eventLogger: throwingLogger }, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + await expect(context.publish()).resolves.toBeUndefined(); + }); + }); + + describe("flush data preservation on failure", () => { + it("should preserve events on publish failure and retry", async () => { + (publisher.publish as ReturnType) + .mockReturnValueOnce(Promise.reject(new Error("network error"))) + .mockReturnValueOnce(Promise.resolve()); + + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + + // First publish fails + await expect(context.publish()).rejects.toThrow("network error"); + + // Events should still be pending + expect(context.pending()).toBe(1); + + // Second publish succeeds with the preserved events + await context.publish(); + expect(context.pending()).toBe(0); + expect(publisher.publish).toHaveBeenCalledTimes(2); + }); + + it("should not lose new events queued during in-flight publish", async () => { + let resolvePublish: () => void; + (publisher.publish as ReturnType).mockReturnValue(new Promise((resolve) => { resolvePublish = resolve; })); + + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + + const publishPromise = context.publish(); + + // Queue new event while publish is in-flight + context.track("goal1"); + expect(context.pending()).toBe(1); // new event pending + + // Resolve the first publish + resolvePublish!(); + await publishPromise; + + // New event should still be pending + expect(context.pending()).toBe(1); + }); + }); + + describe("override cache invalidation", () => { + it("should queue new exposure when override value changes", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + context.override("exp_test_ab", 2); + context.treatment("exp_test_ab"); + expect(context.pending()).toBe(1); + + // Same override value — no new exposure + context.override("exp_test_ab", 2); + context.treatment("exp_test_ab"); + expect(context.pending()).toBe(1); + + // Different override value — new exposure + context.override("exp_test_ab", 3); + context.treatment("exp_test_ab"); + expect(context.pending()).toBe(2); + }); + }); + + describe("input validation", () => { + it("treatment() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.treatment("")).toThrow("Experiment name must be a non-empty string"); + }); + + it("peek() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.peek("")).toThrow("Experiment name must be a non-empty string"); + }); + + it("track() should throw for empty goal name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.track("")).toThrow("Goal name must be a non-empty string"); + }); + + it("attribute() should throw for empty attribute name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.attribute("", "value")).toThrow("Attribute name must be a non-empty string"); + }); + + it("variableValue() should throw for empty key", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.variableValue("", "default")).toThrow("Variable key must be a non-empty string"); + }); + + it("override() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.override("", 1)).toThrow("Experiment name must be a non-empty string"); + }); + + it("override() should throw for negative variant", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.override("exp_test_ab", -1)).toThrow("Variant must be a non-negative integer"); + }); + + it("override() should throw for non-integer variant", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.override("exp_test_ab", 1.5)).toThrow("Variant must be a non-negative integer"); + }); + + it("customAssignment() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.customAssignment("", 1)).toThrow("Experiment name must be a non-empty string"); + }); + + it("customAssignment() should throw for negative variant", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.customAssignment("exp_test_ab", -1)).toThrow("Variant must be a non-negative integer"); + }); + + it("customFieldValue() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.customFieldValue("", "key")).toThrow("Experiment name must be a non-empty string"); + }); + + it("customFieldValue() should throw for empty key", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.customFieldValue("exp_test_ab", "")).toThrow("Key must be a non-empty string"); + }); + }); + + describe("readyError()", () => { + it("should return null when context succeeds", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.readyError()).toBe(null); + }); + + it("should return the error when context fails", async () => { + const error = new Error("network failure"); + const context = new Context(sdk, contextOptions, contextParams, Promise.reject(error) as any); + await context.ready(); + expect(context.readyError()).toBe(error); + }); + }); + + describe("getSDK() and getOptions()", () => { + it("getSDK() should return the sdk", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.getSDK()).toBe(sdk); + }); + + it("getOptions() should return a copy of options", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const options = context.getOptions(); + expect(options.publishDelay).toBe(contextOptions.publishDelay); + expect(options.refreshPeriod).toBe(contextOptions.refreshPeriod); + expect(options).not.toBe(contextOptions); // should be a copy + }); + }); + + describe("getUnits() defensive copy", () => { + it("should return a copy that does not mutate internal state", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const units = context.getUnits(); + units["hacked"] = "value"; + expect(context.getUnit("hacked")).toBeUndefined(); + }); + }); + + describe("peek then treatment", () => { + it("should queue exposure on treatment after peek", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + context.peek("exp_test_ab"); + expect(context.pending()).toBe(0); + context.treatment("exp_test_ab"); + expect(context.pending()).toBe(1); + }); + }); +}); diff --git a/src/__tests__/errors.test.ts b/src/__tests__/errors.test.ts new file mode 100644 index 0000000..1d74064 --- /dev/null +++ b/src/__tests__/errors.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, test } from "vitest"; +import { ABSmartlyError, AbortError, ContextFinalizedError, ContextNotReadyError, RetryError, TimeoutError } from "../errors"; + +describe("ABSmartlyError", () => { + test("has correct name and message", () => { + const error = new ABSmartlyError("test message"); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(ABSmartlyError); + expect(error.name).toBe("ABSmartlyError"); + expect(error.message).toBe("test message"); + }); +}); + +describe("ContextNotReadyError", () => { + test("has correct name, message, and extends ABSmartlyError", () => { + const error = new ContextNotReadyError(); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(ABSmartlyError); + expect(error).toBeInstanceOf(ContextNotReadyError); + expect(error.name).toBe("ContextNotReadyError"); + expect(error.message).toBe("Context is not yet ready"); + }); +}); + +describe("ContextFinalizedError", () => { + test("has correct name, message, and extends ABSmartlyError", () => { + const error = new ContextFinalizedError(); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(ABSmartlyError); + expect(error).toBeInstanceOf(ContextFinalizedError); + expect(error.name).toBe("ContextFinalizedError"); + expect(error.message).toBe("Context has been finalized"); + }); +}); + +describe("TimeoutError", () => { + test("has correct name, message, and timeout", () => { + const error = new TimeoutError(3000); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(TimeoutError); + expect(error.name).toBe("TimeoutError"); + expect(error.message).toBe("Timeout exceeded."); + expect(error.timeout).toBe(3000); + }); +}); + +describe("RetryError", () => { + test("has correct name, message, retries, and exception", () => { + const cause = new Error("connection refused"); + const error = new RetryError(5, cause, "https://example.com/api"); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(RetryError); + expect(error.name).toBe("RetryError"); + expect(error.message).toBe("Retries exhausted. URL: https://example.com/api - Last Error: connection refused"); + expect(error.retries).toBe(5); + expect(error.exception).toBe(cause); + }); +}); + +describe("AbortError", () => { + test("has correct name and default message", () => { + const error = new AbortError(); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AbortError); + expect(error.name).toBe("AbortError"); + }); + + test("accepts custom message", () => { + const error = new AbortError("user cancelled"); + expect(error.message).toBe("user cancelled"); + }); +}); diff --git a/src/__tests__/fetch-shim.test.js b/src/__tests__/fetch-shim.test.js deleted file mode 100644 index d214b7a..0000000 --- a/src/__tests__/fetch-shim.test.js +++ /dev/null @@ -1,135 +0,0 @@ -// eslint-disable-next-line no-shadow -import { fetch } from "../fetch-shim"; -// eslint-disable-next-line no-shadow -import { AbortController } from "../abort"; - -describe("fetch", () => { - it("should be a function", () => { - expect(fetch).toEqual(expect.any(Function)); - }); - - describe("fetch()", () => { - let xhr; - - beforeEach(() => { - xhr = { - setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn().mockReturnValue("X-Foo: bar\nX-Foo:baz"), - open: jest.fn(), - send: jest.fn(), - status: 200, - statusText: "OK", - responseText: '{"a":"b"}', - responseURL: "/foo?redirect", - abort: jest.fn(), - }; - - global.XMLHttpRequest = jest.fn(() => xhr); - }); - - afterEach(() => { - delete global.XMLHttpRequest; - }); - - it("sanity test", async () => { - fetch("/foo", { headers: { a: "b" } }) - .then((response) => { - expect(response).toMatchObject({ - text: expect.any(Function), - json: expect.any(Function), - blob: expect.any(Function), - clone: expect.any(Function), - headers: expect.any(Object), - }); - expect(response.clone()).not.toBe(response); - expect(response.clone().url).toEqual("/foo?redirect"); - expect(response.headers.get).toEqual(expect.any(Function)); - expect(response.headers.get("x-foo")).toEqual("bar,baz"); - return response.json(); - }) - .then((data) => { - expect(data).toEqual({ a: "b" }); - - expect(xhr.setRequestHeader).toHaveBeenCalledTimes(1); - expect(xhr.setRequestHeader).toHaveBeenCalledWith("a", "b"); - expect(xhr.open).toHaveBeenCalledTimes(1); - expect(xhr.open).toHaveBeenCalledWith("get", "/foo", true); - expect(xhr.send).toHaveBeenCalledTimes(1); - expect(xhr.send).toHaveBeenCalledWith(null); - }); - - expect(xhr.onload).toEqual(expect.any(Function)); - expect(xhr.onerror).toEqual(expect.any(Function)); - expect(xhr.onabort).toEqual(expect.any(Function)); - - xhr.onload(); - }); - - it("handles empty header values", async () => { - xhr.getAllResponseHeaders = jest.fn().mockReturnValue("Server: \nX-Foo:baz"); - fetch("/foo").then((response) => { - expect(response.headers.get("server")).toEqual(""); - expect(response.headers.get("X-foo")).toEqual("baz"); - }); - - xhr.onload(); - }); - - it("adds and removes the abort event listener", async () => { - const controller = new AbortController(); - jest.spyOn(controller.signal, "addEventListener"); - jest.spyOn(controller.signal, "removeEventListener"); - - fetch("/foo", { - signal: controller.signal, - }) - .then((response) => { - return response.json(); - }) - .then((data) => { - expect(xhr.abort).not.toHaveBeenCalled(); - expect(data).toEqual({ a: "b" }); - expect(controller.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(controller.signal.removeEventListener).toHaveBeenCalledTimes(1); - }); - - xhr.onload(); - }); - - it("adds and removes the abort event listener on abort", async () => { - const controller = new AbortController(); - - jest.spyOn(controller.signal, "addEventListener"); - jest.spyOn(controller.signal, "removeEventListener"); - - fetch("/foo", { - signal: controller.signal, - }).catch((error) => { - expect(error.name).toBe("AbortError"); - expect(xhr.abort).toHaveBeenCalledTimes(1); - expect(controller.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(controller.signal.removeEventListener).toHaveBeenCalledTimes(1); - }); - - controller.abort(); - xhr.onabort(); - }); - - it("adds and removes the abort event listener on error", async () => { - const controller = new AbortController(); - - jest.spyOn(controller.signal, "addEventListener"); - jest.spyOn(controller.signal, "removeEventListener"); - - fetch("/foo", { - signal: controller.signal, - }).catch(() => { - expect(xhr.abort).not.toHaveBeenCalled(); - expect(controller.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(controller.signal.removeEventListener).toHaveBeenCalledTimes(1); - }); - - xhr.onerror(); - }); - }); -}); diff --git a/src/__tests__/hashing.test.ts b/src/__tests__/hashing.test.ts new file mode 100644 index 0000000..f312c4f --- /dev/null +++ b/src/__tests__/hashing.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, test } from "vitest"; +import { base64UrlNoPadding, hashUnit, stringToUint8Array } from "../hashing"; + +describe("stringToUint8Array", () => { + test("encodes ASCII", () => { + const result = stringToUint8Array("abc"); + expect(Array.from(result)).toEqual([97, 98, 99]); + }); + + test("encodes multi-byte characters", () => { + const result = stringToUint8Array("\u00e9"); + expect(Array.from(result)).toEqual([0xc3, 0xa9]); + }); + + test("encodes empty string", () => { + const result = stringToUint8Array(""); + expect(result.length).toBe(0); + }); + + test("encodes 4-byte characters (emoji/surrogate pairs)", () => { + const result = stringToUint8Array("\u{1F600}"); + // U+1F600 = F0 9F 98 80 in UTF-8 + expect(Array.from(result)).toEqual([0xf0, 0x9f, 0x98, 0x80]); + }); +}); + +describe("base64UrlNoPadding", () => { + test("encodes empty", () => { + expect(base64UrlNoPadding(new Uint8Array([]))).toBe(""); + }); + + test("encodes 1 byte", () => { + expect(base64UrlNoPadding(new Uint8Array([0]))).toBe("AA"); + }); + + test("encodes 2 bytes", () => { + expect(base64UrlNoPadding(new Uint8Array([0, 0]))).toBe("AAA"); + }); + + test("encodes 3 bytes", () => { + expect(base64UrlNoPadding(new Uint8Array([0, 0, 0]))).toBe("AAAA"); + }); + + test("uses URL-safe characters (no +, /, =)", () => { + const result = base64UrlNoPadding(new Uint8Array([255, 254, 253, 252, 251, 250])); + expect(result).not.toContain("+"); + expect(result).not.toContain("/"); + expect(result).not.toContain("="); + }); +}); + +describe("hashUnit", () => { + test("hashes string unit", () => { + const result = hashUnit("test_unit"); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + test("hashes numeric unit", () => { + const result = hashUnit(12345); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + test("produces consistent results", () => { + expect(hashUnit("abc")).toBe(hashUnit("abc")); + expect(hashUnit(123)).toBe(hashUnit(123)); + }); + + test("produces different results for different inputs", () => { + expect(hashUnit("abc")).not.toBe(hashUnit("def")); + }); + + test("returns exactly 22 chars (MD5 = 16 bytes = 22 base64url chars)", () => { + expect(hashUnit("a").length).toBe(22); + expect(hashUnit("abcdefghijklmnopqrstuvwxyz").length).toBe(22); + expect(hashUnit("bleh@absmartly.com").length).toBe(22); + expect(hashUnit(123456789).length).toBe(22); + }); +}); diff --git a/src/__tests__/jsonexpr/evaluator.test.js b/src/__tests__/jsonexpr/evaluator.test.js deleted file mode 100644 index 53b1927..0000000 --- a/src/__tests__/jsonexpr/evaluator.test.js +++ /dev/null @@ -1,470 +0,0 @@ -import { Evaluator } from "../../jsonexpr/evaluator"; - -describe("Evaluator", () => { - describe("evaluate", () => { - it("should consider an array as implicit AND combinator", () => { - const and = { evaluate: jest.fn().mockReturnValue(true) }; - const or = { evaluate: jest.fn().mockReturnValue(true) }; - - const evaluator = new Evaluator({ and, or }, {}); - const args = [{ value: true }, { value: false }]; - expect(evaluator.evaluate(args)).not.toBe(null); - - expect(and.evaluate).toHaveBeenCalledTimes(1); - expect(and.evaluate).toHaveBeenCalledWith(evaluator, args); - expect(or.evaluate).not.toHaveBeenCalled(); - }); - - it("should return null if operator not found", () => { - const value = { evaluate: jest.fn().mockReturnValue(true) }; - - const evaluator = new Evaluator({ value }, {}); - expect(evaluator.evaluate({ not_found: true })).toBe(null); - - expect(value.evaluate).not.toHaveBeenCalled(); - }); - - it("should call operator evaluate will args", () => { - const value = { evaluate: jest.fn().mockReturnValue(true) }; - - const evaluator = new Evaluator({ value }, {}); - const args = [1, 2, 3]; - expect(evaluator.evaluate({ value: args })).toBe(true); - - expect(value.evaluate).toHaveBeenCalledTimes(1); - expect(value.evaluate).toHaveBeenCalledWith(evaluator, args); - }); - }); - - describe("booleanConvert()", () => { - it("should convert all types of values to boolean", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.booleanConvert({})).toBe(true); - expect(evaluator.booleanConvert([])).toBe(true); - expect(evaluator.booleanConvert(null)).toBe(false); - - expect(evaluator.booleanConvert(true)).toBe(true); - expect(evaluator.booleanConvert(1)).toBe(true); - expect(evaluator.booleanConvert(2)).toBe(true); - expect(evaluator.booleanConvert("abc")).toBe(true); - expect(evaluator.booleanConvert("1")).toBe(true); - - expect(evaluator.booleanConvert(false)).toBe(false); - expect(evaluator.booleanConvert(0)).toBe(false); - expect(evaluator.booleanConvert("")).toBe(false); - expect(evaluator.booleanConvert("0")).toBe(false); - expect(evaluator.booleanConvert("false")).toBe(false); - }); - }); - - describe("numberConvert()", () => { - it("should convert boolean, and numeric strings to number", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.numberConvert(null)).toBe(null); - expect(evaluator.numberConvert({})).toBe(null); - expect(evaluator.numberConvert([])).toBe(null); - expect(evaluator.numberConvert("")).toBe(null); - expect(evaluator.numberConvert("abc")).toBe(null); - expect(evaluator.numberConvert("x1234")).toBe(null); - - expect(evaluator.numberConvert(true)).toBe(1.0); - expect(evaluator.numberConvert(false)).toBe(0.0); - - expect(evaluator.numberConvert(-1.0)).toBe(-1.0); - expect(evaluator.numberConvert(0.0)).toBe(0.0); - expect(evaluator.numberConvert(1.0)).toBe(1.0); - expect(evaluator.numberConvert(1.5)).toBe(1.5); - expect(evaluator.numberConvert(2.0)).toBe(2.0); - expect(evaluator.numberConvert(3.0)).toBe(3.0); - - expect(evaluator.numberConvert(-1)).toBe(-1.0); - expect(evaluator.numberConvert(0)).toBe(0.0); - expect(evaluator.numberConvert(1)).toBe(1.0); - expect(evaluator.numberConvert(2)).toBe(2.0); - expect(evaluator.numberConvert(3)).toBe(3.0); - - expect(evaluator.numberConvert(0x7fffffff)).toBe(2147483647.0); - expect(evaluator.numberConvert(-0x7fffffff)).toBe(-2147483647.0); - expect(evaluator.numberConvert(Number.MAX_SAFE_INTEGER)).toBe(9007199254740991.0); - expect(evaluator.numberConvert(-Number.MAX_SAFE_INTEGER)).toBe(-9007199254740991.0); - - expect(evaluator.numberConvert("-1")).toBe(-1.0); - expect(evaluator.numberConvert("0")).toBe(0.0); - expect(evaluator.numberConvert("1")).toBe(1.0); - expect(evaluator.numberConvert("1.5")).toBe(1.5); - expect(evaluator.numberConvert("2")).toBe(2.0); - expect(evaluator.numberConvert("3.0")).toBe(3.0); - }); - }); - - describe("stringConvert()", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.stringConvert(null)).toBe(null); - expect(evaluator.stringConvert({})).toBe(null); - expect(evaluator.stringConvert([])).toBe(null); - - expect(evaluator.stringConvert(true)).toBe("true"); - expect(evaluator.stringConvert(false)).toBe("false"); - - expect(evaluator.stringConvert("")).toBe(""); - expect(evaluator.stringConvert("abc")).toBe("abc"); - - expect(evaluator.stringConvert(-1.0)).toBe("-1"); - expect(evaluator.stringConvert(0.0)).toBe("0"); - expect(evaluator.stringConvert(1.0)).toBe("1"); - expect(evaluator.stringConvert(2.0)).toBe("2"); - expect(evaluator.stringConvert(3.0)).toBe("3"); - expect(evaluator.stringConvert(2147483647.0)).toBe("2147483647"); - expect(evaluator.stringConvert(-2147483647.0)).toBe("-2147483647"); - expect(evaluator.stringConvert(9007199254740991)).toBe("9007199254740991"); - expect(evaluator.stringConvert(-9007199254740991)).toBe("-9007199254740991"); - expect(evaluator.stringConvert(0.9007199254740991)).toBe("0.900719925474099"); - expect(evaluator.stringConvert(-0.9007199254740991)).toBe("-0.900719925474099"); - expect(evaluator.stringConvert(-1)).toBe("-1"); - expect(evaluator.stringConvert(0)).toBe("0"); - expect(evaluator.stringConvert(1)).toBe("1"); - expect(evaluator.stringConvert(2)).toBe("2"); - expect(evaluator.stringConvert(3)).toBe("3"); - expect(evaluator.stringConvert(2147483647)).toBe("2147483647"); - expect(evaluator.stringConvert(-2147483647)).toBe("-2147483647"); - expect(evaluator.stringConvert(9007199254740991)).toBe("9007199254740991"); - expect(evaluator.stringConvert(-9007199254740991)).toBe("-9007199254740991"); - }); - - describe("extractVar()", () => { - it("should find data by paths delimited by /", () => { - const vars = { - a: 1, - b: true, - c: false, - d: [1, 2, 3], - e: [1, { z: 2 }, 3], - f: { y: { x: 3, 0: 10 } }, - }; - - const evaluator = new Evaluator({}, vars); - - expect(evaluator.extractVar("a")).toBe(1); - expect(evaluator.extractVar("b")).toBe(true); - expect(evaluator.extractVar("c")).toBe(false); - expect(evaluator.extractVar("d")).toEqual([1, 2, 3]); - expect(evaluator.extractVar("e")).toEqual([1, { z: 2 }, 3]); - expect(evaluator.extractVar("f")).toEqual({ y: { x: 3, 0: 10 } }); - - expect(evaluator.extractVar("a/0")).toBe(null); - expect(evaluator.extractVar("a/b")).toBe(null); - expect(evaluator.extractVar("b/0")).toBe(null); - expect(evaluator.extractVar("b/e")).toBe(null); - - expect(evaluator.extractVar("d/0")).toBe(1); - expect(evaluator.extractVar("d/1")).toBe(2); - expect(evaluator.extractVar("d/2")).toBe(3); - expect(evaluator.extractVar("d/3")).toBe(null); - - expect(evaluator.extractVar("e/0")).toBe(1); - expect(evaluator.extractVar("e/1/z")).toBe(2); - expect(evaluator.extractVar("e/2")).toBe(3); - expect(evaluator.extractVar("e/1/0")).toBe(null); - - expect(evaluator.extractVar("f/y")).toMatchObject({ x: 3 }); - expect(evaluator.extractVar("f/y/x")).toBe(3); - expect(evaluator.extractVar("f/y/0")).toBe(10); - }); - }); - - describe("compare()", () => { - it("should return null if comparing non-null with null", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare(null, null)).toBe(0); - - expect(evaluator.compare(null, 0)).toBe(null); - expect(evaluator.compare(null, 1)).toBe(null); - expect(evaluator.compare(null, true)).toBe(null); - expect(evaluator.compare(null, false)).toBe(null); - expect(evaluator.compare(null, "")).toBe(null); - expect(evaluator.compare(null, "abc")).toBe(null); - expect(evaluator.compare(null, {})).toBe(null); - expect(evaluator.compare(null, [])).toBe(null); - - expect(evaluator.compare(0, null)).toBe(null); - expect(evaluator.compare(1, null)).toBe(null); - expect(evaluator.compare(true, null)).toBe(null); - expect(evaluator.compare(false, null)).toBe(null); - expect(evaluator.compare("", null)).toBe(null); - expect(evaluator.compare("abc", null)).toBe(null); - expect(evaluator.compare({}, null)).toBe(null); - expect(evaluator.compare([], null)).toBe(null); - }); - - it("should return null if comparing non-object with object", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare({}, 0)).toBe(null); - expect(evaluator.compare({}, 1)).toBe(null); - expect(evaluator.compare({}, true)).toBe(null); - expect(evaluator.compare({}, false)).toBe(null); - expect(evaluator.compare({}, "")).toBe(null); - expect(evaluator.compare({}, "abc")).toBe(null); - expect(evaluator.compare({}, {})).toBe(0); - expect(evaluator.compare({ a: 1 }, { a: 1 })).toBe(0); - expect(evaluator.compare({ a: 1 }, { b: 2 })).toBe(null); - expect(evaluator.compare({}, [])).toBe(null); - - expect(evaluator.compare([], 0)).toBe(null); - expect(evaluator.compare([], 1)).toBe(null); - expect(evaluator.compare([], true)).toBe(null); - expect(evaluator.compare([], false)).toBe(null); - expect(evaluator.compare([], "")).toBe(null); - expect(evaluator.compare([], "abc")).toBe(null); - expect(evaluator.compare([], {})).toBe(null); - expect(evaluator.compare([], [])).toBe(0); - expect(evaluator.compare([1, 2], [1, 2])).toBe(0); - expect(evaluator.compare([1, 2], [3, 4])).toBe(null); - }); - - it("should coerce right-side argument to boolean and compare", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare(false, 0)).toBe(0); - expect(evaluator.compare(false, 1)).toBe(-1); - expect(evaluator.compare(false, true)).toBe(-1); - expect(evaluator.compare(false, false)).toBe(0); - expect(evaluator.compare(false, "")).toBe(0); - expect(evaluator.compare(false, "abc")).toBe(-1); - expect(evaluator.compare(false, {})).toBe(-1); - expect(evaluator.compare(false, [])).toBe(-1); - - expect(evaluator.compare(true, 0)).toBe(1); - expect(evaluator.compare(true, 1)).toBe(0); - expect(evaluator.compare(true, true)).toBe(0); - expect(evaluator.compare(true, false)).toBe(1); - expect(evaluator.compare(true, "")).toBe(1); - expect(evaluator.compare(true, "abc")).toBe(0); - expect(evaluator.compare(true, {})).toBe(0); - expect(evaluator.compare(true, [])).toBe(0); - }); - - it("should coerce right-side argument to boolean and compare", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare(0, 0)).toBe(0); - expect(evaluator.compare(0, 1)).toBe(-1); - expect(evaluator.compare(0, true)).toBe(-1); - expect(evaluator.compare(0, false)).toBe(0); - expect(evaluator.compare(0, "")).toBe(null); - expect(evaluator.compare(0, "abc")).toBe(null); - expect(evaluator.compare(0, {})).toBe(null); - expect(evaluator.compare(0, [])).toBe(null); - - expect(evaluator.compare(1, 0)).toBe(1); - expect(evaluator.compare(1, 1)).toBe(0); - expect(evaluator.compare(1, true)).toBe(0); - expect(evaluator.compare(1, false)).toBe(1); - expect(evaluator.compare(1, "")).toBe(null); - expect(evaluator.compare(1, "abc")).toBe(null); - expect(evaluator.compare(1, {})).toBe(null); - expect(evaluator.compare(1, [])).toBe(null); - - expect(evaluator.compare(1.0, 1)).toBe(0); - expect(evaluator.compare(1.5, 1)).toBe(1); - expect(evaluator.compare(2.0, 1)).toBe(1); - expect(evaluator.compare(3.0, 1)).toBe(1); - - expect(evaluator.compare(1, 1.0)).toBe(0); - expect(evaluator.compare(1, 1.5)).toBe(-1); - expect(evaluator.compare(1, 2.0)).toBe(-1); - expect(evaluator.compare(1, 3.0)).toBe(-1); - - expect(evaluator.compare(9007199254740991, 9007199254740991)).toBe(0); - expect(evaluator.compare(0, 9007199254740991)).toBe(-1); - expect(evaluator.compare(9007199254740991, 0)).toBe(1); - - expect(evaluator.compare(9007199254740991.0, 9007199254740991.0)).toBe(0); - expect(evaluator.compare(0, 9007199254740991.0)).toBe(-1); - expect(evaluator.compare(9007199254740991.0, 0)).toBe(1); - }); - - it("should coerce right-hand side argument to string and compare", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare("", "")).toBe(0); - expect(evaluator.compare("abc", "abc")).toBe(0); - expect(evaluator.compare("0", 0)).toBe(0); - expect(evaluator.compare("1", 1)).toBe(0); - expect(evaluator.compare("true", true)).toBe(0); - expect(evaluator.compare("false", false)).toBe(0); - expect(evaluator.compare("", {})).toBe(null); - expect(evaluator.compare("abc", {})).toBe(null); - expect(evaluator.compare("", [])).toBe(null); - expect(evaluator.compare("abc", [])).toBe(null); - - expect(evaluator.compare("abc", "bcd")).toBe(-1); - expect(evaluator.compare("bcd", "abc")).toBe(1); - expect(evaluator.compare("0", "1")).toBe(-1); - expect(evaluator.compare("1", "0")).toBe(1); - expect(evaluator.compare("9", "100")).toBe(1); - expect(evaluator.compare("100", "9")).toBe(-1); - }); - }); - - describe("versionCompare()", () => { - it("should return 0 for equal versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("0.0.0", "0.0.0")).toBe(0); - expect(evaluator.versionCompare("999.999.999", "999.999.999")).toBe(0); - }); - - it("should compare major versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("2.0.0", "1.0.0")).toBe(1); - expect(evaluator.versionCompare("1.0.0", "2.0.0")).toBe(-1); - }); - - it("should compare minor versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.2.0", "1.1.0")).toBe(1); - expect(evaluator.versionCompare("1.1.0", "1.2.0")).toBe(-1); - }); - - it("should compare patch versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.2", "1.0.1")).toBe(1); - expect(evaluator.versionCompare("1.0.1", "1.0.2")).toBe(-1); - }); - - it("should compare numerically not lexicographically", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.10.0", "1.9.0")).toBe(1); - expect(evaluator.versionCompare("1.9.0", "1.10.0")).toBe(-1); - expect(evaluator.versionCompare("10.0.0", "9.0.0")).toBe(1); - }); - - it("should treat missing parts as 0", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.2", "1.2.0")).toBe(0); - expect(evaluator.versionCompare("1", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("1.2.0", "1.2")).toBe(0); - expect(evaluator.versionCompare("1.0.0", "1")).toBe(0); - }); - - it("should handle leading zeros in version parts", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.02.0", "1.2.0")).toBe(0); - expect(evaluator.versionCompare("1.002.030", "1.2.30")).toBe(0); - expect(evaluator.versionCompare("01.0.0", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("1.02.0", "1.3.0")).toBe(-1); - }); - - it("should handle pre-release versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0", "1.0.0-alpha")).toBe(1); - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-alpha")).toBe(0); - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-beta")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-beta", "1.0.0-alpha")).toBe(1); - expect(evaluator.versionCompare("1.0.0-alpha.1", "1.0.0-alpha.2")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha.2", "1.0.0-alpha.1")).toBe(1); - expect(evaluator.versionCompare("1.0.0-1", "1.0.0-2")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-2", "1.0.0-1")).toBe(1); - }); - - it("should compare numeric pre-release identifiers as less than string identifiers", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0-1", "1.0.0-alpha")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-1")).toBe(1); - }); - - it("should compare pre-release with fewer identifiers as less", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-alpha.1")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha.1", "1.0.0-alpha")).toBe(1); - }); - - it("should strip v prefix", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("v1.0.0", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("V1.0.0", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("v1.0.0", "V1.0.0")).toBe(0); - }); - - it("should return null for null inputs", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare(null, "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", null)).toBe(null); - expect(evaluator.versionCompare(null, null)).toBe(null); - }); - - it("should coerce non-string inputs via stringConvert", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare(1, "1.0.0")).toBe(0); - expect(evaluator.versionCompare("1.0.0", 1)).toBe(0); - expect(evaluator.versionCompare(true, "true")).toBe(0); - }); - - it("should return null for unconvertible inputs", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare({}, "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", {})).toBe(null); - expect(evaluator.versionCompare([], "1.0.0")).toBe(null); - }); - - it("should ignore build metadata", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0+build1", "1.0.0+build2")).toBe(0); - expect(evaluator.versionCompare("1.0.0+build1", "1.0.0")).toBe(0); - }); - - it("should handle pre-release combined with build metadata", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0-alpha+build2")).toBe(0); - expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0-beta")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0")).toBe(-1); - }); - - it("should return null for empty string inputs", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", "")).toBe(null); - expect(evaluator.versionCompare("", "")).toBe(null); - }); - - it("should return null for undefined inputs", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare(undefined, "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", undefined)).toBe(null); - }); - - it("should return null for inputs that normalize to empty core", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("v", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("V", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("+build", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("v+build", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", "v")).toBe(null); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/evaluator.test.ts b/src/__tests__/jsonexpr/evaluator.test.ts new file mode 100644 index 0000000..0122340 --- /dev/null +++ b/src/__tests__/jsonexpr/evaluator.test.ts @@ -0,0 +1,167 @@ +import { describe, expect, test } from "vitest"; +import { Evaluator } from "../../jsonexpr/evaluator"; + +function createEvaluator(vars: Record = {}) { + return new Evaluator({}, vars); +} + +describe("Evaluator", () => { + describe("booleanConvert", () => { + const evaluator = createEvaluator(); + + test("boolean values", () => { + expect(evaluator.booleanConvert(true)).toBe(true); + expect(evaluator.booleanConvert(false)).toBe(false); + }); + + test("number values", () => { + expect(evaluator.booleanConvert(1)).toBe(true); + expect(evaluator.booleanConvert(0)).toBe(false); + expect(evaluator.booleanConvert(-1)).toBe(true); + }); + + test("string values", () => { + expect(evaluator.booleanConvert("true")).toBe(true); + expect(evaluator.booleanConvert("false")).toBe(false); + expect(evaluator.booleanConvert("0")).toBe(false); + expect(evaluator.booleanConvert("")).toBe(false); + expect(evaluator.booleanConvert("abc")).toBe(true); + }); + + test("null/undefined", () => { + expect(evaluator.booleanConvert(null)).toBe(false); + expect(evaluator.booleanConvert(undefined)).toBe(false); + }); + }); + + describe("numberConvert", () => { + const evaluator = createEvaluator(); + + test("number values", () => { + expect(evaluator.numberConvert(42)).toBe(42); + expect(evaluator.numberConvert(0)).toBe(0); + expect(evaluator.numberConvert(-1.5)).toBe(-1.5); + }); + + test("boolean values", () => { + expect(evaluator.numberConvert(true)).toBe(1); + expect(evaluator.numberConvert(false)).toBe(0); + }); + + test("string values", () => { + expect(evaluator.numberConvert("42")).toBe(42); + expect(evaluator.numberConvert("3.14")).toBe(3.14); + expect(evaluator.numberConvert("abc")).toBe(null); + }); + + test("other types", () => { + expect(evaluator.numberConvert(null)).toBe(null); + expect(evaluator.numberConvert({})).toBe(null); + }); + }); + + describe("stringConvert", () => { + const evaluator = createEvaluator(); + + test("string values", () => { + expect(evaluator.stringConvert("hello")).toBe("hello"); + }); + + test("boolean values", () => { + expect(evaluator.stringConvert(true)).toBe("true"); + expect(evaluator.stringConvert(false)).toBe("false"); + }); + + test("number values", () => { + expect(evaluator.stringConvert(42)).toBe("42"); + expect(evaluator.stringConvert(0)).toBe("0"); + }); + + test("other types", () => { + expect(evaluator.stringConvert(null)).toBe(null); + expect(evaluator.stringConvert({})).toBe(null); + }); + }); + + describe("extractVar", () => { + test("extracts top-level variable", () => { + const evaluator = createEvaluator({ name: "John" }); + expect(evaluator.extractVar("name")).toBe("John"); + }); + + test("extracts nested variable", () => { + const evaluator = createEvaluator({ user: { name: "John" } }); + expect(evaluator.extractVar("user/name")).toBe("John"); + }); + + test("returns null for missing path", () => { + const evaluator = createEvaluator({ name: "John" }); + expect(evaluator.extractVar("missing")).toBe(null); + }); + }); + + describe("compare", () => { + const evaluator = createEvaluator(); + + test("numbers", () => { + expect(evaluator.compare(1, 2)).toBe(-1); + expect(evaluator.compare(2, 1)).toBe(1); + expect(evaluator.compare(1, 1)).toBe(0); + }); + + test("strings", () => { + expect(evaluator.compare("a", "b")).toBe(-1); + expect(evaluator.compare("b", "a")).toBe(1); + expect(evaluator.compare("a", "a")).toBe(0); + }); + + test("booleans", () => { + expect(evaluator.compare(true, true)).toBe(0); + expect(evaluator.compare(false, false)).toBe(0); + }); + + test("null handling", () => { + expect(evaluator.compare(null, null)).toBe(0); + expect(evaluator.compare(null, 1)).toBe(null); + expect(evaluator.compare(1, null)).toBe(null); + }); + }); + + describe("versionCompare", () => { + const evaluator = createEvaluator(); + + test("equal versions", () => { + expect(evaluator.versionCompare("1.0.0", "1.0.0")).toBe(0); + }); + + test("greater version", () => { + expect(evaluator.versionCompare("2.0.0", "1.0.0")).toBe(1); + }); + + test("lesser version", () => { + expect(evaluator.versionCompare("1.0.0", "2.0.0")).toBe(-1); + }); + + test("prerelease is less than release", () => { + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0")).toBe(-1); + }); + + test("v prefix", () => { + expect(evaluator.versionCompare("v1.0.0", "1.0.0")).toBe(0); + }); + + test("build metadata ignored", () => { + expect(evaluator.versionCompare("1.0.0+build1", "1.0.0+build2")).toBe(0); + }); + + test("null inputs", () => { + expect(evaluator.versionCompare(null, "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", null)).toBe(null); + }); + + test("empty inputs", () => { + expect(evaluator.versionCompare("", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", "")).toBe(null); + }); + }); +}); diff --git a/src/__tests__/jsonexpr/jsonexpr.test.js b/src/__tests__/jsonexpr/jsonexpr.test.js deleted file mode 100644 index b8bd3cf..0000000 --- a/src/__tests__/jsonexpr/jsonexpr.test.js +++ /dev/null @@ -1,69 +0,0 @@ -import { JsonExpr } from "../../jsonexpr/jsonexpr"; - -describe("jsonexpr", () => { - const valueFor = (x) => ({ value: x }); - const varFor = (p) => ({ var: { path: p } }); - const unaryOp = (op, arg) => ({ [op]: arg }); - const binaryOp = (op, arg0, arg1) => ({ [op]: [arg0, arg1] }); - - const jsonExpr = new JsonExpr(); - - const John = { age: 20, language: "en-US", returning: false }; - const Terry = { age: 20, language: "en-GB", returning: true }; - const Kate = { age: 50, language: "es-ES", returning: false }; - const Maria = { age: 52, language: "pt-PT", returning: true }; - - const AgeTwentyAndUS = [ - binaryOp("eq", varFor("age"), valueFor(20)), - binaryOp("eq", varFor("language"), valueFor("en-US")), - ]; - const AgeOverFifty = [binaryOp("gte", varFor("age"), valueFor(50))]; - const AgeTwentyAndUS_Or_AgeOverFifty = [{ or: [AgeTwentyAndUS, AgeOverFifty] }]; - const Returning = [binaryOp("eq", varFor("returning"), valueFor(true))]; - const Returning_And_AgeTwentyAndUS_Or_AgeOverFifty = [Returning, AgeTwentyAndUS_Or_AgeOverFifty]; - const NotReturning_And_Spanish = [unaryOp("not", Returning), binaryOp("eq", varFor("language"), valueFor("es-ES"))]; - - describe("evaluateBooleanExpr()", () => { - test("AgeTwentyAndUS", () => { - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS, John)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS, Kate)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS, Maria)).toEqual(false); - }); - - test("AgeOverFifty", () => { - expect(jsonExpr.evaluateBooleanExpr(AgeOverFifty, John)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeOverFifty, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeOverFifty, Kate)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(AgeOverFifty, Maria)).toEqual(true); - }); - - test("AgeTwentyAndUS_Or_AgeOverFifty", () => { - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS_Or_AgeOverFifty, John)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS_Or_AgeOverFifty, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS_Or_AgeOverFifty, Kate)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS_Or_AgeOverFifty, Maria)).toEqual(true); - }); - - test("Returning", () => { - expect(jsonExpr.evaluateBooleanExpr(Returning, John)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning, Terry)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(Returning, Kate)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning, Maria)).toEqual(true); - }); - - test("Returning_And_AgeTwentyAndUS_Or_AgeOverFifty", () => { - expect(jsonExpr.evaluateBooleanExpr(Returning_And_AgeTwentyAndUS_Or_AgeOverFifty, John)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning_And_AgeTwentyAndUS_Or_AgeOverFifty, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning_And_AgeTwentyAndUS_Or_AgeOverFifty, Kate)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning_And_AgeTwentyAndUS_Or_AgeOverFifty, Maria)).toEqual(true); - }); - - test("NotReturning_And_Spanish", () => { - expect(jsonExpr.evaluateBooleanExpr(NotReturning_And_Spanish, John)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(NotReturning_And_Spanish, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(NotReturning_And_Spanish, Kate)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(NotReturning_And_Spanish, Maria)).toEqual(false); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/jsonexpr.test.ts b/src/__tests__/jsonexpr/jsonexpr.test.ts new file mode 100644 index 0000000..95ff21a --- /dev/null +++ b/src/__tests__/jsonexpr/jsonexpr.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, test } from "vitest"; +import { JsonExpr } from "../../jsonexpr/jsonexpr"; + +describe("JsonExpr", () => { + const jsonExpr = new JsonExpr(); + + test("evaluateBooleanExpr with and-array", () => { + expect(jsonExpr.evaluateBooleanExpr([{ value: true }, { value: 1 }], {})).toBe(true); + expect(jsonExpr.evaluateBooleanExpr([{ value: true }, { value: false }], {})).toBe(false); + }); + + test("evaluateBooleanExpr with object expr", () => { + expect(jsonExpr.evaluateBooleanExpr({ value: true }, {})).toBe(true); + expect(jsonExpr.evaluateBooleanExpr({ value: false }, {})).toBe(false); + }); + + test("evaluateExpr returns raw value", () => { + expect(jsonExpr.evaluateExpr({ value: 42 }, {})).toBe(42); + expect(jsonExpr.evaluateExpr({ value: "hello" }, {})).toBe("hello"); + }); + + test("var operator extracts from vars", () => { + expect(jsonExpr.evaluateExpr({ var: "name" }, { name: "Alice" })).toBe("Alice"); + }); + + test("complex expression with eq and var", () => { + const expr = { eq: [{ var: "age" }, { value: 25 }] }; + expect(jsonExpr.evaluateBooleanExpr(expr, { age: 25 })).toBe(true); + expect(jsonExpr.evaluateBooleanExpr(expr, { age: 30 })).toBe(false); + }); +}); diff --git a/src/__tests__/jsonexpr/operators/and.test.js b/src/__tests__/jsonexpr/operators/and.test.js deleted file mode 100644 index 6b8dfd5..0000000 --- a/src/__tests__/jsonexpr/operators/and.test.js +++ /dev/null @@ -1,54 +0,0 @@ -import { AndCombinator } from "../../../jsonexpr/operators/and"; -import { mockEvaluator } from "./evaluator"; - -describe("AndCombinator", () => { - const combinator = new AndCombinator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if all arguments evaluate to true", () => { - expect(combinator.evaluate(evaluator, [true])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(true); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(true); - }); - - it("should return false if any argument evaluates to false", () => { - expect(combinator.evaluate(evaluator, [false])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(false); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(false); - }); - - it("should return false if any argument evaluates to null", () => { - expect(combinator.evaluate(evaluator, [null])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(null); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(null); - }); - - it("should short-circuit and not evaluate unnecessary expressions", () => { - expect(combinator.evaluate(evaluator, [true, false, true])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, true); - expect(evaluator.booleanConvert).toHaveBeenNthCalledWith(1, true); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, false); - expect(evaluator.booleanConvert).toHaveBeenNthCalledWith(2, false); - }); - - it("should combine multiple arguments", () => { - expect(combinator.evaluate(evaluator, [true, true])).toBe(true); - expect(combinator.evaluate(evaluator, [true, true, true])).toBe(true); - - expect(combinator.evaluate(evaluator, [true, false])).toBe(false); - expect(combinator.evaluate(evaluator, [false, true])).toBe(false); - expect(combinator.evaluate(evaluator, [false, false])).toBe(false); - expect(combinator.evaluate(evaluator, [false, false, false])).toBe(false); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/eq.test.js b/src/__tests__/jsonexpr/operators/eq.test.js deleted file mode 100644 index d7132af..0000000 --- a/src/__tests__/jsonexpr/operators/eq.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { EqualsOperator } from "../../../jsonexpr/operators/eq"; - -describe("EqOperator", () => { - const operator = new EqualsOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when arguments are equal and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect( - operator.evaluate(evaluator, [ - [1, 2], - [1, 2], - ]) - ).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, [1, 2]); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, [1, 2]); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith([1, 2], [1, 2]); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect( - operator.evaluate(evaluator, [ - [1, 2], - [3, 4], - ]) - ).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, [1, 2]); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, [3, 4]); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith([1, 2], [3, 4]); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect( - operator.evaluate(evaluator, [ - { a: 1, b: 2 }, - { a: 1, b: 2 }, - ]) - ).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, { a: 1, b: 2 }); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, { a: 1, b: 2 }); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith({ a: 1, b: 2 }, { a: 1, b: 2 }); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect( - operator.evaluate(evaluator, [ - { a: 1, b: 2 }, - { a: 3, b: 4 }, - ]) - ).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, { a: 1, b: 2 }); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, { a: 3, b: 4 }); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith({ a: 1, b: 2 }, { a: 3, b: 4 }); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/evaluator.js b/src/__tests__/jsonexpr/operators/evaluator.js deleted file mode 100644 index 2c67b58..0000000 --- a/src/__tests__/jsonexpr/operators/evaluator.js +++ /dev/null @@ -1,62 +0,0 @@ -import { isEqualsDeep, isObject } from "../../../utils"; - -export function mockEvaluator() { - return { - evaluate: jest.fn((expr) => { - return expr; - }), - - booleanConvert: jest.fn((expr) => { - return expr; - }), - - numberConvert: jest.fn((expr) => { - return expr; - }), - - stringConvert: jest.fn((expr) => { - return expr; - }), - - versionCompare: jest.fn((lhs, rhs) => { - const lhsStr = typeof lhs === "string" ? lhs : null; - const rhsStr = typeof rhs === "string" ? rhs : null; - if (lhsStr === null || rhsStr === null) return null; - const lParts = lhsStr.split(".").map(Number); - const rParts = rhsStr.split(".").map(Number); - const len = Math.max(lParts.length, rParts.length); - for (let i = 0; i < len; i++) { - const l = lParts[i] || 0; - const r = rParts[i] || 0; - if (l !== r) return l > r ? 1 : -1; - } - return 0; - }), - - compare: jest.fn((lhs, rhs) => { - switch (typeof lhs) { - case "boolean": - case "number": - case "string": - return lhs === rhs ? 0 : lhs > rhs ? 1 : -1; - default: - if (isObject(lhs) || Array.isArray(lhs)) { - if (isEqualsDeep(lhs, rhs)) { - return 0; - } - } - break; - } - return null; - }), - - extractVar: jest.fn((path) => { - switch (path) { - case "a/b/c": - return "abc"; - default: - return null; - } - }), - }; -} diff --git a/src/__tests__/jsonexpr/operators/gt.test.js b/src/__tests__/jsonexpr/operators/gt.test.js deleted file mode 100644 index e134f72..0000000 --- a/src/__tests__/jsonexpr/operators/gt.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { GreaterThanOperator } from "../../../jsonexpr/operators/gt"; - -describe("GreaterThanOperator", () => { - const operator = new GreaterThanOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when left-side argument is greater and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/gte.test.js b/src/__tests__/jsonexpr/operators/gte.test.js deleted file mode 100644 index 4adbbea..0000000 --- a/src/__tests__/jsonexpr/operators/gte.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { GreaterThanOrEqualOperator } from "../../../jsonexpr/operators/gte"; - -describe("GreaterThanOrEqualOperator", () => { - const operator = new GreaterThanOrEqualOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when left-side argument is greater or equal and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/in.test.js b/src/__tests__/jsonexpr/operators/in.test.js deleted file mode 100644 index 27fcef5..0000000 --- a/src/__tests__/jsonexpr/operators/in.test.js +++ /dev/null @@ -1,178 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { InOperator } from "../../../jsonexpr/operators/in"; - -describe("InOperator", () => { - const operator = new InOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if string contains needle", () => { - expect(operator.evaluate(evaluator, ["abcdefghijk", "abc"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "def"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "xxx"])).toBe(false); - - expect(operator.evaluate(evaluator, ["abcdefghijk", null])).toBe(null); - expect(operator.evaluate(evaluator, [null, "abc"])).toBe(null); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(9); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, "abcdefghijk"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "abc"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(3, "abcdefghijk"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(4, "def"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(5, "abcdefghijk"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(6, "xxx"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(7, "abcdefghijk"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(8, null); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(9, null); - - expect(evaluator.stringConvert).toHaveBeenCalledTimes(3); - expect(evaluator.stringConvert).toHaveBeenNthCalledWith(1, "abc"); - expect(evaluator.stringConvert).toHaveBeenNthCalledWith(2, "def"); - expect(evaluator.stringConvert).toHaveBeenNthCalledWith(3, "xxx"); - }); - - it("should return false with empty array", () => { - expect(operator.evaluate(evaluator, [[], 1])).toBe(false); - expect(operator.evaluate(evaluator, [[], "1"])).toBe(false); - expect(operator.evaluate(evaluator, [[], true])).toBe(false); - expect(operator.evaluate(evaluator, [[], false])).toBe(false); - expect(operator.evaluate(evaluator, [[], null])).toBe(null); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(10); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(3, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(4, "1"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(5, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(6, true); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(7, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(8, false); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(9, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(10, null); - - expect(evaluator.booleanConvert).not.toHaveBeenCalled(); - expect(evaluator.numberConvert).not.toHaveBeenCalled(); - expect(evaluator.stringConvert).not.toHaveBeenCalled(); - expect(evaluator.compare).not.toHaveBeenCalled(); - }); - - it("should compare array elements as left-side and needle as right-side", () => { - const haystack01 = [0, 1]; - const haystack12 = [1, 2]; - - expect(operator.evaluate(evaluator, [haystack01, 2])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystack01); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 2); - expect(evaluator.compare).toHaveBeenCalledTimes(2); - expect(evaluator.compare).toHaveBeenNthCalledWith(1, 0, 2); - expect(evaluator.compare).toHaveBeenNthCalledWith(2, 1, 2); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [haystack12, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystack12); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(2); - expect(evaluator.compare).toHaveBeenNthCalledWith(1, 1, 0); - expect(evaluator.compare).toHaveBeenNthCalledWith(2, 2, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [haystack12, 1])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystack12); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenNthCalledWith(1, 1, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [haystack12, 2])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystack12); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 2); - expect(evaluator.compare).toHaveBeenCalledTimes(2); - expect(evaluator.compare).toHaveBeenNthCalledWith(1, 1, 2); - expect(evaluator.compare).toHaveBeenNthCalledWith(2, 2, 2); - }); - - it("should return true if object contains key", () => { - const haystackab = { a: 1, b: 2 }; - const haystackbc = { b: 2, c: 3, 0: 100 }; - - expect(operator.evaluate(evaluator, [haystackab, "c"])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackab); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "c"); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith("c"); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - - expect(operator.evaluate(evaluator, [haystackbc, "a"])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackbc); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "a"); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith("a"); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - - expect(operator.evaluate(evaluator, [haystackbc, "b"])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackbc); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "b"); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith("b"); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - - expect(operator.evaluate(evaluator, [haystackbc, "c"])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackbc); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "c"); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith("c"); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - - expect(operator.evaluate(evaluator, [haystackbc, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackbc); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith(0); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - }); - }); -}); - -/* - assertTrue((Boolean) operator.evaluate(evaluator, listOf(haystackbc, "b"))); - verify(evaluator, times(2)).evaluate(any()); - verify(evaluator, times(1)).evaluate(haystackbc); - verify(evaluator, times(1)).stringConvert(any()); - verify(evaluator, times(1)).stringConvert("b"); - verify(evaluator, times(1)).evaluate("b"); - Mockito.clearInvocations(evaluator); - - assertTrue((Boolean) operator.evaluate(evaluator, listOf(haystackbc, "c"))); - verify(evaluator, times(2)).evaluate(any()); - verify(evaluator, times(1)).evaluate(haystackbc); - verify(evaluator, times(1)).stringConvert(any()); - verify(evaluator, times(1)).stringConvert("c"); - verify(evaluator, times(1)).evaluate("c"); - Mockito.clearInvocations(evaluator); - */ diff --git a/src/__tests__/jsonexpr/operators/lt.test.js b/src/__tests__/jsonexpr/operators/lt.test.js deleted file mode 100644 index fac955e..0000000 --- a/src/__tests__/jsonexpr/operators/lt.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { LessThanOperator } from "../../../jsonexpr/operators/lt"; - -describe("LessThanOperator", () => { - const operator = new LessThanOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when left-side argument is less and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/lte.test.js b/src/__tests__/jsonexpr/operators/lte.test.js deleted file mode 100644 index bb59afc..0000000 --- a/src/__tests__/jsonexpr/operators/lte.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { LessThanOrEqualOperator } from "../../../jsonexpr/operators/lte"; - -describe("LessThanOrEqualOperator", () => { - const operator = new LessThanOrEqualOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when left-side argument is less or equal and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/match.test.js b/src/__tests__/jsonexpr/operators/match.test.js deleted file mode 100644 index 4783aa1..0000000 --- a/src/__tests__/jsonexpr/operators/match.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { MatchOperator } from "../../../jsonexpr/operators/match"; - -describe("MatchOperator", () => { - const operator = new MatchOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("", () => { - expect(operator.evaluate(evaluator, ["abcdefghijk", ""])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "abc"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "ijk"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "^abc"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "ijk$"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "def"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "b.*j"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "xyz"])).toBe(false); - - expect(operator.evaluate(evaluator, [null, "abc"])).toBe(null); - expect(operator.evaluate(evaluator, ["abcdefghijk", null])).toBe(null); - }); - }); -}); - -/* - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "abc"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "ijk"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "^abc"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf(",l5abcdefghijk", "ijk$"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "def"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "b.*j"))); - assertFalse((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "xyz"))); - - */ diff --git a/src/__tests__/jsonexpr/operators/not.test.js b/src/__tests__/jsonexpr/operators/not.test.js deleted file mode 100644 index a848892..0000000 --- a/src/__tests__/jsonexpr/operators/not.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { NotOperator } from "../../../jsonexpr/operators/not"; - -describe("NotOperator", () => { - const operator = new NotOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if argument is falsy", () => { - expect(operator.evaluate(evaluator, false)).toBe(true); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(false); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(false); - }); - - it("should return false if argument is truthy", () => { - expect(operator.evaluate(evaluator, true)).toBe(false); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(true); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(true); - }); - - it("should return true if argument is null", () => { - expect(operator.evaluate(evaluator, null)).toBe(true); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(null); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(null); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/null.test.js b/src/__tests__/jsonexpr/operators/null.test.js deleted file mode 100644 index e464821..0000000 --- a/src/__tests__/jsonexpr/operators/null.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { NullOperator } from "../../../jsonexpr/operators/null"; - -describe("NullOperator", () => { - const operator = new NullOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if argument is null", () => { - expect(operator.evaluate(evaluator, null)).toBe(true); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(null); - expect(evaluator.booleanConvert).not.toHaveBeenCalled(); - }); - - it("should return true if argument is not null", () => { - expect(operator.evaluate(evaluator, true)).toBe(false); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(true); - - expect(operator.evaluate(evaluator, false)).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, false); - - expect(operator.evaluate(evaluator, 0)).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(3); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(3, 0); - }); - }); -}); - -/* - @Test - void testNull() { - assertTrue((Boolean) operator.evaluate(evaluator, null)); - verify(evaluator, times(1)).evaluate(null); - } - - @Test - void testNotNull() { - assertFalse((Boolean) operator.evaluate(evaluator, true)); - verify(evaluator, times(1)).evaluate(true); - - assertFalse((Boolean) operator.evaluate(evaluator, false)); - verify(evaluator, times(1)).evaluate(false); - - assertFalse((Boolean) operator.evaluate(evaluator, 0)); - verify(evaluator, times(1)).evaluate(0); - } - */ diff --git a/src/__tests__/jsonexpr/operators/or.test.js b/src/__tests__/jsonexpr/operators/or.test.js deleted file mode 100644 index 566a223..0000000 --- a/src/__tests__/jsonexpr/operators/or.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { OrCombinator } from "../../../jsonexpr/operators/or"; - -describe("OrCombinator", () => { - const combinator = new OrCombinator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if any argument evaluates to true", () => { - expect(combinator.evaluate(evaluator, [true])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(true); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(true); - }); - - it("should return false if all arguments evaluate to false", () => { - expect(combinator.evaluate(evaluator, [false])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(false); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(false); - }); - - it("should return false if all arguments evaluates to null", () => { - expect(combinator.evaluate(evaluator, [null])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(null); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(null); - }); - - it("should short-circuit and not evaluate unnecessary expressions", () => { - expect(combinator.evaluate(evaluator, [true, false, true])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, true); - expect(evaluator.booleanConvert).toHaveBeenNthCalledWith(1, true); - }); - - it("should combine multiple arguments", () => { - expect(combinator.evaluate(evaluator, [true, true])).toBe(true); - expect(combinator.evaluate(evaluator, [true, true, true])).toBe(true); - - expect(combinator.evaluate(evaluator, [true, false])).toBe(true); - expect(combinator.evaluate(evaluator, [false, true])).toBe(true); - expect(combinator.evaluate(evaluator, [false, false])).toBe(false); - expect(combinator.evaluate(evaluator, [false, false, false])).toBe(false); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_eq.test.js b/src/__tests__/jsonexpr/operators/semver_eq.test.js deleted file mode 100644 index 187747b..0000000 --- a/src/__tests__/jsonexpr/operators/semver_eq.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverEqualsOperator } from "../../../jsonexpr/operators/semver_eq"; - -describe("SemverEqualsOperator", () => { - const operator = new SemverEqualsOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when versions are not equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_gt.test.js b/src/__tests__/jsonexpr/operators/semver_gt.test.js deleted file mode 100644 index bb63d6f..0000000 --- a/src/__tests__/jsonexpr/operators/semver_gt.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverGreaterThanOperator } from "../../../jsonexpr/operators/semver_gt"; - -describe("SemverGreaterThanOperator", () => { - const operator = new SemverGreaterThanOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when left version is greater", () => { - expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); - }); - - it("should return false when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when left version is less", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_gte.test.js b/src/__tests__/jsonexpr/operators/semver_gte.test.js deleted file mode 100644 index 38e6e84..0000000 --- a/src/__tests__/jsonexpr/operators/semver_gte.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverGreaterThanOrEqualOperator } from "../../../jsonexpr/operators/semver_gte"; - -describe("SemverGreaterThanOrEqualOperator", () => { - const operator = new SemverGreaterThanOrEqualOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when left version is greater", () => { - expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); - }); - - it("should return true when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when left version is less", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_lt.test.js b/src/__tests__/jsonexpr/operators/semver_lt.test.js deleted file mode 100644 index 410619f..0000000 --- a/src/__tests__/jsonexpr/operators/semver_lt.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverLessThanOperator } from "../../../jsonexpr/operators/semver_lt"; - -describe("SemverLessThanOperator", () => { - const operator = new SemverLessThanOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when left version is less", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return false when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when left version is greater", () => { - expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_lte.test.js b/src/__tests__/jsonexpr/operators/semver_lte.test.js deleted file mode 100644 index b4a870f..0000000 --- a/src/__tests__/jsonexpr/operators/semver_lte.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverLessThanOrEqualOperator } from "../../../jsonexpr/operators/semver_lte"; - -describe("SemverLessThanOrEqualOperator", () => { - const operator = new SemverLessThanOrEqualOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when left version is less", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return true when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when left version is greater", () => { - expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/value.test.js b/src/__tests__/jsonexpr/operators/value.test.js deleted file mode 100644 index d676acd..0000000 --- a/src/__tests__/jsonexpr/operators/value.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { ValueOperator } from "../../../jsonexpr/operators/value"; - -describe("ValueOperator", () => { - const operator = new ValueOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should not call evaluator evaluate", () => { - expect(operator.evaluate(evaluator, 0)).toBe(0); - expect(operator.evaluate(evaluator, 1)).toBe(1); - expect(operator.evaluate(evaluator, true)).toBe(true); - expect(operator.evaluate(evaluator, false)).toBe(false); - expect(operator.evaluate(evaluator, "")).toBe(""); - expect(operator.evaluate(evaluator, null)).toBe(null); - expect(operator.evaluate(evaluator, {})).toEqual({}); - expect(operator.evaluate(evaluator, [])).toEqual([]); - - expect(evaluator.evaluate).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/var.test.js b/src/__tests__/jsonexpr/operators/var.test.js deleted file mode 100644 index ac00c64..0000000 --- a/src/__tests__/jsonexpr/operators/var.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { VarOperator } from "../../../jsonexpr/operators/var"; - -describe("VarOperator", () => { - const operator = new VarOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should call evaluator extract", () => { - expect(operator.evaluate(evaluator, "a/b/c")).toBe("abc"); - - expect(evaluator.extractVar).toHaveBeenCalledTimes(1); - expect(evaluator.extractVar).toHaveBeenCalledWith("a/b/c"); - expect(evaluator.evaluate).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/matcher.test.js b/src/__tests__/matcher.test.js deleted file mode 100644 index 05c345c..0000000 --- a/src/__tests__/matcher.test.js +++ /dev/null @@ -1,49 +0,0 @@ -import { AudienceMatcher } from "../matcher"; - -describe("AudienceMatcher", () => { - const matcher = new AudienceMatcher(); - - it("should return null on empty audience", () => { - expect(matcher.evaluate("", null)).toBe(null); - expect(matcher.evaluate("{}", null)).toBe(null); - expect(matcher.evaluate("null", null)).toBe(null); - }); - - it("should return null if filter not object or array", () => { - expect(matcher.evaluate('{"filter":null}', null)).toBe(null); - expect(matcher.evaluate('{"filter":false}', null)).toBe(null); - expect(matcher.evaluate('{"filter":5}', null)).toBe(null); - expect(matcher.evaluate('{"filter":"a"}', null)).toBe(null); - }); - - it("should return boolean", () => { - expect(matcher.evaluate('{"filter":[{"value":5}]}', null)).toBe(true); - expect(matcher.evaluate('{"filter":[{"value":true}]}', null)).toBe(true); - expect(matcher.evaluate('{"filter":[{"value":1}]}', null)).toBe(true); - expect(matcher.evaluate('{"filter":[{"value":null}]}', null)).toBe(false); - expect(matcher.evaluate('{"filter":[{"value":0}]}', null)).toBe(false); - - expect(matcher.evaluate('{"filter":[{"not":{"var":"returning"}}]}', { returning: true })).toBe(false); - expect(matcher.evaluate('{"filter":[{"not":{"var":"returning"}}]}', { returning: false })).toBe(true); - }); -}); - -/* - - @Test - void evaluateReturnsNullIfFilterNotMapOrList() { - assertNull(matcher.evaluate("{\"filter\":5}", null)); - } - - @Test - void evaluateReturnsBoolean() { - assertTrue(matcher.evaluate("{\"filter\":[{\"value\":5}]}", null)); - assertTrue(matcher.evaluate("{\"filter\":[{\"value\":true}]}", null)); - assertTrue(matcher.evaluate("{\"filter\":[{\"value\":1}]}", null)); - assertFalse(matcher.evaluate("{\"filter\":[{\"value\":null}]}", null)); - assertFalse(matcher.evaluate("{\"filter\":[{\"value\":0}]}", null)); - - assertFalse(matcher.evaluate("{\"filter\":[{\"not\":{\"var\":\"returning\"}}]}", mapOf("returning", true))); - assertTrue(matcher.evaluate("{\"filter\":[{\"not\":{\"var\":\"returning\"}}]}", mapOf("returning", false))); - } - */ diff --git a/src/__tests__/matcher.test.ts b/src/__tests__/matcher.test.ts new file mode 100644 index 0000000..0664f7a --- /dev/null +++ b/src/__tests__/matcher.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, test, it } from "vitest"; +import { AudienceMatcher } from "../matcher"; + +describe("AudienceMatcher", () => { + const matcher = new AudienceMatcher(); + + describe("null/empty handling", () => { + test("null audience → null", () => { + expect(matcher.evaluate(null as any, {})).toBe(null); + }); + + test("empty string → null", () => { + expect(matcher.evaluate("", {})).toBe(null); + }); + + test("returns null for invalid JSON", () => { + expect(matcher.evaluate("invalid json", {})).toBe(null); + }); + + test("returns null for missing filter", () => { + expect(matcher.evaluate(JSON.stringify({}), {})).toBe(null); + }); + + test("returns null for null filter", () => { + expect(matcher.evaluate(JSON.stringify({ filter: null }), {})).toBe(null); + }); + }); + + describe("filter evaluation", () => { + test("evaluates matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: true }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + test("evaluates non-matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: false }] }); + expect(matcher.evaluate(audience, {})).toBe(false); + }); + + test("evaluates with not operator", () => { + const audience = JSON.stringify({ filter: [{ not: { value: false } }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + it("and with all true → true", () => { + const audience = JSON.stringify({ + filter: [{ and: [{ value: true }, { value: true }] }], + }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + it("and with one false → false", () => { + const audience = JSON.stringify({ + filter: [{ and: [{ value: true }, { value: false }] }], + }); + expect(matcher.evaluate(audience, {})).toBe(false); + }); + + it("evaluates with attributes (gte)", () => { + const audience = JSON.stringify({ + filter: [{ gte: [{ var: "age" }, { value: 18 }] }], + }); + expect(matcher.evaluate(audience, { age: 25 })).toBe(true); + expect(matcher.evaluate(audience, { age: 15 })).toBe(false); + }); + + it("complex filter with and+gte+in", () => { + const audience = JSON.stringify({ + filter: [{ + and: [ + { gte: [{ var: "age" }, { value: 18 }] }, + { in: [{ value: ["US", "UK"] }, { var: "country" }] }, + ], + }], + }); + expect(matcher.evaluate(audience, { age: 25, country: "US" })).toBe(true); + expect(matcher.evaluate(audience, { age: 25, country: "FR" })).toBe(false); + expect(matcher.evaluate(audience, { age: 15, country: "US" })).toBe(false); + }); + + it("or filter", () => { + const audience = JSON.stringify({ + filter: [{ + or: [ + { eq: [{ var: "plan" }, { value: "pro" }] }, + { eq: [{ var: "plan" }, { value: "enterprise" }] }, + ], + }], + }); + expect(matcher.evaluate(audience, { plan: "pro" })).toBe(true); + expect(matcher.evaluate(audience, { plan: "enterprise" })).toBe(true); + expect(matcher.evaluate(audience, { plan: "free" })).toBe(false); + }); + + it("not filter", () => { + const audience = JSON.stringify({ + filter: [{ not: { eq: [{ var: "bot" }, { value: true }] } }], + }); + expect(matcher.evaluate(audience, { bot: false })).toBe(true); + expect(matcher.evaluate(audience, { bot: true })).toBe(false); + }); + + it("match filter with regex", () => { + const audience = JSON.stringify({ + filter: [{ match: [{ var: "email" }, { value: ".*@absmartly\\.com$" }] }], + }); + expect(matcher.evaluate(audience, { email: "user@absmartly.com" })).toBe(true); + expect(matcher.evaluate(audience, { email: "user@other.com" })).toBe(false); + }); + }); +}); diff --git a/src/__tests__/md5.test.js b/src/__tests__/md5.test.js deleted file mode 100644 index 6086c21..0000000 --- a/src/__tests__/md5.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import { md5 } from "../md5"; -import { base64UrlNoPadding, stringToUint8Array } from "../utils"; - -describe("md5()", () => { - it("should match known hashes", (done) => { - const testCases = [ - ["", "1B2M2Y8AsgTpgAmY7PhCfg"], - [" ", "chXunH2dwinSkhpA6JnsXw"], - ["t", "41jvpIn1gGLxDdcxa2Vkng"], - ["te", "Vp73JkK-D63XEdakaNaO4Q"], - ["tes", "KLZi2IO212_Zbk3cXpungA"], - ["test", "CY9rzUYh03PK3k6DJie09g"], - ["testy", "K5I_V6RgP8c6sYKz-TVn8g"], - ["testy1", "8fT8xGipOhPkZ2DncKU-1A"], - ["testy12", "YqRAtOz000gIu61ErEH18A"], - ["testy123", "pfV2H07L6WvdqlY0zHuYIw"], - ["special characters açb↓c", "4PIrO7lKtTxOcj2eMYlG7A"], - ["The quick brown fox jumps over the lazy dog", "nhB9nTcrtoJr2B01QqQZ1g"], - ["The quick brown fox jumps over the lazy dog and eats a pie", "iM-8ECRrLUQzixl436y96A"], - [ - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "24m7XOq4f5wPzCqzbBicLA", - ], - ]; - - testCases.forEach((testCase) => { - const bytes = stringToUint8Array(testCase[0]); - const hash = md5(bytes.buffer); - expect(base64UrlNoPadding(hash)).toEqual(testCase[1]); - }); - - done(); - }); -}); diff --git a/src/__tests__/md5.test.ts b/src/__tests__/md5.test.ts new file mode 100644 index 0000000..96ad237 --- /dev/null +++ b/src/__tests__/md5.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, test } from "vitest"; +import { md5 } from "../md5"; + +function stringToBuffer(value: string): ArrayBuffer { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array).buffer; +} + +function toHex(bytes: Uint8Array): string { + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +describe("md5", () => { + const testCases: [string, string][] = [ + ["", "d41d8cd98f00b204e9800998ecf8427e"], + ["a", "0cc175b9c0f1b6a831c399e269772661"], + ["abc", "900150983cd24fb0d6963f7d28e17f72"], + ["message digest", "f96b697d7cb7938d525a2f31aaf161d0"], + ["abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"], + ]; + + for (const [input, expectedHex] of testCases) { + test(`md5("${input}") == ${expectedHex}`, () => { + const result = md5(stringToBuffer(input)); + expect(toHex(result)).toBe(expectedHex); + }); + } +}); diff --git a/src/__tests__/murmur3_32.test.js b/src/__tests__/murmur3.test.ts similarity index 65% rename from src/__tests__/murmur3_32.test.js rename to src/__tests__/murmur3.test.ts index 6b5a279..7f76321 100644 --- a/src/__tests__/murmur3_32.test.js +++ b/src/__tests__/murmur3.test.ts @@ -1,8 +1,9 @@ -import { stringToUint8Array } from "../utils"; -import { murmur3_32 } from "../murmur3_32"; +import { describe, expect, test } from "vitest"; +import { murmur3_32 } from "../murmur3"; +import { stringToUint8Array } from "../hashing"; -describe("murmur3_32()", () => { - const testCases = [ +describe("murmur3_32", () => { + const testCases: [string, number, number][] = [ ["", 0x00000000, 0x00000000], [" ", 0x00000000, 0x7ef49b98], ["t", 0x00000000, 0xca87df4d], @@ -13,7 +14,7 @@ describe("murmur3_32()", () => { ["testy1", 0x00000000, 0x8a1a243a], ["testy12", 0x00000000, 0x845461b9], ["testy123", 0x00000000, 0x47628ac4], - ["special characters açb↓c", 0x00000000, 0xbe83b140], + ["special characters a\u00e7b\u2193c", 0x00000000, 0xbe83b140], ["The quick brown fox jumps over the lazy dog", 0x00000000, 0x2e4ff723], ["", 0xdeadbeef, 0x0de5c6a9], [" ", 0xdeadbeef, 0x25acce43], @@ -25,7 +26,7 @@ describe("murmur3_32()", () => { ["testy1", 0xdeadbeef, 0x09ed28e9], ["testy12", 0xdeadbeef, 0x22467835], ["testy123", 0xdeadbeef, 0xd633060d], - ["special characters açb↓c", 0xdeadbeef, 0xf7fdd8a2], + ["special characters a\u00e7b\u2193c", 0xdeadbeef, 0xf7fdd8a2], ["The quick brown fox jumps over the lazy dog", 0xdeadbeef, 0x3a7b3f4d], ["", 0x00000001, 0x514e28b7], [" ", 0x00000001, 0x4f0f7132], @@ -37,16 +38,14 @@ describe("murmur3_32()", () => { ["testy1", 0x00000001, 0x33925ceb], ["testy12", 0x00000001, 0xd92c9f23], ["testy123", 0x00000001, 0x3bc1712d], - ["special characters açb↓c", 0x00000001, 0x293327b5], + ["special characters a\u00e7b\u2193c", 0x00000001, 0x293327b5], ["The quick brown fox jumps over the lazy dog", 0x00000001, 0x78e69e27], ]; - it("should match known hashes", (done) => { - testCases.forEach((testCase) => { - const bytes = stringToUint8Array(testCase[0]); - expect(murmur3_32(bytes.buffer, testCase[1])).toBe(testCase[2]); + for (const [input, seed, expected] of testCases) { + test(`murmur3_32("${input}", 0x${seed.toString(16)}) == 0x${expected.toString(16).padStart(8, "0")}`, () => { + const bytes = stringToUint8Array(input); + expect(murmur3_32(bytes.buffer, seed)).toBe(expected); }); - - done(); - }); + } }); diff --git a/src/__tests__/operators.test.ts b/src/__tests__/operators.test.ts new file mode 100644 index 0000000..3c2421e --- /dev/null +++ b/src/__tests__/operators.test.ts @@ -0,0 +1,205 @@ +import { describe, it, expect } from "vitest"; +import { Evaluator } from "../jsonexpr/evaluator"; +import { + AndCombinator, + EqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualOperator, + InOperator, + LessThanOperator, + LessThanOrEqualOperator, + MatchOperator, + NotOperator, + NullOperator, + OrCombinator, + ValueOperator, + VarOperator, +} from "../jsonexpr/operators"; + +const operators = { + and: new AndCombinator(), + or: new OrCombinator(), + value: new ValueOperator(), + var: new VarOperator(), + null: new NullOperator(), + not: new NotOperator(), + in: new InOperator(), + match: new MatchOperator(), + eq: new EqualsOperator(), + gt: new GreaterThanOperator(), + gte: new GreaterThanOrEqualOperator(), + lt: new LessThanOperator(), + lte: new LessThanOrEqualOperator(), +}; + +function evalExpr(expr: unknown, vars: Record = {}): unknown { + const e = new Evaluator(operators, vars); + return e.evaluate(expr); +} + +describe("operators", () => { + describe("value", () => { + it("returns literal", () => expect(evalExpr({ value: 42 })).toBe(42)); + it("returns string", () => expect(evalExpr({ value: "hello" })).toBe("hello")); + it("returns null", () => expect(evalExpr({ value: null })).toBe(null)); + it("returns boolean", () => expect(evalExpr({ value: true })).toBe(true)); + }); + + // NOTE: Branch VarOperator expects path string or {path: "..."}, not {value: "..."} + describe("var", () => { + it("extracts variable by path string", () => { + expect(evalExpr({ var: "x" }, { x: 99 })).toBe(99); + }); + it("extracts nested via slash-separated path", () => { + expect(evalExpr({ var: "a/b" }, { a: { b: 5 } } as any)).toBe(5); + }); + it("missing returns null", () => { + expect(evalExpr({ var: "z" }, { x: 1 })).toBe(null); + }); + it("extracts via object with path property", () => { + expect(evalExpr({ var: { path: "x" } as unknown }, { x: 42 })).toBe(42); + }); + }); + + describe("and", () => { + it("empty → true", () => expect(evalExpr({ and: [] })).toBe(true)); + it("all true → true", () => expect(evalExpr({ and: [{ value: true }, { value: 1 }] })).toBe(true)); + it("any false → false", () => expect(evalExpr({ and: [{ value: true }, { value: false }] })).toBe(false)); + it("all false → false", () => expect(evalExpr({ and: [{ value: false }, { value: 0 }] })).toBe(false)); + }); + + // NOTE: Branch OrCombinator returns true for empty array (vacuous truth), matching AndCombinator + describe("or", () => { + it("empty → true (vacuous)", () => expect(evalExpr({ or: [] })).toBe(true)); + it("any true → true", () => expect(evalExpr({ or: [{ value: false }, { value: true }] })).toBe(true)); + it("all false → false", () => expect(evalExpr({ or: [{ value: false }, { value: 0 }] })).toBe(false)); + it("first true → true", () => expect(evalExpr({ or: [{ value: true }, { value: false }] })).toBe(true)); + }); + + describe("not", () => { + it("true → false", () => expect(evalExpr({ not: { value: true } })).toBe(false)); + it("false → true", () => expect(evalExpr({ not: { value: false } })).toBe(true)); + it("1 → false", () => expect(evalExpr({ not: { value: 1 } })).toBe(false)); + it("0 → true", () => expect(evalExpr({ not: { value: 0 } })).toBe(true)); + it("null → true", () => expect(evalExpr({ not: { value: null } })).toBe(true)); + }); + + describe("null", () => { + it("null → true", () => expect(evalExpr({ null: { value: null } })).toBe(true)); + it("0 → false", () => expect(evalExpr({ null: { value: 0 } })).toBe(false)); + it("'' → false", () => expect(evalExpr({ null: { value: "" } })).toBe(false)); + it("false → false", () => expect(evalExpr({ null: { value: false } })).toBe(false)); + }); + + // NOTE: BinaryOperator base returns null if lhs is null, so eq(null, null) → null + describe("eq", () => { + it("null == null → null (null short-circuits)", () => + expect(evalExpr({ eq: [{ value: null }, { value: null }] })).toBe(null)); + it("null == 0 → null", () => expect(evalExpr({ eq: [{ value: null }, { value: 0 }] })).toBe(null)); + it("0 == 0 → true", () => expect(evalExpr({ eq: [{ value: 0 }, { value: 0 }] })).toBe(true)); + it("1 == 0 → false", () => expect(evalExpr({ eq: [{ value: 1 }, { value: 0 }] })).toBe(false)); + it("'a' == 'a' → true", () => expect(evalExpr({ eq: [{ value: "a" }, { value: "a" }] })).toBe(true)); + it("'a' == 'b' → false", () => expect(evalExpr({ eq: [{ value: "a" }, { value: "b" }] })).toBe(false)); + it("true == true → true", () => expect(evalExpr({ eq: [{ value: true }, { value: true }] })).toBe(true)); + it("true == false → false", () => expect(evalExpr({ eq: [{ value: true }, { value: false }] })).toBe(false)); + }); + + describe("gt", () => { + it("1 > 0 → true", () => expect(evalExpr({ gt: [{ value: 1 }, { value: 0 }] })).toBe(true)); + it("0 > 1 → false", () => expect(evalExpr({ gt: [{ value: 0 }, { value: 1 }] })).toBe(false)); + it("0 > 0 → false", () => expect(evalExpr({ gt: [{ value: 0 }, { value: 0 }] })).toBe(false)); + it("null > 0 → null", () => expect(evalExpr({ gt: [{ value: null }, { value: 0 }] })).toBe(null)); + it("'b' > 'a' → true", () => expect(evalExpr({ gt: [{ value: "b" }, { value: "a" }] })).toBe(true)); + }); + + describe("gte", () => { + it("1 >= 0 → true", () => expect(evalExpr({ gte: [{ value: 1 }, { value: 0 }] })).toBe(true)); + it("0 >= 0 → true", () => expect(evalExpr({ gte: [{ value: 0 }, { value: 0 }] })).toBe(true)); + it("0 >= 1 → false", () => expect(evalExpr({ gte: [{ value: 0 }, { value: 1 }] })).toBe(false)); + }); + + describe("lt", () => { + it("0 < 1 → true", () => expect(evalExpr({ lt: [{ value: 0 }, { value: 1 }] })).toBe(true)); + it("1 < 0 → false", () => expect(evalExpr({ lt: [{ value: 1 }, { value: 0 }] })).toBe(false)); + it("0 < 0 → false", () => expect(evalExpr({ lt: [{ value: 0 }, { value: 0 }] })).toBe(false)); + }); + + describe("lte", () => { + it("0 <= 1 → true", () => expect(evalExpr({ lte: [{ value: 0 }, { value: 1 }] })).toBe(true)); + it("0 <= 0 → true", () => expect(evalExpr({ lte: [{ value: 0 }, { value: 0 }] })).toBe(true)); + it("1 <= 0 → false", () => expect(evalExpr({ lte: [{ value: 1 }, { value: 0 }] })).toBe(false)); + }); + + // NOTE: Branch InOperator signature is (haystack, needle) — first arg is the collection + describe("in", () => { + it("string contains", () => { + expect(evalExpr({ in: [{ value: "abcdef" }, { value: "bc" }] })).toBe(true); + }); + it("string not contains", () => { + expect(evalExpr({ in: [{ value: "abcdef" }, { value: "xyz" }] })).toBe(false); + }); + it("array contains", () => { + expect(evalExpr({ in: [{ value: [1, 2, 3] }, { value: 2 }] })).toBe(true); + }); + it("array not contains", () => { + expect(evalExpr({ in: [{ value: [1, 2, 3] }, { value: 4 }] })).toBe(false); + }); + it("null haystack → null", () => { + expect(evalExpr({ in: [{ value: null }, { value: "a" }] })).toBe(null); + }); + it("object key exists", () => { + expect(evalExpr({ in: [{ value: { a: 1, b: 2 } }, { value: "a" }] })).toBe(true); + }); + it("object key missing", () => { + expect(evalExpr({ in: [{ value: { a: 1, b: 2 } }, { value: "c" }] })).toBe(false); + }); + }); + + describe("match", () => { + it("matches regex", () => { + expect(evalExpr({ match: [{ value: "hello world" }, { value: "hello.*" }] })).toBe(true); + }); + it("no match", () => { + expect(evalExpr({ match: [{ value: "hello" }, { value: "^world$" }] })).toBe(false); + }); + it("null text → null", () => { + expect(evalExpr({ match: [{ value: null }, { value: "abc" }] })).toBe(null); + }); + it("null pattern → null", () => { + expect(evalExpr({ match: [{ value: "hello" }, { value: null }] })).toBe(null); + }); + it("invalid regex → null", () => { + expect(evalExpr({ match: [{ value: "hello" }, { value: "[invalid" }] })).toBe(null); + }); + }); + + describe("complex expressions", () => { + it("nested and/or", () => { + const expr = { + and: [ + { or: [{ value: true }, { value: false }] }, + { not: { value: false } }, + ], + }; + expect(evalExpr(expr)).toBe(true); + }); + + it("variable comparison", () => { + const expr = { gt: [{ var: "age" }, { value: 18 }] }; + expect(evalExpr(expr, { age: 25 })).toBe(true); + expect(evalExpr(expr, { age: 15 })).toBe(false); + }); + + it("audience-like filter", () => { + const expr = { + and: [ + { gte: [{ var: "age" }, { value: 18 }] }, + { in: [{ value: ["US", "UK", "CA"] }, { var: "country" }] }, + ], + }; + expect(evalExpr(expr, { age: 25, country: "US" })).toBe(true); + expect(evalExpr(expr, { age: 25, country: "FR" })).toBe(false); + expect(evalExpr(expr, { age: 15, country: "US" })).toBe(false); + }); + }); +}); diff --git a/src/__tests__/provider.test.js b/src/__tests__/provider.test.js deleted file mode 100644 index ed15180..0000000 --- a/src/__tests__/provider.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import Client from "../client"; -import SDK from "../sdk"; -import { ContextDataProvider } from "../provider"; - -jest.mock("../client"); -jest.mock("../sdk"); - -describe("ContextDataProvider", () => { - const client = new Client(); - const sdk = new SDK(); - - sdk.getClient.mockReturnValue(client); - - describe("getContextData()", () => { - it("should call client getContext", async () => { - const provider = new ContextDataProvider(); - - const data = {}; - client.getContext.mockReturnValue(Promise.resolve(data)); - - const result = provider.getContextData(sdk); - - expect(result).toBeInstanceOf(Promise); - expect(client.getContext).toHaveBeenCalledTimes(1); - expect(client.getContext).toHaveBeenCalledWith(undefined); - - result.then((resp) => { - expect(resp).toBe(data); - }); - }); - - it("should pass through options", async () => { - const provider = new ContextDataProvider(); - - const data = {}; - client.getContext.mockReturnValue(Promise.resolve(data)); - - const result = provider.getContextData(sdk, { timeout: 1234 }); - - expect(result).toBeInstanceOf(Promise); - expect(client.getContext).toHaveBeenCalledTimes(1); - expect(client.getContext).toHaveBeenCalledWith({ - timeout: 1234, - }); - - result.then((resp) => { - expect(resp).toBe(data); - }); - }); - }); -}); diff --git a/src/__tests__/provider.test.ts b/src/__tests__/provider.test.ts new file mode 100644 index 0000000..26d5db5 --- /dev/null +++ b/src/__tests__/provider.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, test, vi } from "vitest"; +import { DefaultContextDataProvider } from "../provider"; + +describe("DefaultContextDataProvider", () => { + test("delegates to sdk.getClient().getContext()", async () => { + const mockGetContext = vi.fn().mockResolvedValue({ experiments: [] }); + const mockSdk = { getClient: () => ({ getContext: mockGetContext }) }; + + const provider = new DefaultContextDataProvider(); + const result = await provider.getContextData(mockSdk, { path: "/test" }); + + expect(mockGetContext).toHaveBeenCalledWith({ path: "/test" }); + expect(result).toEqual({ experiments: [] }); + }); +}); diff --git a/src/__tests__/publisher.test.js b/src/__tests__/publisher.test.js deleted file mode 100644 index 4d59dbe..0000000 --- a/src/__tests__/publisher.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import Client from "../client"; -import SDK from "../sdk"; -import Context from "../context"; -import { ContextPublisher } from "../publisher"; - -jest.mock("../client"); -jest.mock("../sdk"); -jest.mock("../context"); - -describe("ContextPublisher", () => { - const client = new Client(); - const sdk = new SDK(); - const context = new Context(); - - sdk.getClient.mockReturnValue(client); - - describe("publish()", () => { - it("should call client publish", async () => { - const publisher = new ContextPublisher(); - - const data = { ok: true }; - client.publish.mockReturnValue(Promise.resolve(data)); - - const request = { test: 1 }; - const result = publisher.publish(request, sdk, context); - - expect(result).toBeInstanceOf(Promise); - expect(client.publish).toHaveBeenCalledTimes(1); - expect(client.publish).toHaveBeenCalledWith(request, undefined); - - result.then((resp) => { - expect(resp).toBe(data); - }); - }); - - it("should pass through options", async () => { - const publisher = new ContextPublisher(); - - const data = { ok: true }; - client.publish.mockReturnValue(Promise.resolve(data)); - - const request = { test: 1 }; - const result = publisher.publish(request, sdk, context, { timeout: 1234 }); - - expect(result).toBeInstanceOf(Promise); - expect(client.publish).toHaveBeenCalledTimes(1); - expect(client.publish).toHaveBeenCalledWith(request, { - timeout: 1234, - }); - - result.then((resp) => { - expect(resp).toBe(data); - }); - }); - }); -}); diff --git a/src/__tests__/publisher.test.ts b/src/__tests__/publisher.test.ts new file mode 100644 index 0000000..d755958 --- /dev/null +++ b/src/__tests__/publisher.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, test, vi } from "vitest"; +import { DefaultContextPublisher } from "../publisher"; + +describe("DefaultContextPublisher", () => { + test("delegates to sdk.getClient().publish()", async () => { + const mockPublish = vi.fn().mockResolvedValue({}); + const mockSdk = { getClient: () => ({ publish: mockPublish }) }; + const request = { + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + }; + + const publisher = new DefaultContextPublisher(); + await publisher.publish(request, mockSdk, {}, { path: "/test" }); + + expect(mockPublish).toHaveBeenCalledWith(request, { path: "/test" }); + }); +}); diff --git a/src/__tests__/sdk.test.js b/src/__tests__/sdk.test.js deleted file mode 100644 index 3dc75de..0000000 --- a/src/__tests__/sdk.test.js +++ /dev/null @@ -1,479 +0,0 @@ -import Client from "../client"; -import SDK from "../sdk"; -import Context from "../context"; -import { ContextPublisher } from "../publisher"; -import { ContextDataProvider } from "../provider"; - -jest.mock("../client"); -jest.mock("../context"); -jest.mock("../publisher"); -jest.mock("../provider"); - -describe("SDK", () => { - const contextParams = { - units: { - session_id: "ab", - }, - }; - - const testEventLogger = jest.fn(); - const testContextDataProvider = new ContextDataProvider(); - const testContextPublisher = new ContextPublisher(); - - const sdkOptions = { - agent: "javascript-sdk", - apiKey: "apikey", - application: "website", - endpoint: "localhost:8080", - environment: "test", - eventLogger: testEventLogger, - publisher: testContextPublisher, - provider: testContextDataProvider, - timeout: 1000, - }; - - describe("constructor()", () => { - it("should try to create a client with no options", (done) => { - const sdk = new SDK(); - - expect(sdk).toBeInstanceOf(SDK); - expect(sdk.getEventLogger()).toBe(SDK.defaultEventLogger); - expect(Client).toHaveBeenCalledTimes(1); - expect(Client).toHaveBeenCalledWith({ - agent: "absmartly-javascript-sdk", - }); - - expect(ContextDataProvider).toHaveBeenCalledTimes(1); - expect(ContextDataProvider).toHaveBeenCalledWith(); - - expect(ContextPublisher).toHaveBeenCalledTimes(1); - expect(ContextPublisher).toHaveBeenCalledWith(); - - expect(sdk.getClient()).toBeInstanceOf(Client); - expect(sdk.getContextDataProvider()).toBeInstanceOf(ContextDataProvider); - expect(sdk.getContextPublisher()).toBeInstanceOf(ContextPublisher); - - done(); - }); - - it("should create a client with specified options", (done) => { - const sdk = new SDK(sdkOptions); - - expect(sdk).toBeInstanceOf(SDK); - expect(sdk.getEventLogger()).toBe(testEventLogger); - expect(sdk.getContextDataProvider()).toBe(testContextDataProvider); - expect(sdk.getContextPublisher()).toBe(testContextPublisher); - expect(Client).toHaveBeenCalledTimes(1); - expect(Client).toHaveBeenCalledWith({ - agent: "javascript-sdk", - apiKey: "apikey", - application: "website", - endpoint: "localhost:8080", - environment: "test", - timeout: 1000, - }); - - expect(sdk.getClient()).toBeInstanceOf(Client); - expect(sdk.getContextDataProvider()).toBe(testContextDataProvider); - expect(sdk.getContextPublisher()).toBe(testContextPublisher); - - done(); - }); - - it("should pass custom agent to client", (done) => { - const customAgent = "my-custom-agent"; - const options = { - ...sdkOptions, - agent: customAgent, - }; - - const sdk = new SDK(options); - - expect(sdk).toBeInstanceOf(SDK); - expect(Client).toHaveBeenCalledTimes(1); - expect(Client).toHaveBeenCalledWith( - expect.objectContaining({ - agent: customAgent, - }) - ); - - done(); - }); - - it("should set default values for unspecified client options", (done) => { - const options = { - application: "application", - apiKey: "apikey", - endpoint: "localhost:8080", - environment: "test", - }; - - const sdk = new SDK(options); - - expect(sdk).toBeInstanceOf(SDK); - expect(sdk.getEventLogger()).toBe(SDK.defaultEventLogger); - expect(Client).toHaveBeenCalledTimes(1); - expect(Client).toHaveBeenCalledWith( - Object.assign( - {}, - { - agent: "absmartly-javascript-sdk", - }, - options - ) - ); - - done(); - }); - }); - - describe("getContextData()", () => { - it("should call client getContext and return data promise", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const data = sdk.getContextData(); - - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - expect(data).toBe(promise); - - expect(Context).not.toHaveBeenCalled(); - - done(); - }); - }); - - describe("createContext()", () => { - it("should create Context object with promise", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - }; - - const context = sdk.createContext(contextParams, contextOptions); - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, contextOptions, contextParams, promise); - - done(); - }); - - it("should pass through request options", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - }; - - const requestOptions = { - timeout: 1234, - }; - - const context = sdk.createContext(contextParams, contextOptions, requestOptions); - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, requestOptions); - - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, contextOptions, contextParams, promise); - - done(); - }); - - it("should coerce unit uid to string", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - }; - - const params = { - units: { - session_id: "ab", - user_id: 125, - float_id: 125.75, - }, - }; - - const context = sdk.createContext(params, contextOptions); - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, contextOptions, params, promise); - - done(); - }); - - it("should throw on unsupported unit uid type", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - eventLogger: testEventLogger, - }; - - const params = { - units: { - session_id: true, - }, - }; - - expect(() => sdk.createContext(params, contextOptions)).toThrow( - new Error( - "Unit 'session_id' UID is of unsupported type 'boolean'. UID must be one of ['string', 'number']" - ) - ); - expect(testContextDataProvider.getContextData).not.toHaveBeenCalled(); - expect(Context).not.toHaveBeenCalled(); - - done(); - }); - - it("should throw on empty unit uid", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - eventLogger: testEventLogger, - }; - - const params = { - units: { - session_id: "", - }, - }; - - expect(() => sdk.createContext(params, contextOptions)).toThrow( - new Error("Unit 'session_id' UID length must be >= 1") - ); - expect(testContextDataProvider.getContextData).not.toHaveBeenCalled(); - expect(Context).not.toHaveBeenCalled(); - - done(); - }); - - it("should initialize context with default options for nodejs", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const context = sdk.createContext(contextParams); - - const defaultOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(context).toBeInstanceOf(Context); - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, defaultOptions, contextParams, promise); - - done(); - }); - - it("should initialize context with default options for browser", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - // fake browser environment - const previousWindow = global.window; - global.window = { document: {} }; - - const context = sdk.createContext(contextParams); - - // restore environment - global.window = previousWindow; - - const defaultOptions = { - publishDelay: 100, - refreshPeriod: 0, - }; - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(context).toBeInstanceOf(Context); - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, defaultOptions, contextParams, promise); - - done(); - }); - }); - - describe("createContextWith()", () => { - it("should not call client getContext", (done) => { - const data = { - guid: "test", - }; - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - eventLogger: testEventLogger, - }; - - const sdk = new SDK(sdkOptions); - const context = sdk.createContextWith(contextParams, data, contextOptions); - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).not.toHaveBeenCalled(); - - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, contextOptions, contextParams, data); - - done(); - }); - - it("should throw on unsupported unit uid type", (done) => { - const sdk = new SDK(sdkOptions); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - eventLogger: testEventLogger, - }; - - const params = { - units: { - session_id: true, - }, - }; - - expect(() => sdk.createContextWith(params, contextOptions, {})).toThrow( - new Error( - "Unit 'session_id' UID is of unsupported type 'boolean'. UID must be one of ['string', 'number']" - ) - ); - expect(testContextDataProvider.getContextData).not.toHaveBeenCalled(); - expect(Context).not.toHaveBeenCalled(); - - done(); - }); - - it("should initialize context with default options", (done) => { - const data = { - guid: "test", - }; - - const defaultOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - const sdk = new SDK(sdkOptions); - const context = sdk.createContextWith(contextParams, data); - - expect(context).toBeInstanceOf(Context); - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, defaultOptions, contextParams, data); - - done(); - }); - }); - - describe("setEventLogger()", () => { - it("should override the current logger", (done) => { - const sdk = new SDK(sdkOptions); - expect(sdk.getEventLogger()).toBe(testEventLogger); - - const newLogger = jest.fn(); - sdk.setEventLogger(newLogger); - expect(sdk.getEventLogger()).toBe(newLogger); - - done(); - }); - }); - - describe("setContextDataProvider()", () => { - it("should override the current provider", (done) => { - const sdk = new SDK(sdkOptions); - expect(sdk.getContextDataProvider()).toBe(testContextDataProvider); - - const newProvider = new ContextDataProvider(); - sdk.setContextDataProvider(newProvider); - expect(sdk.getContextDataProvider()).toBe(newProvider); - - done(); - }); - }); - - describe("setContextPublisher()", () => { - it("should override the current publisher", (done) => { - const sdk = new SDK(sdkOptions); - expect(sdk.getContextPublisher()).toBe(testContextPublisher); - - const newPublisher = new ContextPublisher(); - sdk.setContextPublisher(newPublisher); - expect(sdk.getContextPublisher()).toBe(newPublisher); - - done(); - }); - }); - - describe("defaultLogger", () => { - it("should log only errors to console", (done) => { - const errorSpy = jest.spyOn(console, "error").mockImplementation(() => {}); - jest.spyOn(console, "warn").mockImplementation(() => {}); - jest.spyOn(console, "info").mockImplementation(() => {}); - jest.spyOn(console, "log").mockImplementation(() => {}); - - for (const eventName of ["error", "ready", "refresh", "publish", "goal", "exposure"]) { - if (eventName === "error") { - SDK.defaultEventLogger("context", eventName, "error text"); - expect(console.error).toHaveBeenCalledTimes(1); - expect(console.error).toHaveBeenCalledWith("error text"); - - errorSpy.mockClear(); - } else { - SDK.defaultEventLogger("context", eventName, {}); - expect(console.error).not.toHaveBeenCalled(); - } - expect(console.warn).not.toHaveBeenCalled(); - expect(console.info).not.toHaveBeenCalled(); - expect(console.log).not.toHaveBeenCalled(); - } - - done(); - }); - }); -}); diff --git a/src/__tests__/sdk.test.ts b/src/__tests__/sdk.test.ts new file mode 100644 index 0000000..2ce308f --- /dev/null +++ b/src/__tests__/sdk.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, test, vi } from "vitest"; +import { SDK } from "../sdk"; +import type { ClientOptions } from "../interfaces"; + +const defaultOpts: ClientOptions = { + agent: "test-agent", + apiKey: "test-api-key", + application: "test-app", + endpoint: "https://test.absmartly.io/v1", + environment: "test", +}; + +describe("SDK", () => { + test("creates SDK instance", () => { + const sdk = new SDK(defaultOpts); + expect(sdk).toBeInstanceOf(SDK); + }); + + test("getClient returns client", () => { + const sdk = new SDK(defaultOpts); + expect(sdk.getClient()).toBeDefined(); + }); + + test("get/set event logger", () => { + const sdk = new SDK(defaultOpts); + const logger = vi.fn(); + sdk.setEventLogger(logger); + expect(sdk.getEventLogger()).toBe(logger); + }); + + test("default event logger logs errors", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const error = new Error("test"); + SDK.defaultEventLogger({} as never, "error", error); + expect(consoleSpy).toHaveBeenCalledWith(error); + consoleSpy.mockRestore(); + }); + + test("createContext validates unit types", () => { + const sdk = new SDK(defaultOpts); + expect(() => sdk.createContext({ units: { session_id: true as unknown as string } })).toThrow( + "Unit 'session_id' UID is of unsupported type 'boolean'. UID must be one of ['string', 'number']", + ); + }); + + test("createContext validates empty string units", () => { + const sdk = new SDK(defaultOpts); + expect(() => sdk.createContext({ units: { session_id: "" } })).toThrow( + "Unit 'session_id' UID length must be >= 1", + ); + }); + + test("createContext returns Context instance", () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }), + ); + const sdk = new SDK(defaultOpts); + const context = sdk.createContext({ units: { session_id: "abc" } }); + expect(context).toBeDefined(); + vi.restoreAllMocks(); + }); + + test("createContextWith accepts pre-fetched data", () => { + const sdk = new SDK(defaultOpts); + const context = sdk.createContextWith({ units: { session_id: "abc" } }, { experiments: [] }); + expect(context).toBeDefined(); + expect(context.isReady()).toBe(true); + }); + + test("get/set context publisher", () => { + const sdk = new SDK(defaultOpts); + const publisher = { publish: vi.fn() }; + sdk.setContextPublisher(publisher as never); + expect(sdk.getContextPublisher()).toBe(publisher); + }); + + test("get/set context data provider", () => { + const sdk = new SDK(defaultOpts); + const provider = { getContextData: vi.fn() }; + sdk.setContextDataProvider(provider as never); + expect(sdk.getContextDataProvider()).toBe(provider); + }); + + test("accepts custom client via SDKOptions", () => { + const mockClient = { + getContext: vi.fn().mockResolvedValue({ experiments: [] }), + publish: vi.fn().mockResolvedValue("ok"), + getAgent: vi.fn().mockReturnValue("custom-agent"), + getApplication: vi.fn().mockReturnValue({ name: "custom-app", version: 0 }), + getEnvironment: vi.fn().mockReturnValue("custom-env"), + }; + const sdk = new SDK({ ...defaultOpts, client: mockClient } as any); + expect(sdk.getClient()).toBe(mockClient); + }); + + test("event logger fires ready event on createContextWith", () => { + const logger = vi.fn(); + const sdk = new SDK({ ...defaultOpts, eventLogger: logger }); + const context = sdk.createContextWith({ units: { session_id: "abc" } }, { experiments: [] }); + expect(logger).toHaveBeenCalledWith(context, "ready", expect.anything()); + }); + + test("forwards fetchImpl and AbortControllerImpl to Client", async () => { + const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); + let abortControllerInstances = 0; + class FakeAbortController { + signal = { addEventListener: vi.fn(), removeEventListener: vi.fn() } as unknown as AbortSignal; + constructor() { + abortControllerInstances += 1; + } + abort = vi.fn(); + } + + const sdk = new SDK({ + ...defaultOpts, + fetchImpl, + AbortControllerImpl: FakeAbortController as unknown as typeof AbortController, + }); + const context = sdk.createContext({ units: { session_id: "abc" } }); + await context.ready(); + + expect(fetchImpl).toHaveBeenCalledTimes(1); + expect(abortControllerInstances).toBe(1); + }); +}); + diff --git a/src/__tests__/utils.test.js b/src/__tests__/utils.test.js deleted file mode 100644 index 39cf8d4..0000000 --- a/src/__tests__/utils.test.js +++ /dev/null @@ -1,343 +0,0 @@ -import { - arrayEqualsShallow, - base64UrlNoPadding, - chooseVariant, - hashUnit, - isEqualsDeep, - isNumeric, - isObject, - isPromise, - stringToUint8Array, -} from "../utils"; - -class SomeClass {} - -describe("isObject()", () => { - it("should return true with objects", (done) => { - expect(isObject({})).toBe(true); - expect(isObject(new Object())).toBe(true); - - done(); - }); - - it("should return false with common non-object", (done) => { - expect(isObject(null)).toBe(false); - expect(isObject([])).toBe(false); - expect(isObject(1)).toBe(false); - expect(isObject(true)).toBe(false); - expect(isObject(false)).toBe(false); - expect(isObject("str")).toBe(false); - expect(isObject(new Uint8Array(1))).toBe(false); - expect(isObject(new Map())).toBe(false); - expect(isObject(new SomeClass())).toBe(false); - - done(); - }); -}); - -describe("isNumeric()", () => { - it("should return true with numbers", (done) => { - expect(isNumeric(1)).toBe(true); - expect(isNumeric(1.5)).toBe(true); - - done(); - }); - - it("should return false with non-numbers", (done) => { - expect(isNumeric(null)).toBe(false); - expect(isNumeric("1")).toBe(false); - expect(isNumeric(true)).toBe(false); - expect(isNumeric(false)).toBe(false); - expect(isNumeric([])).toBe(false); - expect(isNumeric({})).toBe(false); - expect(isNumeric(new Object())).toBe(false); - expect(isNumeric(new Map())).toBe(false); - - done(); - }); -}); - -describe("isPromise()", () => { - it("should return true with a thenable object", (done) => { - expect( - isPromise({ - then() {}, - }) - ).toBe(true); - - done(); - }); - - it("should return false with non objects or objects without then function", (done) => { - expect(isPromise(null)).toBe(false); - expect(isPromise([])).toBe(false); - expect(isPromise({})).toBe(false); - expect(isPromise(1)).toBe(false); - expect(isPromise(true)).toBe(false); - expect(isPromise(false)).toBe(false); - expect(isPromise("str")).toBe(false); - expect(isPromise(new Uint8Array(1))).toBe(false); - - done(); - }); -}); - -describe("isEqualsDeep()", () => { - it("should return true with two NaNs", (done) => { - expect(isEqualsDeep(Number.NaN, Number.NaN)).toBe(true); - - done(); - }); - - it("should return true iff basic primitives are equal", (done) => { - expect(isEqualsDeep(0, 0)).toBe(true); - expect(isEqualsDeep(1, 1)).toBe(true); - expect(isEqualsDeep(1.5, 1.5)).toBe(true); - expect(isEqualsDeep("", "")).toBe(true); - expect(isEqualsDeep("abc", "abc")).toBe(true); - expect(isEqualsDeep(true, true)).toBe(true); - expect(isEqualsDeep(false, false)).toBe(true); - - expect(isEqualsDeep(0, 1)).toBe(false); - expect(isEqualsDeep(1, 1.5)).toBe(false); - expect(isEqualsDeep(1.5, "")).toBe(false); - expect(isEqualsDeep("", "abc")).toBe(false); - expect(isEqualsDeep("abc", true)).toBe(false); - expect(isEqualsDeep(true, false)).toBe(false); - expect(isEqualsDeep(false, 0)).toBe(false); - - done(); - }); - - it("should return true iff arrays are equal", (done) => { - expect(isEqualsDeep([1, 2, 3], [1, 2, 3])).toBe(true); - expect(isEqualsDeep([1, 2, 3], [3, 2, 1])).toBe(false); - - expect(isEqualsDeep([], [1])).toBe(false); - expect(isEqualsDeep([1], [])).toBe(false); - - expect(isEqualsDeep([1, 2, [3, 4, [5, 6]]], [1, 2, [3, 4, [5, 6]]])).toBe(true); - expect(isEqualsDeep([1, 2, [3, 4, [5, 6]]], [1, 2, [3, 4, [5, 9]]])).toBe(false); - - expect(isEqualsDeep(["a", "b", ["c", "d", ["e", "f"]]], ["a", "b", ["c", "d", ["e", "f"]]])).toBe(true); - expect(isEqualsDeep(["a", "b", ["c", "d", ["e", "f"]]], ["a", "b", ["c", "d", ["e", "x"]]])).toBe(false); - - done(); - }); - - it("should return true iff arrays with circular references are equal", (done) => { - const a = [1, 2, [3, 4, [5, 6]]]; - a[1] = a; - - const b = [1, 2, [3, 4, [5, 6]]]; - b[1] = b; - - expect(isEqualsDeep(a, b)).toBe(true); - - a[2][2][0] = a; - b[2][2][0] = b; - - expect(isEqualsDeep(a, b)).toBe(true); - - b[2][2][1] = 9; - - expect(isEqualsDeep(a, b)).toBe(false); - - done(); - }); - - it("should return true iff objects are equal", (done) => { - expect(isEqualsDeep({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe(true); - expect(isEqualsDeep({ a: 1, b: 2, c: 3 }, { c: 3, b: 2, a: 1 })).toBe(true); - expect(isEqualsDeep({ a: 1, b: 2, c: 3 }, { a: 3, b: 2, c: 1 })).toBe(false); - - expect(isEqualsDeep({}, { a: 1 })).toBe(false); - expect(isEqualsDeep({ a: 1 }, {})).toBe(false); - - expect( - isEqualsDeep( - { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } }, - { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } } - ) - ).toBe(true); - expect( - isEqualsDeep( - { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } }, - { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 9 } } } - ) - ).toBe(false); - - expect( - isEqualsDeep( - { a: "a", b: "b", c: { d: "c", e: "d", f: { g: "e", h: "f" } } }, - { a: "a", b: "b", c: { d: "c", e: "d", f: { g: "e", h: "f" } } } - ) - ).toBe(true); - expect( - isEqualsDeep( - { a: "a", b: "b", c: { d: "c", e: "d", f: { g: "e", h: "f" } } }, - { a: "a", b: "b", c: { d: "c", e: "d", f: { g: "e", h: "x" } } } - ) - ).toBe(false); - - done(); - }); - - it("should return true iff arrays with circular references are equal", (done) => { - const a = { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } }; - a.b = a; - - const b = { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } }; - b.b = b; - - expect(isEqualsDeep(a, b)).toBe(true); - - a.c.f.g = a; - b.c.f.g = b; - - expect(isEqualsDeep(a, b)).toBe(true); - - b.c.f.h = 9; - - expect(isEqualsDeep(a, b)).toBe(false); - - done(); - }); -}); - -describe("arrayEqualsShallow()", () => { - it("should return true only with equal arrays", (done) => { - expect(arrayEqualsShallow([], [])).toEqual(true); - expect(arrayEqualsShallow([1], [1])).toEqual(true); - expect(arrayEqualsShallow([1, 2, 3], [1, 2, 3])).toEqual(true); - expect(arrayEqualsShallow([0.5, 1.0, 1.5], [0.5, 1.0, 1.5])).toEqual(true); - - expect(arrayEqualsShallow([1], ["1"])).toEqual(false); - expect(arrayEqualsShallow([1, 2, 3], [1, 2, "3"])).toEqual(false); - expect(arrayEqualsShallow([1], [])).toEqual(false); - expect(arrayEqualsShallow([], [1])).toEqual(false); - expect(arrayEqualsShallow([1, 2, 3], [1, 2])).toEqual(false); - expect(arrayEqualsShallow([1, 2], [1, 2, 3])).toEqual(false); - - done(); - }); -}); - -describe("hashUnit()", () => { - it("should return matching hashes", (done) => { - expect(hashUnit("4a42766ca6313d26f49985e799ff4f3790fb86efa0fce46edb3ea8fbf1ea3408")).toBe( - "H2jvj6o9YcAgNdhKqEbtWw" - ); - expect(hashUnit("bleh@absmarty.com")).toBe("DRgslOje35bZMmpaohQjkA"); - expect(hashUnit("açb↓c")).toBe("LxcqH5VC15rXfWfA_smreg"); - expect(hashUnit("testy")).toBe("K5I_V6RgP8c6sYKz-TVn8g"); - expect(hashUnit(123456778999)).toBe("K4uy4bTeCy34W97lmceVRg"); - - done(); - }); -}); - -describe("chooseVariant()", () => { - it("should return correct variant", (done) => { - expect(chooseVariant([0.0, 1.0], 0.0)).toBe(1); - expect(chooseVariant([0.0, 1.0], 0.5)).toBe(1); - expect(chooseVariant([0.0, 1.0], 1.0)).toBe(1); - - expect(chooseVariant([1.0, 0.0], 0.0)).toBe(0); - expect(chooseVariant([1.0, 0.0], 0.5)).toBe(0); - expect(chooseVariant([1.0, 0.0], 1.0)).toBe(1); - - expect(chooseVariant([0.5, 0.5], 0.0)).toBe(0); - expect(chooseVariant([0.5, 0.5], 0.25)).toBe(0); - expect(chooseVariant([0.5, 0.5], 0.49999999)).toBe(0); - expect(chooseVariant([0.5, 0.5], 0.5)).toBe(1); - expect(chooseVariant([0.5, 0.5], 0.50000001)).toBe(1); - expect(chooseVariant([0.5, 0.5], 0.75)).toBe(1); - expect(chooseVariant([0.5, 0.5], 1.0)).toBe(1); - - expect(chooseVariant([0.333, 0.333, 0.334], 0.0)).toBe(0); - expect(chooseVariant([0.333, 0.333, 0.334], 0.25)).toBe(0); - expect(chooseVariant([0.333, 0.333, 0.334], 0.33299999)).toBe(0); - expect(chooseVariant([0.333, 0.333, 0.334], 0.333)).toBe(1); - expect(chooseVariant([0.333, 0.333, 0.334], 0.33300001)).toBe(1); - expect(chooseVariant([0.333, 0.333, 0.334], 0.5)).toBe(1); - expect(chooseVariant([0.333, 0.333, 0.334], 0.66599999)).toBe(1); - expect(chooseVariant([0.333, 0.333, 0.334], 0.666)).toBe(2); - expect(chooseVariant([0.333, 0.333, 0.334], 0.66600001)).toBe(2); - expect(chooseVariant([0.333, 0.333, 0.334], 0.75)).toBe(2); - expect(chooseVariant([0.333, 0.333, 0.334], 1.0)).toBe(2); - - done(); - }); -}); - -describe("stringToUint8Array()", () => { - it("should encode special characters to utf8", (done) => { - const testCases = [ - ["", Uint8Array.from([])], - [" ", Uint8Array.from([32])], - ["a", Uint8Array.from([97])], - ["ab", Uint8Array.from([97, 98])], - ["abc", Uint8Array.from([97, 98, 99])], - ["abcd", Uint8Array.from([97, 98, 99, 100])], - ["ç", Uint8Array.from([195, 167])], - ["aç", Uint8Array.from([97, 195, 167])], - ["çb", Uint8Array.from([195, 167, 98])], - ["açb", Uint8Array.from([97, 195, 167, 98])], - ["↓", Uint8Array.from([226, 134, 147])], - ["a↓", Uint8Array.from([97, 226, 134, 147])], - ["↓b", Uint8Array.from([226, 134, 147, 98])], - ["a↓b", Uint8Array.from([97, 226, 134, 147, 98])], - ["aç↓", Uint8Array.from([97, 195, 167, 226, 134, 147])], - ["aç↓b", Uint8Array.from([97, 195, 167, 226, 134, 147, 98])], - ["açb↓c", Uint8Array.from([97, 195, 167, 98, 226, 134, 147, 99])], - ]; - - for (const testCase of testCases) { - const array = stringToUint8Array(testCase[0]); - const expected = testCase[1]; - expect(array.length).toBe(expected.length); - for (let i = 0; i < array.length; ++i) { - expect(array[i]).toBe(expected[i]); - } - } - done(); - }); -}); - -describe("base64UrlNoPadding()", () => { - it("should match known encodings", (done) => { - const testCases = [ - ["", ""], - [" ", "IA"], - ["t", "dA"], - ["te", "dGU"], - ["tes", "dGVz"], - ["test", "dGVzdA"], - ["testy", "dGVzdHk"], - ["testy1", "dGVzdHkx"], - ["testy12", "dGVzdHkxMg"], - ["testy123", "dGVzdHkxMjM"], - ["special characters açb↓c", "c3BlY2lhbCBjaGFyYWN0ZXJzIGHDp2LihpNj"], - [ - "The quick brown fox jumps over the lazy dog", - "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw", - ], - [ - "The quick brown fox jumps over the lazy dog and eats a pie", - "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZyBhbmQgZWF0cyBhIHBpZQ", - ], - [ - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLg", - ], - ]; - - testCases.forEach((testCase) => { - const bytes = stringToUint8Array(testCase[0]); - expect(base64UrlNoPadding(bytes)).toEqual(testCase[1]); - }); - - done(); - }); -}); diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts new file mode 100644 index 0000000..5af8c4f --- /dev/null +++ b/src/__tests__/utils.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, test } from "vitest"; +import { arrayEqualsShallow, chooseVariant, isEqualsDeep, isObject, isPromise } from "../utils"; + +describe("isObject", () => { + test("returns true for plain objects", () => { + expect(isObject({})).toBe(true); + expect(isObject({ a: 1 })).toBe(true); + }); + + test("returns false for non-objects", () => { + expect(isObject(null)).toBe(false); + expect(isObject(undefined)).toBe(false); + expect(isObject(42)).toBe(false); + expect(isObject("str")).toBe(false); + expect(isObject([])).toBe(false); + expect(isObject(new Date())).toBe(false); + }); +}); + +describe("isPromise", () => { + test("returns true for promises", () => { + expect(isPromise(Promise.resolve())).toBe(true); + expect(isPromise({ then: () => {} })).toBe(true); + }); + + test("returns false for non-promises", () => { + expect(isPromise(null)).toBe(false); + expect(isPromise(undefined)).toBe(false); + expect(isPromise({})).toBe(false); + expect(isPromise(42)).toBe(false); + }); +}); + +describe("isEqualsDeep", () => { + test("primitives", () => { + expect(isEqualsDeep(1, 1)).toBe(true); + expect(isEqualsDeep(1, 2)).toBe(false); + expect(isEqualsDeep("a", "a")).toBe(true); + expect(isEqualsDeep("a", "b")).toBe(false); + expect(isEqualsDeep(true, true)).toBe(true); + expect(isEqualsDeep(true, false)).toBe(false); + }); + + test("NaN", () => { + expect(isEqualsDeep(NaN, NaN)).toBe(true); + }); + + test("arrays", () => { + expect(isEqualsDeep([1, 2, 3], [1, 2, 3])).toBe(true); + expect(isEqualsDeep([1, 2, 3], [1, 2, 4])).toBe(false); + expect(isEqualsDeep([1, 2], [1, 2, 3])).toBe(false); + }); + + test("objects", () => { + expect(isEqualsDeep({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true); + expect(isEqualsDeep({ a: 1 }, { a: 2 })).toBe(false); + expect(isEqualsDeep({ a: 1 }, { a: 1, b: 2 })).toBe(false); + }); + + test("nested structures", () => { + expect(isEqualsDeep({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })).toBe(true); + expect(isEqualsDeep({ a: [1, { b: 2 }] }, { a: [1, { b: 3 }] })).toBe(false); + }); + + test("different types", () => { + expect(isEqualsDeep(1, "1")).toBe(false); + expect(isEqualsDeep([], {})).toBe(false); + }); +}); + +describe("arrayEqualsShallow", () => { + test("same reference", () => { + const arr = [1, 2, 3]; + expect(arrayEqualsShallow(arr, arr)).toBe(true); + }); + + test("equal arrays", () => { + expect(arrayEqualsShallow([1, 2, 3], [1, 2, 3])).toBe(true); + }); + + test("different arrays", () => { + expect(arrayEqualsShallow([1, 2, 3], [1, 2, 4])).toBe(false); + }); + + test("different lengths", () => { + expect(arrayEqualsShallow([1, 2], [1, 2, 3])).toBe(false); + }); + + test("both undefined", () => { + expect(arrayEqualsShallow(undefined, undefined)).toBe(true); + }); + + test("both null", () => { + expect(arrayEqualsShallow(null, null)).toBe(true); + }); + + test("array vs null returns false", () => { + expect(arrayEqualsShallow([1], null)).toBe(false); + }); + + test("null vs array returns false", () => { + expect(arrayEqualsShallow(null, [1])).toBe(false); + }); +}); + +describe("chooseVariant", () => { + test("selects correct variant based on probability", () => { + expect(chooseVariant([0.5, 0.5], 0.0)).toBe(0); + expect(chooseVariant([0.5, 0.5], 0.4)).toBe(0); + expect(chooseVariant([0.5, 0.5], 0.5)).toBe(1); + expect(chooseVariant([0.5, 0.5], 0.9)).toBe(1); + }); + + test("three-way split", () => { + expect(chooseVariant([0.33, 0.33, 0.34], 0.0)).toBe(0); + expect(chooseVariant([0.33, 0.33, 0.34], 0.3)).toBe(0); + expect(chooseVariant([0.33, 0.33, 0.34], 0.33)).toBe(1); + expect(chooseVariant([0.33, 0.33, 0.34], 0.65)).toBe(1); + expect(chooseVariant([0.33, 0.33, 0.34], 0.66)).toBe(2); + }); + + test("returns last variant for probability >= 1", () => { + expect(chooseVariant([0.5, 0.5], 1.0)).toBe(1); + }); + + test("zero-weight first variant [0.0, 1.0] with prob=0.0 returns 1", () => { + expect(chooseVariant([0.0, 1.0], 0.0)).toBe(1); + }); +}); diff --git a/src/abort-controller-shim.ts b/src/abort-controller-shim.ts deleted file mode 100644 index 1456e47..0000000 --- a/src/abort-controller-shim.ts +++ /dev/null @@ -1,90 +0,0 @@ -export type AbortControllerEvents = { - [key: string]: Array<() => unknown>; -}; - -// eslint-disable-next-line no-shadow -export class AbortSignal { - aborted = false; - private readonly _events: AbortControllerEvents; - - constructor() { - this._events = {}; - } - - addEventListener(type: string, listener: () => void) { - let listeners = this._events[type]; - if (!listeners) { - listeners = []; - this._events[type] = listeners; - } - listeners.push(listener); - } - - removeEventListener(type: string, listener: () => void) { - const listeners = this._events[type]; - if (listeners) { - const index = listeners.findIndex((x) => x === listener); - if (index !== -1) { - listeners.splice(index, 1); - if (listeners.length === 0) { - delete this._events[type]; - } - } - } - } - - dispatchEvent(evt: { type: string }) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this[`on${evt.type}`] && this[`on${evt.type}`](evt); - const listeners = this._events[evt.type]; - if (listeners) { - for (const listener of listeners) { - listener.call(null, evt); - } - } - } - - toString() { - return "[object AbortSignal]"; - } -} - -// eslint-disable-next-line no-shadow -export class AbortController { - signal = new AbortSignal(); - - abort() { - let evt: Event | { type: string; bubbles: boolean; cancelable: boolean }; - try { - evt = new Event("abort"); - } catch (e) { - evt = { - type: "abort", - bubbles: false, - cancelable: false, - }; - } - - this.signal.aborted = true; - this.signal.dispatchEvent(evt); - } - - toString() { - return "[object AbortController]"; - } -} - -if (typeof Symbol !== "undefined" && Symbol.toStringTag !== undefined) { - Object.defineProperty(AbortSignal.prototype, Symbol.toStringTag, { - configurable: true, - value: "AbortSignal", - }); - - Object.defineProperty(AbortController.prototype, Symbol.toStringTag, { - configurable: true, - value: "AbortController", - }); -} - -export default AbortController; diff --git a/src/abort.ts b/src/abort.ts deleted file mode 100644 index 563ff75..0000000 --- a/src/abort.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { isLongLivedApp, isWorker } from "./utils"; -import AbortControllerShim from "./abort-controller-shim"; - -// eslint-disable-next-line no-shadow -export const AbortController = - isLongLivedApp() && window.AbortController - ? window.AbortController - : isWorker() && self.AbortController - ? self.AbortController - : global && global.AbortController - ? global.AbortController - : AbortControllerShim; diff --git a/src/algorithm.ts b/src/algorithm.ts index d4c4857..bfb83fb 100644 --- a/src/algorithm.ts +++ b/src/algorithm.ts @@ -1,13 +1,16 @@ -export const insertUniqueSorted = (arr: TData[], value: TData, isSorted: (a: TData, b: TData) => boolean) => { +export function insertUniqueSorted( + arr: TData[], + value: TData, + isSorted: (a: TData, b: TData) => boolean, +): void { let left = 0; let right = arr.length - 1; while (left <= right) { const mid = Math.floor(left + (right - left) / 2); - - if (isSorted(arr[mid], value)) { + if (isSorted(arr[mid]!, value)) { left = mid + 1; - } else if (isSorted(value, arr[mid])) { + } else if (isSorted(value, arr[mid]!)) { right = mid - 1; } else { return; @@ -15,4 +18,4 @@ export const insertUniqueSorted = (arr: TData[], value: TData, isSorted: } arr.splice(left, 0, value); -}; +} diff --git a/src/assigner.ts b/src/assigner.ts index e744af0..f1b8ec0 100644 --- a/src/assigner.ts +++ b/src/assigner.ts @@ -1,25 +1,26 @@ -import { chooseVariant, stringToUint8Array } from "./utils"; -import { murmur3_32 } from "./murmur3_32"; +import { chooseVariant } from "./utils"; +import { stringToUint8Array } from "./hashing"; +import { murmur3_32 } from "./murmur3"; export class VariantAssigner { private readonly _unitHash: number; + constructor(unit: string) { this._unitHash = murmur3_32(stringToUint8Array(unit).buffer); } - assign(split: number[], seedHi: number, seedLo: number) { + assign(split: number[], seedHi: number, seedLo: number): number { const prob = this._probability(seedHi, seedLo); return chooseVariant(split, prob); } - private _probability(seedHi: number, seedLo: number) { + private _probability(seedHi: number, seedLo: number): number { const key = this._unitHash; const buffer = new ArrayBuffer(12); const view = new DataView(buffer); view.setUint32(0, seedLo, true); view.setUint32(4, seedHi, true); view.setUint32(8, key, true); - return murmur3_32(buffer) * (1.0 / 0xffffffff); } } diff --git a/src/browser.ts b/src/browser.ts deleted file mode 100644 index c3399d1..0000000 --- a/src/browser.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Context from "./context"; -import SDK from "./sdk"; -import { mergeConfig } from "./config"; -import { ContextDataProvider } from "./provider"; -import { ContextPublisher } from "./publisher"; -// eslint-disable-next-line no-shadow -import { AbortController } from "./abort"; - -export default { mergeConfig, AbortController, Context, ContextDataProvider, ContextPublisher, SDK }; diff --git a/src/client.ts b/src/client.ts index 3f934bb..c01d45e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,61 +1,22 @@ -import fetch from "./fetch"; // eslint-disable-line no-shadow -// eslint-disable-next-line no-shadow -import { AbortController } from "./abort"; -// eslint-disable-next-line no-shadow import { AbortError, RetryError, TimeoutError } from "./errors"; - -import { AbortSignal as ABsmartlyAbortSignal } from "./abort-controller-shim"; -import { ContextOptions, ContextParams } from "./context"; -import { PublishParams } from "./publisher"; - -export type FetchResponse = { - status: number; - ok: boolean; - text: () => Promise; - statusText: string; - json: () => Promise; -}; - -export type ClientRequestOptions = { - query?: Record; - path: string; - method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; - body?: Record; - auth?: boolean; - signal?: AbortSignal | ABsmartlyAbortSignal; - timeout?: number; -}; - -export type ApplicationObject = { name: string; version: number | string }; - -export type ClientOptions = { - agent?: string; - apiKey: string; - application: string | ApplicationObject; - endpoint: string; - environment: string; - retries?: number; - timeout?: number; - keepalive?: boolean; -}; +import type { ApplicationObject, ContextData, PublishParams } from "./models"; +import type { ContextParams } from "./interfaces"; +import type { Client, ClientOptions, ClientRequestOptions } from "./interfaces"; type NormalizedClientOptions = Omit, "application"> & { application: ApplicationObject; }; -export default class Client { +export class DefaultClient implements Client { private readonly _opts: NormalizedClientOptions; private readonly _delay: number; + private readonly _fetchImpl: typeof fetch; + private readonly _AbortControllerImpl: typeof AbortController; constructor(opts: ClientOptions) { const merged: Record = Object.assign( - { - agent: "javascript-client", - retries: 5, - timeout: 3000, - keepalive: true, - }, - opts + { agent: "javascript-client", retries: 5, timeout: 3000, keepalive: true }, + opts, ); for (const key of ["agent", "application", "apiKey", "endpoint", "environment"]) { @@ -63,9 +24,7 @@ export default class Client { const value = merged[key]; if (typeof value !== "string" || (value as string).length === 0) { if (key === "application") { - if (value !== null && typeof value === "object" && "name" in (value as object)) { - continue; - } + if (value !== null && typeof value === "object" && "name" in (value as object)) continue; } throw new Error(`Invalid '${key}' in options argument`); } @@ -75,17 +34,17 @@ export default class Client { } if (typeof merged.application === "string") { - merged.application = { - name: merged.application, - version: 0, - }; + merged.application = { name: merged.application, version: 0 }; } this._opts = merged as unknown as NormalizedClientOptions; this._delay = 50; + this._fetchImpl = (opts.fetchImpl ?? globalThis.fetch).bind(globalThis) as typeof fetch; + this._AbortControllerImpl = opts.AbortControllerImpl ?? AbortController; } - getContext(options?: Partial) { + + getContext(options?: Partial): Promise { return this.getUnauthed({ ...options, path: "/context", @@ -93,23 +52,15 @@ export default class Client { application: this._opts.application.name, environment: this._opts.environment, }, - }); + }) as Promise; } - createContext(params: ContextParams, options: ContextOptions) { - const body = { - units: params.units, - }; - - return this.post({ - ...options, - path: "/context", - body, - }); + createContext(params: ContextParams): Promise { + return this.post({ path: "/context", body: { units: params.units } }); } - publish(params: PublishParams, options?: ClientRequestOptions) { - const body: PublishParams = { + publish(params: PublishParams, options?: ClientRequestOptions): Promise { + const body: Record = { units: params.units, hashed: params.hashed, publishedAt: params.publishedAt || Date.now(), @@ -119,38 +70,32 @@ export default class Client { if (Array.isArray(params.goals) && params.goals.length > 0) { body.goals = params.goals; } - if (Array.isArray(params.exposures) && params.exposures.length > 0) { body.exposures = params.exposures; } - if (Array.isArray(params.attributes) && params.attributes.length > 0) { body.attributes = params.attributes; } - return this.put({ - ...options, - path: "/context", - body, - }); + return this.put({ ...options, path: "/context", body }); } - request(options: ClientRequestOptions) { + request(options: ClientRequestOptions): Promise { let url = `${this._opts.endpoint}${options.path}`; if (options.query) { const keys = Object.keys(options.query); if (keys.length > 0) { const encoded = keys - .map((k) => (options.query ? `${k}=${encodeURIComponent(options.query[k])}` : null)) + .map((k) => (options.query ? `${k}=${encodeURIComponent(options.query[k]!)}` : null)) .join("&"); url = `${url}?${encoded}`; } } - const controller = new AbortController(); + const controller = new this._AbortControllerImpl(); - const tryOnce = () => { - const opts: Record = { + const tryOnce = (): Promise => { + const opts: RequestInit = { method: options.method, body: options.body !== undefined ? JSON.stringify(options.body, null, 0) : undefined, signal: controller.signal, @@ -164,34 +109,27 @@ export default class Client { "X-Agent": this._opts.agent, "X-Environment": this._opts.environment, "X-Application": this._opts.application.name, - "X-Application-Version": this._opts.application.version, + "X-Application-Version": String(this._opts.application.version), }; } - return fetch(url, opts).then((response: FetchResponse) => { + return this._fetchImpl(url, opts).then((response: Response) => { if (!response.ok) { const bail = response.status >= 400 && response.status < 500; return response.text().then((text: string) => { const error: Error & { _bail?: boolean } = new Error( - text !== null && text.length > 0 ? text : response.statusText + text !== null && text.length > 0 ? text : response.statusText, ); error._bail = bail; - return Promise.reject(error); }); } - return response.json(); }); }; type WaitFn = ((ms: number) => Promise) & { reject?: (reason: AbortError) => void }; - type TryWithFn = (( - retries: number, - timeout: number, - tries?: number, - waited?: number - ) => ReturnType) & { + type TryWithFn = ((retries: number, timeout: number, tries?: number, waited?: number) => Promise) & { timedout?: boolean; }; @@ -212,24 +150,15 @@ export default class Client { delete tryWith.timedout; return tryOnce().catch((reason: Error & { _bail?: boolean }) => { - console.warn(reason); - - if (reason._bail || retries <= 0) { - throw new Error(reason.message); - } else if (tries >= retries) { - throw new RetryError(tries, reason, url); - } else if (waited >= timeout || reason.name === "AbortError") { - if (tryWith.timedout) { - throw new TimeoutError(timeout); - } - + if (reason._bail || retries <= 0) throw new Error(reason.message); + if (tries >= retries) throw new RetryError(tries, reason, url); + if (waited >= timeout || reason.name === "AbortError") { + if (tryWith.timedout) throw new TimeoutError(timeout); throw reason; } let delay = (1 << tries) * this._delay + 0.5 * Math.random() * this._delay; - if (waited + delay > timeout) { - delay = timeout - waited; - } + if (waited + delay > timeout) delay = timeout - waited; return wait(delay).then(() => tryWith(retries, timeout, tries + 1, waited + delay)); }); @@ -253,7 +182,7 @@ export default class Client { ? setTimeout(() => { tryWith.timedout = true; abort(); - }, timeout) + }, timeout) : 0; const finalCleanUp = () => { @@ -263,8 +192,8 @@ export default class Client { } }; - return tryWith(this._opts.retries ?? 5, this._opts.timeout ?? 3000) - .then((value: string) => { + return tryWith(this._opts.retries ?? 5, timeout || this._opts.timeout || 3000) + .then((value) => { finalCleanUp(); return value; }) @@ -274,20 +203,12 @@ export default class Client { }); } - post(options: ClientRequestOptions) { - return this.request({ - ...options, - auth: true, - method: "POST", - }); + post(options: ClientRequestOptions): Promise { + return this.request({ ...options, auth: true, method: "POST" }); } - put(options: ClientRequestOptions) { - return this.request({ - ...options, - auth: true, - method: "PUT", - }); + put(options: ClientRequestOptions): Promise { + return this.request({ ...options, auth: true, method: "PUT" }); } getAgent(): string { @@ -302,10 +223,7 @@ export default class Client { return this._opts.environment; } - getUnauthed(options: ClientRequestOptions) { - return this.request({ - ...options, - method: "GET", - }); + getUnauthed(options: ClientRequestOptions): Promise { + return this.request({ ...options, method: "GET" }); } } diff --git a/src/config.ts b/src/config.ts index 1be84c7..11d844d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,25 +1,43 @@ -import clone from "rfdc/default"; -import Context from "./context"; import { isObject } from "./utils"; -export function mergeConfig(context: Context, previousConfig: Record) { - const merged = clone(previousConfig); +interface ConfigContext { + variableKeys(): Record; + variableValue(key: string, defaultValue: unknown): unknown; +} + +function deepClone(value: T): T { + if (Array.isArray(value)) { + return value.map((item) => deepClone(item)) as T; + } + + if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) { + const result: Record = {}; + for (const [key, item] of Object.entries(value)) { + result[key] = deepClone(item); + } + return result as T; + } + + return value; +} + +export function mergeConfig(context: ConfigContext, previousConfig: Record): Record { + const merged = deepClone(previousConfig); const keys = context.variableKeys(); for (const [variableKey, experimentName] of Object.entries(keys)) { - let target = merged; + let target: Record | undefined = merged; const frags = variableKey.split("."); for (let index = 0; index < frags.length; ++index) { - const frag = frags[index]; + const frag = frags[index]!; + + if (target === undefined) break; if (`_${frag}_setter` in target) { console.error( - `Config key '${frags.slice(0, index + 1).join(".")}' already set by experiment '${ - target[`_${frag}_setter`] - }'.` + `Config key '${frags.slice(0, index + 1).join(".")}' already set by experiment '${target[`_${frag}_setter`]}'.`, ); - target = undefined; break; } @@ -28,14 +46,12 @@ export function mergeConfig(context: Context, previousConfig: Record; } else { - target = target[frag]; + target = target[frag] as Record; } } } @@ -43,15 +59,9 @@ export function mergeConfig(context: Context, previousConfig: Record { - return context.variableValue(variableKey, defaultValue); - }, + get: () => context.variableValue(variableKey, defaultValue), }); } } diff --git a/src/context.ts b/src/context.ts index c862b3d..ae52b3c 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,116 +1,46 @@ -import { arrayEqualsShallow, hashUnit, isObject, isPromise } from "./utils"; +import { hashUnit } from "./hashing"; import { VariantAssigner } from "./assigner"; import { AudienceMatcher } from "./matcher"; import { insertUniqueSorted } from "./algorithm"; -import SDK, { EventLogger, EventName } from "./sdk"; -import { ContextPublisher, PublishParams } from "./publisher"; -import { ContextDataProvider } from "./provider"; -import { ClientRequestOptions } from "./client"; +import { arrayEqualsShallow, isObject, isPromise } from "./utils"; import { SDK_VERSION } from "./version"; +import { ContextNotReadyError, ContextFinalizedError } from "./errors"; +import type { + Assignment, + Attribute, + ContextData, + Experiment, + ExperimentData, + Exposure, + GoalAchievement, + Units, + PublishParams, + JSONValue, + CustomFieldValue, +} from "./models"; +import type { + ClientRequestOptions, + ContextDataProvider, + ContextPublisher, + EventLogger, + EventName, + ContextParams, +} from "./interfaces"; + +interface SDKLike { + getClient(): { + getAgent(): string; + getApplication(): { name: string; version: number | string }; + getEnvironment(): string; + publish(request: PublishParams, options?: ClientRequestOptions): Promise; + getContext(options?: Partial): Promise; + }; + getContextPublisher(): ContextPublisher; + getContextDataProvider(): ContextDataProvider; + getEventLogger(): EventLogger; +} -type JSONPrimitive = string | number | boolean | null; -type JSONObject = { [key: string]: JSONValue }; -type JSONArray = JSONValue[]; -type JSONValue = JSONPrimitive | JSONObject | JSONArray; - -type CustomFieldValueType = "text" | "string" | "number" | "json" | "boolean"; - -type CustomFieldValue = { - name: string; - value: string; - type: CustomFieldValueType; -}; - -export type ExperimentData = { - id: number; - name: string; - unitType: string | null; - iteration: number; - fullOnVariant: number; - trafficSplit: number[]; - trafficSeedHi: number; - trafficSeedLo: number; - audience: string; - audienceStrict: boolean; - split: number[]; - seedHi: number; - seedLo: number; - variants: { config: null | string }[]; - variables: Record; - variant: number; - overridden: boolean; - assigned: boolean; - exposed: boolean; - eligible: boolean; - fullOn: boolean; - custom: boolean; - audienceMismatch: boolean; - customFieldValues: CustomFieldValue[] | null; -}; - -type Assignment = { - id: number; - iteration: number; - fullOnVariant: number; - unitType: string | null; - variant: number; - overridden: boolean; - assigned: boolean; - exposed: boolean; - eligible: boolean; - fullOn: boolean; - custom: boolean; - audienceMismatch: boolean; - trafficSplit?: number[]; - variables?: Record; - attrsSeq?: number; -}; - -export type Experiment = { - data: ExperimentData; - variables: Record[]; -}; - -export type Unit = { - type: string; - uid: string | null; -}; - -export type Exposure = { - id: number; - name: string; - exposedAt: number; - unit: string | null; - variant: number; - assigned: boolean; - eligible: boolean; - overridden: boolean; - fullOn: boolean; - custom: boolean; - audienceMismatch: boolean; -}; - -export type Attribute = { - name: string; - value: unknown; - setAt: number; -}; - -export type Units = { - [key: string]: string | number; -}; - -export type Goal = { - name: string; - properties: Record | null; - achievedAt: number; -}; - -export type ContextParams = { - units: Record; -}; - -export type ContextOptions = { +type ContextOptionsInternal = { publisher?: ContextPublisher; dataProvider?: ContextDataProvider; eventLogger?: EventLogger; @@ -119,39 +49,38 @@ export type ContextOptions = { includeSystemAttributes?: boolean; }; -export type ContextData = { - experiments?: ExperimentData[]; -}; - -export default class Context { +export class Context { private readonly _assigners: Record; private readonly _attrs: Attribute[]; private readonly _audienceMatcher: AudienceMatcher; private readonly _cassignments: Record; private readonly _dataProvider: ContextDataProvider; private readonly _eventLogger: EventLogger; - private readonly _opts: ContextOptions; + private readonly _opts: ContextOptionsInternal; private readonly _publisher: ContextPublisher; - private readonly _sdk: SDK; + private readonly _sdk: SDKLike; private readonly _units: Units; - private _assignments: Record; - private _data: ContextData; + private _assignments: Record = {}; + private _data!: ContextData; private _exposures: Exposure[]; private _failed: boolean; + private _failedError: Error | null = null; private _finalized: boolean; - private _finalizing: boolean | Promise | null; - private _goals: Goal[]; - private _index: Record; - private _indexVariables: Record; + private _finalizing: Promise | null = null; + private _goals: GoalAchievement[]; + private _index!: Record; + private _indexVariables!: Record; private _overrides: Record; private _pending: number; private _attrsSeq: number; + private _attrsMapCache: Record | null = null; + private _attrsMapCacheSeq: number = -1; private _hashes?: Record; private _promise?: Promise; private _publishTimeout?: ReturnType; private _refreshInterval?: ReturnType; - constructor(sdk: SDK, options: ContextOptions, params: ContextParams, promise: ContextData | Promise) { + constructor(sdk: SDKLike, options: ContextOptionsInternal, params: ContextParams, promise: ContextData | Promise) { this._sdk = sdk; this._publisher = options.publisher || this._sdk.getContextPublisher(); this._dataProvider = options.dataProvider || this._sdk.getContextDataProvider(); @@ -190,15 +119,14 @@ export default class Context { this._init({}); this._failed = true; + this._failedError = error; delete this._promise; this._logError(error); }); } else { - promise = promise as ContextData; - this._init(promise); - - this._logEvent("ready", promise); + this._init(promise as ContextData); + this._logEvent("ready", promise as ContextData); } } @@ -218,13 +146,17 @@ export default class Context { return this._failed; } - ready() { + readyError(): Error | null { + return this._failedError; + } + + ready(): Promise { if (this.isReady()) { return Promise.resolve(true); } return new Promise((resolve) => { - this._promise?.then(() => resolve(true)).catch((e) => resolve(e)); + this._promise?.then(() => resolve(true)).catch(() => resolve(true)); }); } @@ -234,7 +166,6 @@ export default class Context { data() { this._checkReady(); - return this._data; } @@ -250,6 +181,14 @@ export default class Context { return this._dataProvider; } + getSDK() { + return this._sdk; + } + + getOptions(): ContextOptionsInternal { + return { ...this._opts }; + } + publish(requestOptions?: ClientRequestOptions) { this._checkReady(true); @@ -302,29 +241,34 @@ export default class Context { } this._units[unitType] = uid; + + if (this._hashes) { + delete this._hashes[unitType]; + } } getUnits() { - return this._units; + return { ...this._units }; } units(units: Record) { - Object.entries(units).forEach(([unitType, uid]) => { + for (const [unitType, uid] of Object.entries(units)) { this.unit(unitType, uid); - }); + } } getAttribute(attrName: string) { let result; - - this._attrs.forEach((attr) => { + for (const attr of this._attrs) { if (attr.name === attrName) result = attr.value; - }); - + } return result; } attribute(attrName: string, value: unknown) { + if (typeof attrName !== "string" || attrName.trim().length === 0) { + throw new Error("Attribute name must be a non-empty string"); + } this._checkNotFinalized(); this._attrs.push({ name: attrName, value: value, setAt: Date.now() }); @@ -333,35 +277,39 @@ export default class Context { getAttributes() { const attributes: Record = {}; - this._attrs - .map((a) => [a.name, a.value]) - .forEach(([key, value]) => { - attributes[key as string] = value; - }); + for (const a of this._attrs) { + attributes[a.name] = a.value; + } return attributes; } attributes(attrs: Record) { - Object.entries(attrs).forEach(([attrName, value]) => { + for (const [attrName, value] of Object.entries(attrs)) { this.attribute(attrName, value); - }); + } } peek(experimentName: string) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } this._checkReady(true); - return this._peek(experimentName).variant; } treatment(experimentName: string) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } this._checkReady(true); - return this._treatment(experimentName).variant; } track(goalName: string, properties?: Record) { + if (typeof goalName !== "string" || goalName.trim().length === 0) { + throw new Error("Goal name must be a non-empty string"); + } this._checkNotFinalized(); - return this._track(goalName, properties); } @@ -371,19 +319,22 @@ export default class Context { experiments() { this._checkReady(); - return this._data.experiments?.map((x) => x.name); } - variableValue(key: string, defaultValue: string): string { + variableValue(key: string, defaultValue: T): T { + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error("Variable key must be a non-empty string"); + } this._checkReady(true); - return this._variableValue(key, defaultValue); } - peekVariableValue(key: string, defaultValue: string): string { + peekVariableValue(key: string, defaultValue: T): T { + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error("Variable key must be a non-empty string"); + } this._checkReady(true); - return this._peekVariable(key, defaultValue); } @@ -392,49 +343,88 @@ export default class Context { const variableExperiments: Record = {}; - Object.entries(this._indexVariables).forEach(([key, values]) => { - values.forEach((value) => { + for (const [key, values] of Object.entries(this._indexVariables)) { + for (const value of values) { if (variableExperiments[key]) variableExperiments[key].push(value.data.name); else variableExperiments[key] = [value.data.name]; - }); - }); + } + } return variableExperiments; } override(experimentName: string, variant: number) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } + if (typeof variant !== "number" || variant < 0 || !Number.isInteger(variant)) { + throw new Error("Variant must be a non-negative integer"); + } + this._checkNotFinalized(); this._overrides = Object.assign(this._overrides, { [experimentName]: variant }); } overrides(experimentVariants: Record) { - Object.entries(experimentVariants).forEach(([experimentName, variant]) => { + for (const [experimentName, variant] of Object.entries(experimentVariants)) { this.override(experimentName, variant); - }); + } } customAssignment(experimentName: string, variant: number) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } + if (typeof variant !== "number" || variant < 0 || !Number.isInteger(variant)) { + throw new Error("Variant must be a non-negative integer"); + } this._checkNotFinalized(); - this._cassignments[experimentName] = variant; } customAssignments(experimentVariants: Record) { - Object.entries(experimentVariants).forEach(([experimentName, variant]) => { + for (const [experimentName, variant] of Object.entries(experimentVariants)) { this.customAssignment(experimentName, variant); - }); + } + } + + customFieldKeys() { + this._checkReady(true); + return this._customFieldKeys(); + } + + customFieldValue(experimentName: string, key: string) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error("Key must be a non-empty string"); + } + this._checkReady(true); + return this._customFieldValue(experimentName, key); + } + + customFieldValueType(experimentName: string, key: string) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error("Key must be a non-empty string"); + } + this._checkReady(true); + return this._customFieldValueType(experimentName, key); } private _checkNotFinalized() { if (this.isFinalized()) { - throw new Error("ABSmartly Context is finalized."); + throw new ContextFinalizedError(); } else if (this.isFinalizing()) { - throw new Error("ABSmartly Context is finalizing."); + throw new ContextFinalizedError(); } } private _checkReady(expectNotFinalized?: boolean) { if (!this.isReady()) { - throw new Error("ABSmartly Context is not yet ready."); + throw new ContextNotReadyError(); } if (expectNotFinalized) { @@ -443,14 +433,28 @@ export default class Context { } private _getAttributesMap(): Record { + if (this._attrsMapCache !== null && this._attrsMapCacheSeq === this._attrsSeq) { + return this._attrsMapCache; + } const attrs: Record = {}; - this._attrs.forEach((attr) => { + for (const attr of this._attrs) { attrs[attr.name] = attr.value; - }); + } + this._attrsMapCache = attrs; + this._attrsMapCacheSeq = this._attrsSeq; return attrs; } - private _assign(experimentName: string) { + private _evaluateAudience(audience: string): boolean | null { + try { + return this._audienceMatcher.evaluate(audience, this._getAttributesMap()); + } catch (error) { + this._logError(error as Error); + return null; + } + } + + private _assign(experimentName: string): Assignment { const experimentMatches = (experiment: ExperimentData, assignment: Assignment) => { return ( experiment.id === assignment.id && @@ -463,9 +467,9 @@ export default class Context { const audienceMatches = (experiment: ExperimentData, assignment: Assignment) => { if (experiment.audience && experiment.audience.length > 0) { - if (this._attrsSeq > (assignment.attrsSeq ?? 0)) { - const result = this._audienceMatcher.evaluate(experiment.audience, this._getAttributesMap()); - const newAudienceMismatch = typeof result === "boolean" ? !result : false; + if (this._attrsSeq > assignment.attrsSeq) { + const result = this._evaluateAudience(experiment.audience); + const newAudienceMismatch = typeof result === "boolean" ? !result : true; if (newAudienceMismatch !== assignment.audienceMismatch) { return false; @@ -479,23 +483,20 @@ export default class Context { const hasCustom = experimentName in this._cassignments; const hasOverride = experimentName in this._overrides; - const experiment = experimentName in this._index ? this._index[experimentName] : null; + const experiment = experimentName in this._index ? this._index[experimentName]! : null; if (experimentName in this._assignments) { - const assignment = this._assignments[experimentName]; + const assignment = this._assignments[experimentName]!; if (hasOverride) { - if (assignment.overridden && assignment.variant === this._overrides[experimentName]) { - // override up-to-date + if (assignment.overridden && assignment.variant === this._overrides[experimentName]!) { return assignment; } } else if (experiment == null) { if (!assignment.assigned) { - // previously not-running experiment return assignment; } - } else if (!hasCustom || this._cassignments[experimentName] === assignment.variant) { + } else if (!hasCustom || this._cassignments[experimentName]! === assignment.variant) { if (experimentMatches(experiment.data, assignment) && audienceMatches(experiment.data, assignment)) { - // assignment up-to-date return assignment; } } @@ -514,6 +515,9 @@ export default class Context { fullOn: false, custom: false, audienceMismatch: false, + trafficSplit: null, + variables: null, + attrsSeq: 0, }; this._assignments[experimentName] = assignment; @@ -525,17 +529,14 @@ export default class Context { } assignment.overridden = true; - assignment.variant = this._overrides[experimentName]; + assignment.variant = this._overrides[experimentName]!; } else { if (experiment != null) { const unitType = experiment.data.unitType; if (experiment.data.audience && experiment.data.audience.length > 0) { - const result = this._audienceMatcher.evaluate(experiment.data.audience, this._getAttributesMap()); - - if (typeof result === "boolean") { - assignment.audienceMismatch = !result; - } + const result = this._evaluateAudience(experiment.data.audience); + assignment.audienceMismatch = typeof result === "boolean" ? !result : true; } if (experiment.data.audienceStrict && assignment.audienceMismatch) { @@ -547,7 +548,7 @@ export default class Context { if (unit !== null) { const assigner = unitType in this._assigners - ? this._assigners[unitType] + ? this._assigners[unitType]! : (this._assigners[unitType] = new VariantAssigner(unit)); const eligible = assigner.assign( @@ -561,7 +562,7 @@ export default class Context { if (eligible) { if (hasCustom) { - assignment.variant = this._cassignments[experimentName]; + assignment.variant = this._cassignments[experimentName]!; assignment.custom = true; } else { assignment.variant = assigner.assign( @@ -583,7 +584,6 @@ export default class Context { assignment.fullOn = true; } - // store these so we can detect changes to running experiment assignment.unitType = unitType; assignment.id = experiment.data.id; assignment.iteration = experiment.data.iteration; @@ -594,7 +594,7 @@ export default class Context { } if (experiment != null && assignment.variant < experiment.data.variants.length) { - assignment.variables = experiment.variables[assignment.variant]; + assignment.variables = experiment.variables[assignment.variant] ?? null; } return assignment; @@ -609,7 +609,6 @@ export default class Context { if (!assignment.exposed) { assignment.exposed = true; - this._queueExposure(experimentName, assignment); } @@ -654,18 +653,14 @@ export default class Context { return Array.from(keys); } - customFieldKeys() { - this._checkReady(true); - - return this._customFieldKeys(); - } - private _customFieldValue(experimentName: string, key: string): JSONValue { const experiment = this._index[experimentName]; if (experiment != null) { - const field = experiment.data.customFieldValues?.find((x) => x.name === key); + const field = experiment.data.customFieldValues?.find((x: CustomFieldValue) => x.name === key); if (field != null) { + if (field.value === null) return null; + switch (field.type) { case "text": case "string": @@ -678,14 +673,16 @@ export default class Context { if (field.value === "") return ""; return JSON.parse(field.value); } catch (e) { - console.error(`Failed to parse JSON custom field value '${key}' for experiment '${experimentName}'`); + this._logError(new Error( + `Failed to parse JSON custom field value '${key}' for experiment '${experimentName}': ${(e as Error).message}` + )); return null; } case "boolean": return field.value === "true"; default: - console.error( - `Unknown custom field type '${field.type}' for experiment '${experimentName}' and key '${key}' - you may need to upgrade to the latest SDK version` + this._logError( + new Error(`Unknown custom field type '${field.type}' for experiment '${experimentName}' and key '${key}' - you may need to upgrade to the latest SDK version`) ); return null; } @@ -695,17 +692,11 @@ export default class Context { return null; } - customFieldValue(experimentName: string, key: string) { - this._checkReady(true); - - return this._customFieldValue(experimentName, key); - } - private _customFieldValueType(experimentName: string, key: string) { const experiment = this._index[experimentName]; if (experiment != null) { - const field = experiment.data.customFieldValues?.find((x) => x.name === key); + const field = experiment.data.customFieldValues?.find((x: CustomFieldValue) => x.name === key); if (field != null) { return field.type; } @@ -714,25 +705,21 @@ export default class Context { return null; } - customFieldValueType(experimentName: string, key: string) { - this._checkReady(true); - - return this._customFieldValueType(experimentName, key); - } - - private _variableValue(key: string, defaultValue: string): string { - for (const i in this._indexVariables[key]) { - const experimentName = this._indexVariables[key][i].data.name; - const assignment = this._assign(experimentName); - if (assignment.variables !== undefined) { - if (!assignment.exposed) { - assignment.exposed = true; - - this._queueExposure(experimentName, assignment); - } + private _variableValue(key: string, defaultValue: T): T { + const experiments = this._indexVariables[key]; + if (experiments) { + for (const experiment of experiments) { + const experimentName = experiment.data.name; + const assignment = this._assign(experimentName); + if (assignment.variables != null) { + if (!assignment.exposed) { + assignment.exposed = true; + this._queueExposure(experimentName, assignment); + } - if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { - return assignment.variables[key] as string; + if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { + return assignment.variables[key] as T; + } } } } @@ -740,13 +727,16 @@ export default class Context { return defaultValue; } - private _peekVariable(key: string, defaultValue: string): string { - for (const i in this._indexVariables[key]) { - const experimentName = this._indexVariables[key][i].data.name; - const assignment = this._assign(experimentName); - if (assignment.variables !== undefined) { - if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { - return assignment.variables[key] as string; + private _peekVariable(key: string, defaultValue: T): T { + const experiments = this._indexVariables[key]; + if (experiments) { + for (const experiment of experiments) { + const experimentName = experiment.data.name; + const assignment = this._assign(experimentName); + if (assignment.variables != null) { + if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { + return assignment.variables[key] as T; + } } } } @@ -768,7 +758,7 @@ export default class Context { private _track(goalName: string, properties?: Record) { const props = this._validateGoal(goalName, properties); - const goalEvent: Goal = { name: goalName, properties: props, achievedAt: Date.now() }; + const goalEvent: GoalAchievement = { name: goalName, properties: props, achievedAt: Date.now() }; this._logEvent("goal", goalEvent); this._goals.push(goalEvent); @@ -864,6 +854,17 @@ export default class Context { request.attributes = allAttributes; } + // Snapshot and clear synchronously before async publish. + // New events accumulate in fresh arrays during in-flight publish. + // On failure, restore the snapshot so events are retried. + const pendingCount = this._pending; + const pendingExposures = this._exposures; + const pendingGoals = this._goals; + + this._pending = 0; + this._exposures = []; + this._goals = []; + this._publisher .publish(request, this._sdk, this, requestOptions) .then(() => { @@ -874,6 +875,10 @@ export default class Context { } }) .catch((e: Error) => { + this._pending += pendingCount; + this._exposures.push(...pendingExposures); + this._goals.push(...pendingGoals); + this._logError(e); if (typeof callback === "function") { @@ -888,14 +893,18 @@ export default class Context { } } } else { + this._logError(new Error( + `Discarding ${this._exposures.length} exposures and ${this._goals.length} goals because context failed to initialize` + )); + + this._pending = 0; + this._exposures = []; + this._goals = []; + if (typeof callback === "function") { callback(); } } - - this._pending = 0; - this._exposures = []; - this._goals = []; } } @@ -926,30 +935,38 @@ export default class Context { } } - private _logEvent(eventName: EventName, data?: Record) { + private _logEvent(eventName: EventName, data?: unknown) { if (this._eventLogger) { - this._eventLogger(this, eventName, data); + try { + this._eventLogger(this, eventName, data as any); + } catch { + // ignore logger errors + } } } private _logError(error: Error) { if (this._eventLogger) { - this._eventLogger(this, "error", error); + try { + this._eventLogger(this, "error", error); + } catch { + // ignore logger errors + } } } - private _unitHash(unitType: string) { + private _unitHash(unitType: string): string | null { if (!this._hashes) { this._hashes = {}; } if (!(unitType in this._hashes)) { - const hash = unitType in this._units ? hashUnit(this._units[unitType]) : null; + const hash = unitType in this._units ? hashUnit(this._units[unitType]!) : null; this._hashes[unitType] = hash; return hash; } - return this._hashes[unitType]; + return this._hashes[unitType]!; } private _init(data: ContextData, assignments: Record = {}) { @@ -958,7 +975,7 @@ export default class Context { const index: Record = {}; const indexVariables: Record = {}; - (data.experiments || []).forEach((experiment) => { + for (const experiment of data.experiments || []) { const variables: Record[] = []; const entry = { data: experiment, @@ -967,11 +984,25 @@ export default class Context { index[experiment.name] = entry; - experiment.variants.forEach((variant, i) => { + for (let i = 0; i < experiment.variants.length; i++) { + const variant = experiment.variants[i]!; const config = variant.config; - const parsed = config != null && config.length > 0 ? JSON.parse(config) : {}; + let parsed: Record = {}; - Object.keys(parsed).forEach((key) => { + if (config != null && config.length > 0) { + try { + const value = JSON.parse(config); + if (isObject(value)) { + parsed = value as Record; + } + } catch (error) { + this._logError(new Error( + `Failed to parse config for experiment '${experiment.name}' variant ${i}: ${(error as Error).message}` + )); + } + } + + for (const key of Object.keys(parsed)) { const value = entry; if (indexVariables[key]) { insertUniqueSorted( @@ -980,11 +1011,11 @@ export default class Context { (a, b) => (a as Experiment).data.id < (b as Experiment).data.id ); } else indexVariables[key] = [value]; - }); + } variables[i] = parsed; - }); - }); + } + } this._index = index; this._indexVariables = indexVariables; @@ -1013,7 +1044,6 @@ export default class Context { } else { this._finalized = true; this._logEvent("finalize"); - resolve(); } }, requestOptions); diff --git a/src/errors.ts b/src/errors.ts index f921770..59f9371 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,3 +1,24 @@ +export class ABSmartlyError extends Error { + constructor(message: string) { + super(message); + this.name = "ABSmartlyError"; + } +} + +export class ContextNotReadyError extends ABSmartlyError { + constructor() { + super("Context is not yet ready"); + this.name = "ContextNotReadyError"; + } +} + +export class ContextFinalizedError extends ABSmartlyError { + constructor() { + super("Context has been finalized"); + this.name = "ContextFinalizedError"; + } +} + export class TimeoutError extends Error { readonly timeout: number; constructor(timeout: number) { diff --git a/src/fetch-shim.ts b/src/fetch-shim.ts deleted file mode 100644 index b391e74..0000000 --- a/src/fetch-shim.ts +++ /dev/null @@ -1,81 +0,0 @@ -// inspired by https://github.com/bradlc/unfetch-abortable -import { AbortError } from "./errors"; - -export type FetchOptions = { - signal?: AbortSignal; - method?: "get" | "post" | "put" | "patch" | "delete" | "head" | "options"; - credentials?: "include"; - headers?: Record; - body?: XMLHttpRequestBodyInit; -}; -// eslint-disable-next-line no-shadow -export function fetch(url: string, options: FetchOptions) { - options = options || {}; - return new Promise((resolve, reject) => { - const request = new XMLHttpRequest(); - const keys: string[] = []; - const all: [string, unknown][] = []; - const headers: Record = {}; - - const abort = () => { - request.abort(); - }; - - const cleanup = options.signal ? () => options.signal?.removeEventListener("abort", abort) : () => {}; - - const response = () => ({ - ok: ((request.status / 100) | 0) === 2, - statusText: request.statusText, - status: request.status, - url: request.responseURL, - text: () => Promise.resolve(request.responseText), - json: () => Promise.resolve(JSON.parse(request.responseText)), - blob: () => Promise.resolve(new Blob([request.response])), - clone: response, - headers: { - keys: () => keys, - entries: () => all, - get: (n: string) => headers[n.toLowerCase()], - has: (n: string) => n.toLowerCase() in headers, - }, - }); - - request.open(options.method || "get", url, true); - - request.onload = () => { - request.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, (m, key, value) => { - keys.push((key = key.toLowerCase())); - all.push([key, value]); - headers[key] = headers[key] ? `${headers[key]},${value}` : value; - return m; - }); - - cleanup(); - resolve(response()); - }; - - if (options.signal) { - options.signal.addEventListener("abort", abort); - } - - request.onerror = (error) => { - cleanup(); - reject(error); - }; - - request.onabort = () => { - cleanup(); - reject(new AbortError("The user aborted a request.")); - }; - - request.withCredentials = options.credentials === "include"; - - for (const i in options.headers) { - request.setRequestHeader(i, options.headers[i]); - } - - request.send(options.body || null); - }); -} - -export default fetch; diff --git a/src/fetch.ts b/src/fetch.ts deleted file mode 100644 index 088f22e..0000000 --- a/src/fetch.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { isLongLivedApp, isWorker } from "./utils"; -import fetchShim from "./fetch-shim"; - -const exported = isLongLivedApp() - ? window.fetch - ? window.fetch.bind(window) - : fetchShim - : isWorker() - ? self.fetch - ? self.fetch.bind(self) - : fetchShim - : global - ? global.fetch - ? global.fetch.bind(global) - : function (url: string, opts: Record) { - return new Promise((resolve, reject) => { - import("node-fetch") - .then((fetchNode) => { - fetchNode.default(url.replace(/^\/\//g, "https://"), opts).then(resolve).catch(reject); - }) - .catch(reject); - }); - } - : undefined; - -export default exported; diff --git a/src/hashing.ts b/src/hashing.ts new file mode 100644 index 0000000..140c230 --- /dev/null +++ b/src/hashing.ts @@ -0,0 +1,71 @@ +import { md5 } from "./md5"; + +export function stringToUint8Array(value: string): Uint8Array { + const utf8: number[] = []; + for (let i = 0; i < value.length; i++) { + let c = value.charCodeAt(i); + if (c >= 0xd800 && c <= 0xdbff && i + 1 < value.length) { + const next = value.charCodeAt(i + 1); + if (next >= 0xdc00 && next <= 0xdfff) { + c = ((c - 0xd800) << 10) + (next - 0xdc00) + 0x10000; + i++; + } + } + if (c < 0x80) { + utf8.push(c); + } else if (c < 0x800) { + utf8.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); + } else if (c < 0x10000) { + utf8.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + } else { + utf8.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + } + } + return new Uint8Array(utf8); +} + +const BASE64_URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +export function base64UrlNoPadding(value: Uint8Array): string { + const chars = BASE64_URL_CHARS; + const remaining = value.byteLength % 3; + const encodeLen = ((value.byteLength / 3) | 0) * 4 + (remaining === 0 ? 0 : remaining === 1 ? 2 : 3); + const result = new Array(encodeLen); + + let i: number; + let out = 0; + const len = value.byteLength - remaining; + for (i = 0; i < len; i += 3) { + const bytes = (value[i]! << 16) | (value[i + 1]! << 8) | value[i + 2]!; + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + result[out + 2] = chars[(bytes >> 6) & 63]!; + result[out + 3] = chars[bytes & 63]!; + out += 4; + } + + switch (remaining) { + case 2: { + const bytes = (value[i]! << 16) | (value[i + 1]! << 8); + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + result[out + 2] = chars[(bytes >> 6) & 63]!; + break; + } + case 1: { + const bytes = value[i]! << 16; + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + break; + } + default: + break; + } + + return result.join(""); +} + +export function hashUnit(value: string | number): string { + const unit = typeof value === "string" ? value : value.toFixed(0); + return base64UrlNoPadding(md5(stringToUint8Array(unit).buffer)); +} diff --git a/src/index.ts b/src/index.ts index aa43221..400730a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,47 @@ -import Context from "./context"; -import SDK from "./sdk"; -import { mergeConfig } from "./config"; -import { ContextDataProvider } from "./provider"; -import { ContextPublisher } from "./publisher"; -// eslint-disable-next-line no-shadow -import { AbortController } from "./abort"; +export { SDK } from "./sdk"; +export { Context } from "./context"; +export { DefaultContextDataProvider } from "./provider"; +export { DefaultContextPublisher } from "./publisher"; +export { mergeConfig } from "./config"; -export { mergeConfig, AbortController, Context, ContextDataProvider, ContextPublisher, SDK }; -export default { mergeConfig, AbortController, Context, ContextDataProvider, ContextPublisher, SDK }; +export { + ABSmartlyError, + ContextNotReadyError, + ContextFinalizedError, + TimeoutError, + RetryError, + AbortError, +} from "./errors"; + +export type { + ApplicationObject, + Assignment, + Attribute, + ContextData, + CustomFieldValue, + CustomFieldValueType, + Experiment, + ExperimentApplication, + ExperimentData, + ExperimentVariant, + Exposure, + GoalAchievement, + JSONValue, + PublishParams, + Unit, + Units, +} from "./models"; + +export type { + Client, + ClientOptions, + ClientRequestOptions, + ContextDataProvider, + ContextOptions, + ContextParams, + ContextPublisher, + EventLogger, + EventLoggerData, + EventName, + SDKOptions, +} from "./interfaces"; diff --git a/src/interfaces.ts b/src/interfaces.ts new file mode 100644 index 0000000..ae5fe87 --- /dev/null +++ b/src/interfaces.ts @@ -0,0 +1,74 @@ +import type { + ApplicationObject, + ContextData, + Exposure, + GoalAchievement, + PublishParams, +} from "./models"; + +export type EventName = "error" | "ready" | "refresh" | "publish" | "exposure" | "goal" | "finalize"; + +export type EventLoggerData = Error | Exposure | GoalAchievement | ContextData | PublishParams; + +export type EventLogger = (context: unknown, eventName: EventName, data?: EventLoggerData) => void; + +export type ContextParams = { + units: Record; +}; + +export type ContextOptions = { + publishDelay: number; + refreshPeriod: number; + includeSystemAttributes?: boolean; +}; + +export interface ClientRequestOptions { + query?: Record; + path: string; + method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; + body?: Record; + auth?: boolean; + signal?: AbortSignal; + timeout?: number; +} + +export type ClientOptions = { + agent?: string; + apiKey: string; + application: string | ApplicationObject; + endpoint: string; + environment: string; + retries?: number; + timeout?: number; + keepalive?: boolean; + fetchImpl?: typeof fetch; + AbortControllerImpl?: typeof AbortController; +}; + +export type SDKOptions = { + client?: Client; + eventLogger?: EventLogger; + publisher?: ContextPublisher; + provider?: ContextDataProvider; +}; + +export interface Client { + getContext(options?: Partial): Promise; + publish(params: PublishParams, options?: ClientRequestOptions): Promise; + getAgent(): string; + getApplication(): ApplicationObject; + getEnvironment(): string; +} + +export interface ContextDataProvider { + getContextData(sdk: unknown, requestOptions?: Partial): Promise; +} + +export interface ContextPublisher { + publish( + request: PublishParams, + sdk: unknown, + context: unknown, + requestOptions?: ClientRequestOptions, + ): Promise; +} diff --git a/src/jsonexpr/evaluator.ts b/src/jsonexpr/evaluator.ts index 5c52ff0..905e6f8 100644 --- a/src/jsonexpr/evaluator.ts +++ b/src/jsonexpr/evaluator.ts @@ -1,6 +1,9 @@ -/* eslint-disable */ import { isEqualsDeep, isObject } from "../utils"; +export interface Operator { + evaluate(evaluator: Evaluator, args: unknown): unknown; +} + function parseSemver(version: string) { let v = version; if (v.startsWith("v") || v.startsWith("V")) { @@ -12,39 +15,32 @@ function parseSemver(version: string) { v = v.substring(0, plusIndex); } - if (v === "") { - return null; - } + if (v === "") return null; const [core, ...preReleaseParts] = v.split("-"); const preRelease = preReleaseParts.join("-"); - if (core === "") { - return null; - } - - const parts = core.split("."); + if (core === "") return null; + const parts = core!.split("."); return { parts, preRelease }; } const NUMERIC_IDENTIFIER = /^\d+$/; -function stripLeadingZeros(s: string) { +function stripLeadingZeros(s: string): string { const stripped = s.replace(/^0+/, ""); return stripped === "" ? "0" : stripped; } -function compareIdentifiers(a: string, b: string) { +function compareIdentifiers(a: string, b: string): number { const aIsNum = NUMERIC_IDENTIFIER.test(a); const bIsNum = NUMERIC_IDENTIFIER.test(b); if (aIsNum && bIsNum) { const aNorm = stripLeadingZeros(a); const bNorm = stripLeadingZeros(b); - if (aNorm.length !== bNorm.length) { - return aNorm.length > bNorm.length ? 1 : -1; - } + if (aNorm.length !== bNorm.length) return aNorm.length > bNorm.length ? 1 : -1; return aNorm === bNorm ? 0 : aNorm > bNorm ? 1 : -1; } if (aIsNum) return -1; @@ -53,17 +49,17 @@ function compareIdentifiers(a: string, b: string) { } export class Evaluator { - private readonly operators: any; - private readonly vars: any; + private readonly operators: Record; + private readonly vars: Record; - constructor(operators: any, vars: any) { + constructor(operators: Record, vars: Record) { this.operators = operators; this.vars = vars; } - evaluate(expr: TExpr) { + evaluate(expr: unknown): unknown { if (Array.isArray(expr)) { - return this.operators["and"].evaluate(this, expr); + return this.operators["and"]?.evaluate(this, expr) ?? null; } else if (isObject(expr)) { for (const [key, value] of Object.entries(expr)) { const op = this.operators[key]; @@ -73,13 +69,11 @@ export class Evaluator { break; } } - return null; } - booleanConvert(x: TData) { - const type = typeof x; - switch (type) { + booleanConvert(x: unknown): boolean { + switch (typeof x) { case "boolean": return x; case "number": @@ -91,7 +85,7 @@ export class Evaluator { } } - numberConvert(x: TData) { + numberConvert(x: unknown): number | null { switch (typeof x) { case "number": return x; @@ -106,7 +100,7 @@ export class Evaluator { } } - stringConvert(x: TData) { + stringConvert(x: unknown): string | null { switch (typeof x) { case "string": return x; @@ -119,37 +113,27 @@ export class Evaluator { } } - extractVar(path: string) { + extractVar(path: string): unknown { const frags = path.split("/"); - - let target = this.vars ?? {}; - for (let index = 0; index < frags.length; ++index) { - const frag = frags[index]; - - const value = target[frag]; - if (value !== undefined) { - target = value; + let target: unknown = this.vars ?? {}; + for (const frag of frags) { + if (target !== null && typeof target === "object" && frag in (target as Record)) { + target = (target as Record)[frag]; continue; } - return null; } - return target; } - versionCompare(lhs: TData, rhs: TData): number | null { + versionCompare(lhs: unknown, rhs: unknown): number | null { const lhsStr = this.stringConvert(lhs); const rhsStr = this.stringConvert(rhs); - if (lhsStr === null || rhsStr === null || lhsStr === "" || rhsStr === "") { - return null; - } + if (lhsStr === null || rhsStr === null || lhsStr === "" || rhsStr === "") return null; const l = parseSemver(lhsStr); const r = parseSemver(rhsStr); - if (l === null || r === null) { - return null; - } + if (l === null || r === null) return null; const maxLen = Math.max(l.parts.length, r.parts.length); for (let i = 0; i < maxLen; i++) { @@ -169,46 +153,35 @@ export class Evaluator { for (let i = 0; i < preLen; i++) { if (i >= lPreParts.length) return -1; if (i >= rPreParts.length) return 1; - const result = compareIdentifiers(lPreParts[i], rPreParts[i]); + const result = compareIdentifiers(lPreParts[i]!, rPreParts[i]!); if (result !== 0) return result; } return 0; } - compare(lhs: TData, rhs: TData) { - if (lhs === null) { - return rhs === null ? 0 : null; - } else if (rhs === null) { - return null; - } + compare(lhs: unknown, rhs: unknown): number | null { + if (lhs === null) return rhs === null ? 0 : null; + if (rhs === null) return null; switch (typeof lhs) { case "number": { const rvalue = this.numberConvert(rhs); - if (rvalue !== null) { - return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; - } + if (rvalue !== null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; break; } case "string": { const rvalue = this.stringConvert(rhs); - if (rvalue !== null) { - return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; - } + if (rvalue !== null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; break; } case "boolean": { const rvalue = this.booleanConvert(rhs); - if (rvalue != null) { - return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; - } + if (rvalue != null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; break; } default: { - if (isEqualsDeep(lhs, rhs)) { - return 0; - } + if (isEqualsDeep(lhs, rhs)) return 0; break; } } diff --git a/src/jsonexpr/jsonexpr.ts b/src/jsonexpr/jsonexpr.ts index 84414c7..8ae4562 100644 --- a/src/jsonexpr/jsonexpr.ts +++ b/src/jsonexpr/jsonexpr.ts @@ -1,22 +1,24 @@ -import { ValueOperator } from "./operators/value"; -import { AndCombinator } from "./operators/and"; -import { OrCombinator } from "./operators/or"; -import { VarOperator } from "./operators/var"; -import { NotOperator } from "./operators/not"; -import { NullOperator } from "./operators/null"; -import { MatchOperator } from "./operators/match"; -import { InOperator } from "./operators/in"; import { Evaluator } from "./evaluator"; -import { EqualsOperator } from "./operators/eq"; -import { GreaterThanOperator } from "./operators/gt"; -import { GreaterThanOrEqualOperator } from "./operators/gte"; -import { LessThanOperator } from "./operators/lt"; -import { LessThanOrEqualOperator } from "./operators/lte"; -import { SemverEqualsOperator } from "./operators/semver_eq"; -import { SemverGreaterThanOperator } from "./operators/semver_gt"; -import { SemverGreaterThanOrEqualOperator } from "./operators/semver_gte"; -import { SemverLessThanOperator } from "./operators/semver_lt"; -import { SemverLessThanOrEqualOperator } from "./operators/semver_lte"; +import { + AndCombinator, + EqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualOperator, + InOperator, + LessThanOperator, + LessThanOrEqualOperator, + MatchOperator, + NotOperator, + NullOperator, + OrCombinator, + SemverEqualsOperator, + SemverGreaterThanOperator, + SemverGreaterThanOrEqualOperator, + SemverLessThanOperator, + SemverLessThanOrEqualOperator, + ValueOperator, + VarOperator, +} from "./operators"; const operators = { and: new AndCombinator(), @@ -40,12 +42,12 @@ const operators = { }; export class JsonExpr { - evaluateBooleanExpr(expr: TData[] | Record, vars: Record) { + evaluateBooleanExpr(expr: unknown, vars: Record): boolean { const evaluator = new Evaluator(operators, vars); return evaluator.booleanConvert(evaluator.evaluate(expr)); } - evaluateExpr(expr: TData[] | Record, vars: Record) { + evaluateExpr(expr: unknown, vars: Record): unknown { const evaluator = new Evaluator(operators, vars); return evaluator.evaluate(expr); } diff --git a/src/jsonexpr/operators.ts b/src/jsonexpr/operators.ts new file mode 100644 index 0000000..f380f57 --- /dev/null +++ b/src/jsonexpr/operators.ts @@ -0,0 +1,182 @@ +import { Evaluator } from "./evaluator"; +import { isObject } from "../utils"; + +export class ValueOperator { + evaluate(_: Evaluator, value: unknown): unknown { + return value; + } +} + +export class VarOperator { + evaluate(evaluator: Evaluator, path: unknown): unknown { + if (isObject(path)) { + path = (path as { path: string }).path; + } + return typeof path === "string" ? evaluator.extractVar(path) : null; + } +} + +export class AndCombinator { + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + for (const expr of args) { + if (!evaluator.booleanConvert(evaluator.evaluate(expr))) return false; + } + return true; + } + return null; + } +} + +export class OrCombinator { + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + for (const expr of args) { + if (evaluator.booleanConvert(evaluator.evaluate(expr))) return true; + } + return args.length === 0; + } + return null; + } +} + +abstract class UnaryOperator { + abstract unary(evaluator: Evaluator, arg: unknown): boolean; + evaluate(evaluator: Evaluator, arg: unknown): boolean { + arg = evaluator.evaluate(arg); + return this.unary(evaluator, arg); + } +} + +export class NotOperator extends UnaryOperator { + unary(evaluator: Evaluator, arg: unknown): boolean { + return !evaluator.booleanConvert(arg); + } +} + +export class NullOperator extends UnaryOperator { + unary(_: Evaluator, value: unknown): boolean { + return value === null; + } +} + +abstract class BinaryOperator { + abstract binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null; + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + const lhs = args.length > 0 ? evaluator.evaluate(args[0]) : null; + if (lhs !== null) { + const rhs = args.length > 1 ? evaluator.evaluate(args[1]) : null; + if (rhs !== null) { + return this.binary(evaluator, lhs, rhs); + } + } + } + return null; + } +} + +export class EqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} + +export class GreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} + +export class GreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} + +export class LessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} + +export class LessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +} + +export class InOperator extends BinaryOperator { + binary(evaluator: Evaluator, haystack: unknown, needle: unknown): boolean | null { + if (Array.isArray(haystack)) { + for (const item of haystack) { + if (evaluator.compare(item, needle) === 0) return true; + } + return false; + } else if (typeof haystack === "string") { + const needleString = evaluator.stringConvert(needle); + return needleString !== null && haystack.includes(needleString); + } else if (isObject(haystack)) { + const needleString = evaluator.stringConvert(needle); + return needleString != null && Object.prototype.hasOwnProperty.call(haystack, needleString); + } + return null; + } +} + +export class MatchOperator extends BinaryOperator { + binary(evaluator: Evaluator, text: unknown, pattern: unknown): boolean | null { + const textStr = evaluator.stringConvert(text); + if (textStr !== null) { + const patternStr = evaluator.stringConvert(pattern); + if (patternStr !== null) { + try { + return new RegExp(patternStr).test(textStr); + } catch { + return null; + } + } + } + return null; + } +} + +export class SemverEqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} + +export class SemverGreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} + +export class SemverGreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} + +export class SemverLessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} + +export class SemverLessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +} diff --git a/src/jsonexpr/operators/and.ts b/src/jsonexpr/operators/and.ts deleted file mode 100644 index e3d7177..0000000 --- a/src/jsonexpr/operators/and.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export class AndCombinator { - evaluate(evaluator: Evaluator, args: unknown[]): boolean | null { - if (Array.isArray(args)) { - for (const expr of args) { - if (!evaluator.booleanConvert(evaluator.evaluate(expr))) { - return false; - } - } - return true; - } - return null; - } -} diff --git a/src/jsonexpr/operators/binary.ts b/src/jsonexpr/operators/binary.ts deleted file mode 100644 index 99ec1aa..0000000 --- a/src/jsonexpr/operators/binary.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export abstract class BinaryOperator { - abstract binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null; - - evaluate(evaluator: Evaluator, args: unknown[]) { - if (Array.isArray(args)) { - const lhs = args.length > 0 ? evaluator.evaluate(args[0]) : null; - if (lhs !== null) { - const rhs = args.length > 1 ? evaluator.evaluate(args[1]) : null; - if (rhs !== null) { - return this.binary(evaluator, lhs, rhs); - } - } - } - return null; - } -} diff --git a/src/jsonexpr/operators/eq.ts b/src/jsonexpr/operators/eq.ts deleted file mode 100644 index bc71f8c..0000000 --- a/src/jsonexpr/operators/eq.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class EqualsOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result === 0 : null; - } -} diff --git a/src/jsonexpr/operators/gt.ts b/src/jsonexpr/operators/gt.ts deleted file mode 100644 index 60735a4..0000000 --- a/src/jsonexpr/operators/gt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class GreaterThanOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result > 0 : null; - } -} diff --git a/src/jsonexpr/operators/gte.ts b/src/jsonexpr/operators/gte.ts deleted file mode 100644 index c5a220f..0000000 --- a/src/jsonexpr/operators/gte.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class GreaterThanOrEqualOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result >= 0 : null; - } -} diff --git a/src/jsonexpr/operators/in.ts b/src/jsonexpr/operators/in.ts deleted file mode 100644 index fa0cf28..0000000 --- a/src/jsonexpr/operators/in.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { BinaryOperator } from "./binary"; -import { isObject } from "../../utils"; -import { Evaluator } from "../evaluator"; - -export class InOperator extends BinaryOperator { - binary(evaluator: Evaluator, haystack: unknown, needle: string | number | boolean | null) { - if (Array.isArray(haystack)) { - for (const item of haystack) { - if (evaluator.compare(item, needle) === 0) { - return true; - } - } - return false; - } else if (typeof haystack === "string") { - const needleString = evaluator.stringConvert(needle); - return needleString !== null && haystack.includes(needleString); - } else if (isObject(haystack)) { - const needleString = evaluator.stringConvert(needle); - return needleString != null && Object.prototype.hasOwnProperty.call(haystack, needleString); - } - return null; - } -} diff --git a/src/jsonexpr/operators/lt.ts b/src/jsonexpr/operators/lt.ts deleted file mode 100644 index 77e3e2d..0000000 --- a/src/jsonexpr/operators/lt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class LessThanOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result < 0 : null; - } -} diff --git a/src/jsonexpr/operators/lte.ts b/src/jsonexpr/operators/lte.ts deleted file mode 100644 index b2f6da3..0000000 --- a/src/jsonexpr/operators/lte.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class LessThanOrEqualOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result <= 0 : null; - } -} diff --git a/src/jsonexpr/operators/match.ts b/src/jsonexpr/operators/match.ts deleted file mode 100644 index a18be52..0000000 --- a/src/jsonexpr/operators/match.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class MatchOperator extends BinaryOperator { - binary(evaluator: Evaluator, text: string | null, pattern: string | null) { - text = evaluator.stringConvert(text); - if (text !== null) { - pattern = evaluator.stringConvert(pattern); - if (pattern !== null) { - try { - const compiled = new RegExp(pattern); - return compiled.test(text); - } catch (ignored) { - return null; - } - } - } - return null; - } -} diff --git a/src/jsonexpr/operators/not.ts b/src/jsonexpr/operators/not.ts deleted file mode 100644 index 395120a..0000000 --- a/src/jsonexpr/operators/not.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { UnaryOperator } from "./unary"; - -export class NotOperator extends UnaryOperator { - unary(evaluator: Evaluator, arg: string | number | boolean) { - return !evaluator.booleanConvert(arg); - } -} diff --git a/src/jsonexpr/operators/null.ts b/src/jsonexpr/operators/null.ts deleted file mode 100644 index fbbc442..0000000 --- a/src/jsonexpr/operators/null.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { UnaryOperator } from "./unary"; - -export class NullOperator extends UnaryOperator { - unary(_: Evaluator, value: unknown) { - return value === null; - } -} diff --git a/src/jsonexpr/operators/or.ts b/src/jsonexpr/operators/or.ts deleted file mode 100644 index 62f6b2d..0000000 --- a/src/jsonexpr/operators/or.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export class OrCombinator { - evaluate(evaluator: Evaluator, args: unknown) { - if (Array.isArray(args)) { - for (const expr of args) { - if (evaluator.booleanConvert(evaluator.evaluate(expr))) { - return true; - } - } - return args.length === 0; - } - return null; - } -} diff --git a/src/jsonexpr/operators/semver_eq.ts b/src/jsonexpr/operators/semver_eq.ts deleted file mode 100644 index d35dfc2..0000000 --- a/src/jsonexpr/operators/semver_eq.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverEqualsOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result === 0 : null; - } -} diff --git a/src/jsonexpr/operators/semver_gt.ts b/src/jsonexpr/operators/semver_gt.ts deleted file mode 100644 index 1b41166..0000000 --- a/src/jsonexpr/operators/semver_gt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverGreaterThanOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result > 0 : null; - } -} diff --git a/src/jsonexpr/operators/semver_gte.ts b/src/jsonexpr/operators/semver_gte.ts deleted file mode 100644 index 8525453..0000000 --- a/src/jsonexpr/operators/semver_gte.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverGreaterThanOrEqualOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result >= 0 : null; - } -} diff --git a/src/jsonexpr/operators/semver_lt.ts b/src/jsonexpr/operators/semver_lt.ts deleted file mode 100644 index aa44efd..0000000 --- a/src/jsonexpr/operators/semver_lt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverLessThanOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result < 0 : null; - } -} diff --git a/src/jsonexpr/operators/semver_lte.ts b/src/jsonexpr/operators/semver_lte.ts deleted file mode 100644 index 8d6261f..0000000 --- a/src/jsonexpr/operators/semver_lte.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverLessThanOrEqualOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result <= 0 : null; - } -} diff --git a/src/jsonexpr/operators/unary.ts b/src/jsonexpr/operators/unary.ts deleted file mode 100644 index ee27089..0000000 --- a/src/jsonexpr/operators/unary.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export abstract class UnaryOperator { - abstract unary(evaluator: Evaluator, arg: unknown[] | Record | string | number | boolean): boolean; - evaluate(evaluator: Evaluator, arg: unknown[] | Record | string | number | boolean) { - arg = evaluator.evaluate(arg); - return this.unary(evaluator, arg); - } -} diff --git a/src/jsonexpr/operators/value.ts b/src/jsonexpr/operators/value.ts deleted file mode 100644 index c5081c7..0000000 --- a/src/jsonexpr/operators/value.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export class ValueOperator { - evaluate(_: Evaluator, value: unknown) { - return value; - } -} diff --git a/src/jsonexpr/operators/var.ts b/src/jsonexpr/operators/var.ts deleted file mode 100644 index 2da792f..0000000 --- a/src/jsonexpr/operators/var.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { isObject } from "../../utils"; -import { Evaluator } from "../evaluator"; - -export class VarOperator { - evaluate(evaluator: Evaluator, path: unknown) { - if (isObject(path)) { - path = (path as { path: string }).path; - } - - return typeof path === "string" ? evaluator.extractVar(path) : null; - } -} diff --git a/src/matcher.ts b/src/matcher.ts index 424988e..3833493 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -2,20 +2,21 @@ import { isObject } from "./utils"; import { JsonExpr } from "./jsonexpr/jsonexpr"; export class AudienceMatcher { - evaluate(audienceString: string, vars: Record) { + private readonly _jsonExpr = new JsonExpr(); + + evaluate(audienceString: string, vars: Record): boolean | null { + let audience; try { - const audience = JSON.parse(audienceString); - if (audience && audience.filter) { - if (Array.isArray(audience.filter) || isObject(audience.filter)) { - return this._jsonExpr.evaluateBooleanExpr(audience.filter, vars); - } - } - } catch (e) { - console.error(e); + audience = JSON.parse(audienceString); + } catch { + return null; } + if (audience && audience.filter) { + if (Array.isArray(audience.filter) || isObject(audience.filter)) { + return this._jsonExpr.evaluateBooleanExpr(audience.filter, vars); + } + } return null; } - - _jsonExpr = new JsonExpr(); } diff --git a/src/md5.ts b/src/md5.ts index f1b2e07..5ed0d48 100644 --- a/src/md5.ts +++ b/src/md5.ts @@ -1,108 +1,108 @@ -function cmn(q: number, a: number, b: number, x: number, s: number, t: number) { +function cmn(q: number, a: number, b: number, x: number, s: number, t: number): number { a = a + q + (x >>> 0) + t; return ((a << s) | (a >>> (32 - s))) + b; } -function ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { +function ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { return cmn((b & c) | (~b & d), a, b, x, s, t); } -function gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { +function gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { return cmn((b & d) | (c & ~d), a, b, x, s, t); } -function hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { +function hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { return cmn(b ^ c ^ d, a, b, x, s, t); } -function ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { +function ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { return cmn(c ^ (b | ~d), a, b, x, s, t); } -function md5cycle(x: Uint32Array, k: Uint32Array) { - let a = x[0]; - let b = x[1]; - let c = x[2]; - let d = x[3]; - - a = ff(a, b, c, d, k[0], 7, -680876936); - d = ff(d, a, b, c, k[1], 12, -389564586); - c = ff(c, d, a, b, k[2], 17, 606105819); - b = ff(b, c, d, a, k[3], 22, -1044525330); - a = ff(a, b, c, d, k[4], 7, -176418897); - d = ff(d, a, b, c, k[5], 12, 1200080426); - c = ff(c, d, a, b, k[6], 17, -1473231341); - b = ff(b, c, d, a, k[7], 22, -45705983); - a = ff(a, b, c, d, k[8], 7, 1770035416); - d = ff(d, a, b, c, k[9], 12, -1958414417); - c = ff(c, d, a, b, k[10], 17, -42063); - b = ff(b, c, d, a, k[11], 22, -1990404162); - a = ff(a, b, c, d, k[12], 7, 1804603682); - d = ff(d, a, b, c, k[13], 12, -40341101); - c = ff(c, d, a, b, k[14], 17, -1502002290); - b = ff(b, c, d, a, k[15], 22, 1236535329); - - a = gg(a, b, c, d, k[1], 5, -165796510); - d = gg(d, a, b, c, k[6], 9, -1069501632); - c = gg(c, d, a, b, k[11], 14, 643717713); - b = gg(b, c, d, a, k[0], 20, -373897302); - a = gg(a, b, c, d, k[5], 5, -701558691); - d = gg(d, a, b, c, k[10], 9, 38016083); - c = gg(c, d, a, b, k[15], 14, -660478335); - b = gg(b, c, d, a, k[4], 20, -405537848); - a = gg(a, b, c, d, k[9], 5, 568446438); - d = gg(d, a, b, c, k[14], 9, -1019803690); - c = gg(c, d, a, b, k[3], 14, -187363961); - b = gg(b, c, d, a, k[8], 20, 1163531501); - a = gg(a, b, c, d, k[13], 5, -1444681467); - d = gg(d, a, b, c, k[2], 9, -51403784); - c = gg(c, d, a, b, k[7], 14, 1735328473); - b = gg(b, c, d, a, k[12], 20, -1926607734); - - a = hh(a, b, c, d, k[5], 4, -378558); - d = hh(d, a, b, c, k[8], 11, -2022574463); - c = hh(c, d, a, b, k[11], 16, 1839030562); - b = hh(b, c, d, a, k[14], 23, -35309556); - a = hh(a, b, c, d, k[1], 4, -1530992060); - d = hh(d, a, b, c, k[4], 11, 1272893353); - c = hh(c, d, a, b, k[7], 16, -155497632); - b = hh(b, c, d, a, k[10], 23, -1094730640); - a = hh(a, b, c, d, k[13], 4, 681279174); - d = hh(d, a, b, c, k[0], 11, -358537222); - c = hh(c, d, a, b, k[3], 16, -722521979); - b = hh(b, c, d, a, k[6], 23, 76029189); - a = hh(a, b, c, d, k[9], 4, -640364487); - d = hh(d, a, b, c, k[12], 11, -421815835); - c = hh(c, d, a, b, k[15], 16, 530742520); - b = hh(b, c, d, a, k[2], 23, -995338651); - - a = ii(a, b, c, d, k[0], 6, -198630844); - d = ii(d, a, b, c, k[7], 10, 1126891415); - c = ii(c, d, a, b, k[14], 15, -1416354905); - b = ii(b, c, d, a, k[5], 21, -57434055); - a = ii(a, b, c, d, k[12], 6, 1700485571); - d = ii(d, a, b, c, k[3], 10, -1894986606); - c = ii(c, d, a, b, k[10], 15, -1051523); - b = ii(b, c, d, a, k[1], 21, -2054922799); - a = ii(a, b, c, d, k[8], 6, 1873313359); - d = ii(d, a, b, c, k[15], 10, -30611744); - c = ii(c, d, a, b, k[6], 15, -1560198380); - b = ii(b, c, d, a, k[13], 21, 1309151649); - a = ii(a, b, c, d, k[4], 6, -145523070); - d = ii(d, a, b, c, k[11], 10, -1120210379); - c = ii(c, d, a, b, k[2], 15, 718787259); - b = ii(b, c, d, a, k[9], 21, -343485551); - - x[0] = (a + x[0]) >>> 0; - x[1] = (b + x[1]) >>> 0; - x[2] = (c + x[2]) >>> 0; - x[3] = (d + x[3]) >>> 0; +function md5cycle(x: Uint32Array, k: Uint32Array): void { + let a = x[0]!; + let b = x[1]!; + let c = x[2]!; + let d = x[3]!; + + a = ff(a, b, c, d, k[0]!, 7, -680876936); + d = ff(d, a, b, c, k[1]!, 12, -389564586); + c = ff(c, d, a, b, k[2]!, 17, 606105819); + b = ff(b, c, d, a, k[3]!, 22, -1044525330); + a = ff(a, b, c, d, k[4]!, 7, -176418897); + d = ff(d, a, b, c, k[5]!, 12, 1200080426); + c = ff(c, d, a, b, k[6]!, 17, -1473231341); + b = ff(b, c, d, a, k[7]!, 22, -45705983); + a = ff(a, b, c, d, k[8]!, 7, 1770035416); + d = ff(d, a, b, c, k[9]!, 12, -1958414417); + c = ff(c, d, a, b, k[10]!, 17, -42063); + b = ff(b, c, d, a, k[11]!, 22, -1990404162); + a = ff(a, b, c, d, k[12]!, 7, 1804603682); + d = ff(d, a, b, c, k[13]!, 12, -40341101); + c = ff(c, d, a, b, k[14]!, 17, -1502002290); + b = ff(b, c, d, a, k[15]!, 22, 1236535329); + + a = gg(a, b, c, d, k[1]!, 5, -165796510); + d = gg(d, a, b, c, k[6]!, 9, -1069501632); + c = gg(c, d, a, b, k[11]!, 14, 643717713); + b = gg(b, c, d, a, k[0]!, 20, -373897302); + a = gg(a, b, c, d, k[5]!, 5, -701558691); + d = gg(d, a, b, c, k[10]!, 9, 38016083); + c = gg(c, d, a, b, k[15]!, 14, -660478335); + b = gg(b, c, d, a, k[4]!, 20, -405537848); + a = gg(a, b, c, d, k[9]!, 5, 568446438); + d = gg(d, a, b, c, k[14]!, 9, -1019803690); + c = gg(c, d, a, b, k[3]!, 14, -187363961); + b = gg(b, c, d, a, k[8]!, 20, 1163531501); + a = gg(a, b, c, d, k[13]!, 5, -1444681467); + d = gg(d, a, b, c, k[2]!, 9, -51403784); + c = gg(c, d, a, b, k[7]!, 14, 1735328473); + b = gg(b, c, d, a, k[12]!, 20, -1926607734); + + a = hh(a, b, c, d, k[5]!, 4, -378558); + d = hh(d, a, b, c, k[8]!, 11, -2022574463); + c = hh(c, d, a, b, k[11]!, 16, 1839030562); + b = hh(b, c, d, a, k[14]!, 23, -35309556); + a = hh(a, b, c, d, k[1]!, 4, -1530992060); + d = hh(d, a, b, c, k[4]!, 11, 1272893353); + c = hh(c, d, a, b, k[7]!, 16, -155497632); + b = hh(b, c, d, a, k[10]!, 23, -1094730640); + a = hh(a, b, c, d, k[13]!, 4, 681279174); + d = hh(d, a, b, c, k[0]!, 11, -358537222); + c = hh(c, d, a, b, k[3]!, 16, -722521979); + b = hh(b, c, d, a, k[6]!, 23, 76029189); + a = hh(a, b, c, d, k[9]!, 4, -640364487); + d = hh(d, a, b, c, k[12]!, 11, -421815835); + c = hh(c, d, a, b, k[15]!, 16, 530742520); + b = hh(b, c, d, a, k[2]!, 23, -995338651); + + a = ii(a, b, c, d, k[0]!, 6, -198630844); + d = ii(d, a, b, c, k[7]!, 10, 1126891415); + c = ii(c, d, a, b, k[14]!, 15, -1416354905); + b = ii(b, c, d, a, k[5]!, 21, -57434055); + a = ii(a, b, c, d, k[12]!, 6, 1700485571); + d = ii(d, a, b, c, k[3]!, 10, -1894986606); + c = ii(c, d, a, b, k[10]!, 15, -1051523); + b = ii(b, c, d, a, k[1]!, 21, -2054922799); + a = ii(a, b, c, d, k[8]!, 6, 1873313359); + d = ii(d, a, b, c, k[15]!, 10, -30611744); + c = ii(c, d, a, b, k[6]!, 15, -1560198380); + b = ii(b, c, d, a, k[13]!, 21, 1309151649); + a = ii(a, b, c, d, k[4]!, 6, -145523070); + d = ii(d, a, b, c, k[11]!, 10, -1120210379); + c = ii(c, d, a, b, k[2]!, 15, 718787259); + b = ii(b, c, d, a, k[9]!, 21, -343485551); + + x[0] = (a + x[0]!) >>> 0; + x[1] = (b + x[1]!) >>> 0; + x[2] = (c + x[2]!) >>> 0; + x[3] = (d + x[3]!) >>> 0; } -export function md5(key: ArrayBufferLike) { +export function md5(key: ArrayBufferLike): Uint8Array { const dataView = new DataView(key); - let i; + let i: number; const l = dataView.byteLength; const n = l & ~63; const block = new Uint32Array(16); @@ -111,7 +111,6 @@ export function md5(key: ArrayBufferLike) { for (let w = 0; w < 16; ++w) { block[w] = dataView.getUint32(i + (w << 2), true); } - md5cycle(state, block); } @@ -142,7 +141,6 @@ export function md5(key: ArrayBufferLike) { for (; w < 16; ++w) { block[w] = 0; } - md5cycle(state, block); w = 0; } diff --git a/src/models.ts b/src/models.ts new file mode 100644 index 0000000..bd214d3 --- /dev/null +++ b/src/models.ts @@ -0,0 +1,112 @@ +export type JSONPrimitive = string | number | boolean | null; +export type JSONObject = { [key: string]: JSONValue }; +export type JSONArray = JSONValue[]; +export type JSONValue = JSONPrimitive | JSONObject | JSONArray; + +export type CustomFieldValueType = "text" | "string" | "number" | "json" | "boolean"; + +export interface CustomFieldValue { + name: string; + value: string | null; + type: CustomFieldValueType; +} + +export interface ExperimentVariant { + name: string; + config: string | null; +} + +export interface ExperimentApplication { + name: string; +} + +export interface ExperimentData { + id: number; + name: string; + unitType: string | null; + iteration: number; + fullOnVariant: number; + trafficSplit: number[]; + trafficSeedHi: number; + trafficSeedLo: number; + audience: string; + audienceStrict: boolean; + split: number[]; + seedHi: number; + seedLo: number; + variants: ExperimentVariant[]; + applications: ExperimentApplication[]; + customFieldValues: CustomFieldValue[] | null; +} + +export interface Assignment { + id: number; + iteration: number; + fullOnVariant: number; + unitType: string | null; + variant: number; + overridden: boolean; + assigned: boolean; + exposed: boolean; + eligible: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; + trafficSplit: number[] | null; + variables: Record | null; + attrsSeq: number; +} + +export type Experiment = { + data: ExperimentData; + variables: Record[]; +}; + +export interface Unit { + type: string; + uid: string | null; +} + +export interface Exposure { + id: number; + name: string; + exposedAt: number; + unit: string | null; + variant: number; + assigned: boolean; + eligible: boolean; + overridden: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; +} + +export interface Attribute { + name: string; + value: unknown; + setAt: number; +} + +export type Units = Record; + +export interface GoalAchievement { + name: string; + properties: Record | null; + achievedAt: number; +} + +export interface ContextData { + experiments?: ExperimentData[]; +} + +export type ApplicationObject = { name: string; version: number | string }; + +export interface PublishParams { + units: Unit[]; + publishedAt: number; + hashed: boolean; + sdkVersion: string; + attributes?: Attribute[]; + goals?: GoalAchievement[]; + exposures?: Exposure[]; +} diff --git a/src/murmur3_32.ts b/src/murmur3.ts similarity index 81% rename from src/murmur3_32.ts rename to src/murmur3.ts index 5d014cd..511853f 100644 --- a/src/murmur3_32.ts +++ b/src/murmur3.ts @@ -4,29 +4,28 @@ const C3 = 0xe6546b64; const imul32 = Math.imul; -function fmix32(h: number) { +function fmix32(h: number): number { h ^= h >>> 16; h = imul32(h, 0x85ebca6b); h ^= h >>> 13; h = imul32(h, 0xc2b2ae35); h ^= h >>> 16; - return h >>> 0; } -function rotl32(a: number, b: number) { +function rotl32(a: number, b: number): number { return (a << b) | (a >>> (32 - b)); } -function scramble32(block: number) { +function scramble32(block: number): number { return imul32(rotl32(imul32(block, C1), 15), C2); } -export function murmur3_32(key: ArrayBufferLike, hash?: number) { +export function murmur3_32(key: ArrayBufferLike, hash?: number): number { hash = (hash || 0) >>> 0; const dataView = new DataView(key); - let i; + let i: number; const n = dataView.byteLength & ~3; for (i = 0; i < n; i += 4) { const chunk = dataView.getUint32(i, true); diff --git a/src/provider.ts b/src/provider.ts index 30dac20..7375656 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -1,8 +1,12 @@ -import SDK from "./sdk"; -import { ClientRequestOptions } from "./client"; +import type { ContextData } from "./models"; +import type { ClientRequestOptions, ContextDataProvider } from "./interfaces"; -export class ContextDataProvider { - getContextData(sdk: SDK, requestOptions?: Partial) { - return sdk.getClient().getContext(requestOptions); +interface SDKLike { + getClient(): { getContext(options?: Partial): Promise }; +} + +export class DefaultContextDataProvider implements ContextDataProvider { + getContextData(sdk: SDKLike, requestOptions?: Partial): Promise { + return sdk.getClient().getContext(requestOptions) as Promise; } } diff --git a/src/publisher.ts b/src/publisher.ts index 79c5f14..0c844e7 100644 --- a/src/publisher.ts +++ b/src/publisher.ts @@ -1,19 +1,12 @@ -import Context, { Attribute, Exposure, Goal, Unit } from "./context"; -import SDK from "./sdk"; -import { ClientRequestOptions } from "./client"; +import type { PublishParams } from "./models"; +import type { ClientRequestOptions, ContextPublisher } from "./interfaces"; -export type PublishParams = { - units: Unit[]; - publishedAt: number; - hashed: boolean; - sdkVersion: string; - attributes?: Attribute[]; - goals?: Goal[]; - exposures?: Exposure[]; -}; +interface SDKLike { + getClient(): { publish(request: PublishParams, options?: ClientRequestOptions): Promise }; +} -export class ContextPublisher { - publish(request: PublishParams, sdk: SDK, _: Context, requestOptions?: ClientRequestOptions) { +export class DefaultContextPublisher implements ContextPublisher { + publish(request: PublishParams, sdk: SDKLike, _context: unknown, requestOptions?: ClientRequestOptions): Promise { return sdk.getClient().publish(request, requestOptions); } } diff --git a/src/rfdc.d.ts b/src/rfdc.d.ts deleted file mode 100644 index 8399146..0000000 --- a/src/rfdc.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "rfdc/default"; diff --git a/src/sdk.ts b/src/sdk.ts index 32ac4cf..fcb60af 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -1,136 +1,152 @@ -import Client, { ClientOptions, ClientRequestOptions } from "./client"; -import Context, { ContextData, ContextOptions, ContextParams, Exposure, Goal } from "./context"; -import { ContextPublisher, PublishParams } from "./publisher"; -import { ContextDataProvider } from "./provider"; -import { isLongLivedApp } from "./utils"; - -export type EventLoggerData = Error | Exposure | Goal | ContextData | PublishParams; - -export type EventName = "error" | "ready" | "refresh" | "publish" | "exposure" | "goal" | "finalize"; - -export type EventLogger = (context: Context, eventName: EventName, data?: EventLoggerData) => void; - -export type SDKOptions = { - client?: Client; - eventLogger?: EventLogger; +import { DefaultClient } from "./client"; +import { Context } from "./context"; +import { DefaultContextPublisher } from "./publisher"; +import { DefaultContextDataProvider } from "./provider"; +import type { + Client, + ClientOptions, + ClientRequestOptions, + ContextDataProvider, + ContextParams, + ContextPublisher, + EventLogger, + EventLoggerData, + SDKOptions, +} from "./interfaces"; +import type { ContextData } from "./models"; + +type ContextOptionsInput = { publisher?: ContextPublisher; - provider?: ContextDataProvider; + dataProvider?: ContextDataProvider; + eventLogger?: EventLogger; + refreshPeriod?: number; + publishDelay?: number; + includeSystemAttributes?: boolean; }; -export default class SDK { - static defaultEventLogger: EventLogger = (_, eventName, data) => { - if (eventName === "error") { - console.error(data); - } +type ContextOptionsFull = Required> & + Omit; + +function isLongLivedApp(): boolean { + return ( + (typeof window !== "undefined" && typeof window.document !== "undefined") || + (typeof navigator !== "undefined" && navigator.product === "ReactNative") + ); +} + +const CLIENT_OPTION_KEYS = [ + "application", + "agent", + "apiKey", + "endpoint", + "keepalive", + "environment", + "retries", + "timeout", + "fetchImpl", + "AbortControllerImpl", +]; + +export class SDK { + static defaultEventLogger: EventLogger = (_: unknown, eventName: string, data?: EventLoggerData) => { + if (eventName === "error") console.error(data); }; + private _eventLogger: EventLogger; private _publisher: ContextPublisher; private _provider: ContextDataProvider; private readonly _client: Client; constructor(options: ClientOptions & SDKOptions) { - const clientOptions = Object.assign( - { - agent: "absmartly-javascript-sdk", - }, - ...Object.entries(options || {}) - .filter( - (x) => - ["application", "agent", "apiKey", "endpoint", "keepalive", "environment", "retries", "timeout"].indexOf( - x[0] - ) !== -1 - ) - .map((x) => ({ [x[0]]: x[1] })) - ); - - options = Object.assign({}, options); - - this._client = options.client || new Client(clientOptions); + if (options.client) { + this._client = options.client; + } else { + const clientOptions = Object.assign( + { agent: "absmartly-javascript-sdk" }, + ...Object.entries(options || {}) + .filter((x) => CLIENT_OPTION_KEYS.indexOf(x[0]) !== -1) + .map((x) => ({ [x[0]]: x[1] })), + ) as ClientOptions; + + this._client = new DefaultClient(clientOptions); + } + this._eventLogger = options.eventLogger || SDK.defaultEventLogger; - this._publisher = options.publisher || new ContextPublisher(); - this._provider = options.provider || new ContextDataProvider(); + this._publisher = options.publisher || new DefaultContextPublisher(); + this._provider = options.provider || new DefaultContextDataProvider(); } - getContextData(requestOptions: ClientRequestOptions) { + getContextData(requestOptions?: Partial): Promise { return this._provider.getContextData(this, requestOptions); } createContext( params: ContextParams, - options?: Partial, - requestOptions?: Partial - ) { + options?: Partial, + requestOptions?: Partial, + ): Context { SDK._validateParams(params); - const fullOptions = SDK._contextOptions(options); const data = this._provider.getContextData(this, requestOptions); return new Context(this, fullOptions, params, data); } - setEventLogger(logger: EventLogger) { + createContextWith( + params: ContextParams, + data: ContextData | Promise, + options?: Partial, + ): Context { + SDK._validateParams(params); + const fullOptions = SDK._contextOptions(options); + return new Context(this, fullOptions, params, data); + } + + setEventLogger(logger: EventLogger): void { this._eventLogger = logger; } - getEventLogger() { + getEventLogger(): EventLogger { return this._eventLogger; } - setContextPublisher(publisher: ContextPublisher) { + setContextPublisher(publisher: ContextPublisher): void { this._publisher = publisher; } - getContextPublisher() { + getContextPublisher(): ContextPublisher { return this._publisher; } - setContextDataProvider(provider: ContextDataProvider) { + setContextDataProvider(provider: ContextDataProvider): void { this._provider = provider; } - getContextDataProvider() { + getContextDataProvider(): ContextDataProvider { return this._provider; } - getClient() { + getClient(): Client { return this._client; } - createContextWith( - params: ContextParams, - data: ContextData | Promise, - options?: Partial - ) { - SDK._validateParams(params); - - const fullOptions = SDK._contextOptions(options); - - return new Context(this, fullOptions, params, data); - } - - private static _contextOptions(options?: Partial): ContextOptions { + private static _contextOptions(options?: Partial): ContextOptionsFull { return Object.assign( - { - publishDelay: isLongLivedApp() ? 100 : -1, - refreshPeriod: 0, - }, - options || {} - ); + { publishDelay: isLongLivedApp() ? 100 : -1, refreshPeriod: 0 }, + options || {}, + ) as ContextOptionsFull; } - private static _validateParams(params: ContextParams) { - Object.entries(params.units).forEach((entry) => { - const type = typeof entry[1]; + private static _validateParams(params: ContextParams): void { + for (const [key, value] of Object.entries(params.units)) { + const type = typeof value; if (type !== "string" && type !== "number") { throw new Error( - `Unit '${entry[0]}' UID is of unsupported type '${type}'. UID must be one of ['string', 'number']` + `Unit '${key}' UID is of unsupported type '${type}'. UID must be one of ['string', 'number']`, ); } - - if (typeof entry[1] === "string") { - if (entry[1].length === 0) { - throw new Error(`Unit '${entry[0]}' UID length must be >= 1`); - } + if (typeof value === "string" && value.length === 0) { + throw new Error(`Unit '${key}' UID length must be >= 1`); } - }); + } } } diff --git a/src/utils.ts b/src/utils.ts index 529d486..b059a8e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,36 +1,5 @@ -import { md5 } from "./md5"; - -export const getApplicationName = (app: string | { name: string; version: number | string }): string => - typeof app !== "string" ? app.name : app; - -export const getApplicationVersion = (app: string | { name: string; version: number | string }): number | string => - typeof app !== "string" ? app.version : 0; - -function isBrowser() { - return typeof window !== "undefined" && typeof window.document !== "undefined"; -} - -function isReactNative() { - return typeof navigator !== "undefined" && navigator.product === "ReactNative"; -} - -export function isLongLivedApp() { - return isBrowser() || isReactNative(); -} - -export function isWorker() { - return typeof self === "object" && self.constructor && self.constructor.name === "DedicatedWorkerGlobalScope"; -} - -export function isNumeric(value: unknown): value is number { - return typeof value === "number"; -} - export function isObject(value: unknown): value is Record { - if (!(value instanceof Object)) { - return false; - } - + if (!(value instanceof Object)) return false; const proto = Object.getPrototypeOf(value); return proto == null || proto === Object.prototype; } @@ -39,21 +8,17 @@ export function isPromise(value: unknown): value is Promise { return value !== null && typeof value === "object" && typeof (value as Promise).then === "function"; } -function arrayEqualsDeep(a: unknown[], b: unknown[], astack: unknown[] = [], bstack: unknown[] = []) { - let len = astack?.length ?? 0; +function arrayEqualsDeep(a: unknown[], b: unknown[], astack: unknown[] = [], bstack: unknown[] = []): boolean { + let len = astack.length; while (len--) { if (astack[len] === a) return bstack[len] === b; } - astack = astack ?? []; - bstack = bstack ?? []; - astack.push(a); bstack.push(b); len = a.length; while (len--) { - // eslint-disable-next-line no-use-before-define if (!isEqualsDeep(a[len], b[len], astack, bstack)) return false; } @@ -68,11 +33,11 @@ function objectEqualsDeep( b: Record, keys: string[], astack?: unknown[], - bstack?: unknown[] -) { + bstack?: unknown[], +): boolean { let len = astack?.length ?? 0; while (len--) { - if (astack && astack[len] === a) return bstack && bstack[len] === b; + if (astack && astack[len] === a) return bstack !== undefined && bstack[len] === b; } astack = astack ?? []; @@ -83,11 +48,8 @@ function objectEqualsDeep( len = keys.length; while (len--) { - const key = keys[len]; - // eslint-disable-next-line no-prototype-builtins + const key = keys[len]!; if (!Object.prototype.hasOwnProperty.call(b, key)) return false; - - // eslint-disable-next-line no-use-before-define if (!isEqualsDeep(a[key], b[key], astack, bstack)) return false; } @@ -97,7 +59,7 @@ function objectEqualsDeep( return true; } -export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack?: unknown[]) { +export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack?: unknown[]): boolean { if (a === b) return true; if (typeof a !== typeof b) return false; @@ -105,7 +67,7 @@ export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack? case "boolean": return a === b; case "number": - if (Number.isNaN(a)) return Number.isNaN(b); + if (Number.isNaN(a)) return Number.isNaN(b as number); return a === b; case "string": return a === b; @@ -122,18 +84,16 @@ export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack? if (a.length === b.length) { return arrayEqualsDeep(a, b, astack, bstack); } - } else { - if (a && b) { - const keys = Object.keys(a); - if (keys.length === Object.keys(b).length) { - return objectEqualsDeep( - a as Record, - b as Record, - keys, - astack, - bstack - ); - } + } else if (a && b) { + const keys = Object.keys(a); + if (keys.length === Object.keys(b as Record).length) { + return objectEqualsDeep( + a as Record, + b as Record, + keys, + astack, + bstack, + ); } } break; @@ -144,88 +104,15 @@ export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack? return false; } -export function arrayEqualsShallow(a?: unknown[], b?: unknown[]) { +export function arrayEqualsShallow(a?: unknown[] | null, b?: unknown[] | null): boolean { return a === b || (a?.length === b?.length && !a?.some((va, vi) => b && va !== b[vi])); } -export function stringToUint8Array(value: string) { - const n = value.length; - const array = new Array(value.length); - - let k = 0; - for (let i = 0; i < n; ++i) { - const c = value.charCodeAt(i); - if (c < 0x80) { - array[k++] = c; - } else if (c < 0x800) { - array[k++] = (c >> 6) | 192; - array[k++] = (c & 63) | 128; - } else { - array[k++] = (c >> 12) | 224; - array[k++] = ((c >> 6) & 63) | 128; - array[k++] = (c & 63) | 128; - } - } - return Uint8Array.from(array); -} - -const Base64URLNoPaddingChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - -export function base64UrlNoPadding(value: Uint8Array) { - const chars = Base64URLNoPaddingChars; - - const remaining = value.byteLength % 3; - const encodeLen = ((value.byteLength / 3) | 0) * 4 + (remaining === 0 ? 0 : remaining === 1 ? 2 : 3); - const result = new Array(encodeLen); - - let i; - let out = 0; - const len = value.byteLength - remaining; - for (i = 0; i < len; i += 3) { - const bytes = (value[i] << 16) | (value[i + 1] << 8) | value[i + 2]; - result[out] = chars[(bytes >> 18) & 63]; - result[out + 1] = chars[(bytes >> 12) & 63]; - result[out + 2] = chars[(bytes >> 6) & 63]; - result[out + 3] = chars[bytes & 63]; - out += 4; - } - - switch (remaining) { - case 2: - { - const bytes = (value[i] << 16) | (value[i + 1] << 8); - result[out] = chars[(bytes >> 18) & 63]; - result[out + 1] = chars[(bytes >> 12) & 63]; - result[out + 2] = chars[(bytes >> 6) & 63]; - } - break; - case 1: - { - const bytes = value[i] << 16; - result[out] = chars[(bytes >> 18) & 63]; - result[out + 1] = chars[(bytes >> 12) & 63]; - } - break; - default: - break; - } - - return result.join(""); -} - -export function hashUnit(value: string | number) { - const unit = typeof value === "string" ? value : value.toFixed(0); - return base64UrlNoPadding(md5(stringToUint8Array(unit).buffer)); -} - -export function chooseVariant(split: number[], prob: number) { +export function chooseVariant(split: number[], prob: number): number { let cumSum = 0.0; for (let i = 0; i < split.length; ++i) { - cumSum += split[i]; - if (prob < cumSum) { - return i; - } + cumSum += split[i]!; + if (prob < cumSum) return i; } - return split.length - 1; } diff --git a/src/version.ts b/src/version.ts index 9bcd66f..478f50d 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const SDK_VERSION = "1.13.4"; +export const SDK_VERSION = "2.0.0"; diff --git a/tsconfig.json b/tsconfig.json index acd4126..5a2ac7e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,25 @@ { - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], - "skipLibCheck": true, - "sourceMap": true, - "outDir": "./js", - "allowJs": true, - "declaration": true, - "declarationMap": true, - "declarationDir": "./types", - "isolatedModules": true, - "moduleResolution": "node", - "removeComments": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "resolveJsonModule": true, - "baseUrl": "." - }, - "exclude": ["node_modules"], - "include": ["./src/**/*.ts"] + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "skipLibCheck": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "isolatedModules": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "node_modules"] } diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..a8d5196 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from "tsup"; + +export default defineConfig([ + { + entry: ["src/index.ts"], + format: ["esm", "cjs", "iife"], + globalName: "absmartly", + dts: true, + sourcemap: true, + clean: true, + minify: true, + target: "es2022", + outExtension({ format }) { + if (format === "iife") return { js: ".global.js" }; + return {}; + }, + }, + { + entry: ["src/index.ts"], + format: ["iife"], + globalName: "absmartly", + sourcemap: true, + minify: true, + target: "es2015", + outExtension() { + return { js: ".legacy.js" }; + }, + }, +]); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..f9ae823 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + coverage: { + provider: "v8", + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.ts", "src/version.ts"], + }, + }, +}); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 15ff2da..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,55 +0,0 @@ -const TerserPlugin = require("terser-webpack-plugin"); -const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; - -const env = process.env.NODE_ENV || "development"; -const analyze = process.env.WEBPACK_ANALYZE || false; - -module.exports = function () { - const config = { - devtool: "source-map", - entry: { - absmartly: ["./js/browser.js"], - }, - - target: "browserslist", - - output: { - library: "absmartly", - libraryTarget: "umd", - libraryExport: "default", - }, - - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - use: ["babel-loader"], - }, - ], - }, - - mode: env, - }; - - if (env === "production") { - config.output.filename = "[name].min.js"; - config.performance = { - hints: "error", - maxAssetSize: 131072, - }; - - config.optimization = { - minimize: true, - minimizer: [new TerserPlugin()], - }; - - if (analyze) { - config.plugins = [new BundleAnalyzerPlugin()]; - } - } else { - config.output.filename = "[name].js"; - } - - return config; -};