diff --git a/.gitmodules b/.gitmodules
index bba15b6..d7c6389 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +1,4 @@
[submodule "tests/engine/engine-tests/engine-test-data"]
path = tests/engine/engine-tests/engine-test-data
url = git@github.com:Flagsmith/engine-test-data.git
- branch = v1.0.0
+ branch = feat/context-values-intensifies
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 938cbdb..c221482 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,6 +1,7 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
+npm run generate-engine-types
npm run lint
git add ./flagsmith-engine ./sdk ./tests ./index.ts ./.github
npm run test
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 133c163..1e0ca02 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,83 +1,86 @@
+
# [v6.1.0](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v6.1.0) - 2025-06-18
## What's Changed
-* Bump undici from 6.21.1 to 6.21.2 by [@dependabot](https://github.com/dependabot) in [#184](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/184)
-* feat: Export FeatureModel to enable custom offline handler by [@phiggins](https://github.com/phiggins) in [#187](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/187)
-* Update test running instructions in README and other housekeeping by [@phiggins](https://github.com/phiggins) in [#186](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/186)
-* Bump vite from 5.4.18 to 5.4.19 by [@dependabot](https://github.com/dependabot) in [#185](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/185)
-* feat: Export BaseFlag, FlagsmithConfig, FlagsmithValue, TraitConfig types by [@rolodato](https://github.com/rolodato) in [#188](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/188)
+- Bump undici from 6.21.1 to 6.21.2 by [@dependabot](https://github.com/dependabot) in [#184](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/184)
+- feat: Export FeatureModel to enable custom offline handler by [@phiggins](https://github.com/phiggins) in [#187](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/187)
+- Update test running instructions in README and other housekeeping by [@phiggins](https://github.com/phiggins) in [#186](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/186)
+- Bump vite from 5.4.18 to 5.4.19 by [@dependabot](https://github.com/dependabot) in [#185](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/185)
+- feat: Export BaseFlag, FlagsmithConfig, FlagsmithValue, TraitConfig types by [@rolodato](https://github.com/rolodato) in [#188](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/188)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v6.0.1...v6.1.0
[Changes][v6.1.0]
-
+
# [v6.0.1](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v6.0.1) - 2025-04-24
## What's Changed
-* Remove uses of `any` in models.ts by [@phiggins](https://github.com/phiggins) in [#180](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/180)
-* Bump esbuild from 0.14.54 to 0.25.0 by [@dependabot](https://github.com/dependabot) in [#175](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/175)
-* Bump vite from 5.4.14 to 5.4.18 by [@dependabot](https://github.com/dependabot) in [#182](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/182)
+
+- Remove uses of `any` in models.ts by [@phiggins](https://github.com/phiggins) in [#180](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/180)
+- Bump esbuild from 0.14.54 to 0.25.0 by [@dependabot](https://github.com/dependabot) in [#175](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/175)
+- Bump vite from 5.4.14 to 5.4.18 by [@dependabot](https://github.com/dependabot) in [#182](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/182)
## New Contributors
-* [@phiggins](https://github.com/phiggins) made their first contribution in [#180](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/180)
+
+- [@phiggins](https://github.com/phiggins) made their first contribution in [#180](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/180)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v6.0.0...v6.0.1
[Changes][v6.0.1]
-
+
# [v6.0.0](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v6.0.0) - 2025-03-24
## What's Changed
### BREAKING CHANGES
-* `Flagsmith.environment` was removed. Use `getEnvironment` instead. This returns a Promise, and not a reference to the environment which could be uninitialised.
-* `onEnvironmentChange` handlers can now be invoked with an `undefined` environment if an error occurred.
-* The `Flagsmith` client now returns an error if initialised with local evaluation enabled but without a server-side SDK key. Previously, it would log an error and continue.
+
+- `Flagsmith.environment` was removed. Use `getEnvironment` instead. This returns a Promise, and not a reference to the environment which could be uninitialised.
+- `onEnvironmentChange` handlers can now be invoked with an `undefined` environment if an error occurred.
+- The `Flagsmith` client now returns an error if initialised with local evaluation enabled but without a server-side SDK key. Previously, it would log an error and continue.
### New features
-* Added a new `requestRetryDelayMilliseconds` which controls how long the SDK will wait before retrying any failed HTTP requests. Previously, this was hard-coded to always be 1 second.
-* Added a `getEnvironment` method which returns the SDK's current local environment state as a Promise.
+- Added a new `requestRetryDelayMilliseconds` which controls how long the SDK will wait before retrying any failed HTTP requests. Previously, this was hard-coded to always be 1 second.
+- Added a `getEnvironment` method which returns the SDK's current local environment state as a Promise.
### Bug fixes
-* `getIdentityFlags` now uses any provided default flag handler if it fails, instead of just returning an error.
-* Setting `environmentRefreshInterval` to `0` now prevents any environment polling from happening.
-* Fixed a bug where if the SDK initially failed to fetch the environment document, then `getIdentityFlags` would always fail with an error even if the environment was later fetched successfully (https://github.com/Flagsmith/flagsmith-nodejs-client/issues/177).
-
-
+- `getIdentityFlags` now uses any provided default flag handler if it fails, instead of just returning an error.
+- Setting `environmentRefreshInterval` to `0` now prevents any environment polling from happening.
+- Fixed a bug where if the SDK initially failed to fetch the environment document, then `getIdentityFlags` would always fail with an error even if the environment was later fetched successfully (https://github.com/Flagsmith/flagsmith-nodejs-client/issues/177).
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v5.1.1...v6.0.0
[Changes][v6.0.0]
-
+
# [v5.1.1](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v5.1.1) - 2025-02-10
## What's Changed
-* Bump undici from 6.19.8 to 6.21.1 by [@dependabot](https://github.com/dependabot) in [#170](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/170)
-* Bump vite from 5.4.8 to 5.4.14 by [@dependabot](https://github.com/dependabot) in [#171](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/171)
-* Bump vitest and @vitest/coverage-v8 by [@dependabot](https://github.com/dependabot) in [#173](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/173)
+- Bump undici from 6.19.8 to 6.21.1 by [@dependabot](https://github.com/dependabot) in [#170](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/170)
+- Bump vite from 5.4.8 to 5.4.14 by [@dependabot](https://github.com/dependabot) in [#171](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/171)
+- Bump vitest and @vitest/coverage-v8 by [@dependabot](https://github.com/dependabot) in [#173](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/173)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v5.1.0...v5.1.1
[Changes][v5.1.1]
-
+
# [v5.1.0](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v5.1.0) - 2025-01-20
## What's Changed
-* feat: Allow configuring analytics API endpoint separate from flags API by [@rolodato](https://github.com/rolodato) in [#168](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/168)
-* ci: Run tests on currently maintained Node LTS versions by [@rolodato](https://github.com/rolodato) in [#169](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/169)
+
+- feat: Allow configuring analytics API endpoint separate from flags API by [@rolodato](https://github.com/rolodato) in [#168](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/168)
+- ci: Run tests on currently maintained Node LTS versions by [@rolodato](https://github.com/rolodato) in [#169](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/169)
## Deprecated
@@ -87,34 +90,35 @@ The [`baseApiUrl` constructor argument of `AnalyticsProcessor`](https://www.tsdo
[Changes][v5.1.0]
-
+
# [v5.0.1](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v5.0.1) - 2025-01-14
## What's Changed
-* fix: Return 0 as number flag value instead of undefined by [@rolodato](https://github.com/rolodato) in [#167](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/167)
+- fix: Return 0 as number flag value instead of undefined by [@rolodato](https://github.com/rolodato) in [#167](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/167)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v5.0.0...v5.0.1
[Changes][v5.0.1]
-
+
# [v5.0.0](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v5.0.0) - 2024-11-28
## What's Changed
-* fix: Export offline handler types by [@rolodato](https://github.com/rolodato) in [#166](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/166)
-* feat!: Simplify FlagsmithCache interface by [@rolodato](https://github.com/rolodato) in [#165](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/165)
+
+- fix: Export offline handler types by [@rolodato](https://github.com/rolodato) in [#166](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/166)
+- feat!: Simplify FlagsmithCache interface by [@rolodato](https://github.com/rolodato) in [#165](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/165)
## BREAKING CHANGES
The `FlagsmithCache` interface has been simplified. In practice, this will not affect most users:
-* Removed `has` method
-* Removed `ttl` parameter from `set`
-* Changed `set` return type to `Promise`
-* Changed `get` return type to `Promise`
+- Removed `has` method
+- Removed `ttl` parameter from `set`
+- Changed `set` return type to `Promise`
+- Changed `get` return type to `Promise`
`FlagsmithCache` since 5.0.0: https://www.tsdocs.dev/docs/flagsmith-nodejs/5.0.0/interfaces/FlagsmithCache.html
`FlagsmithCache` prior to 5.0.0: https://www.tsdocs.dev/docs/flagsmith-nodejs/4.0.0/interfaces/FlagsmithCache.html
@@ -123,14 +127,15 @@ The `FlagsmithCache` interface has been simplified. In practice, this will not a
[Changes][v5.0.0]
-
+
# [v4.0.0](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v4.0.0) - 2024-11-07
## What's Changed
-* feat: Support transient identities and traits by [@novakzaballa](https://github.com/novakzaballa) in [#158](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/158)
-* feat!: Custom fetch support, remove node-fetch, ESM+CJS dual build, migrate to vitest, TS fixes, test improvements by [@rolodato](https://github.com/rolodato) in [#162](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/162)
-* feat!: Remove all uses of CJS, add named Flagsmith export by [@rolodato](https://github.com/rolodato) in [#163](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/163)
+
+- feat: Support transient identities and traits by [@novakzaballa](https://github.com/novakzaballa) in [#158](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/158)
+- feat!: Custom fetch support, remove node-fetch, ESM+CJS dual build, migrate to vitest, TS fixes, test improvements by [@rolodato](https://github.com/rolodato) in [#162](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/162)
+- feat!: Remove all uses of CJS, add named Flagsmith export by [@rolodato](https://github.com/rolodato) in [#163](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/163)
### BREAKING CHANGES
@@ -142,255 +147,264 @@ In 3.x and earlier, `Flagsmith` is the default export:
```js
// ES modules
-import Flagsmith from 'flagsmith-nodejs'
+import Flagsmith from 'flagsmith-nodejs';
```
```js
// CommonJS
-const Flagsmith = require('flagsmith-nodejs')
+const Flagsmith = require('flagsmith-nodejs');
```
In 4.x, you must use the named export:
```js
// ES modules
-import { Flagsmith } from 'flagsmith-nodejs'
+import { Flagsmith } from 'flagsmith-nodejs';
```
```js
// CommonJS
-const { Flagsmith } = require('flagsmith-nodejs')
+const { Flagsmith } = require('flagsmith-nodejs');
```
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.3.3...v4.0.0
[Changes][v4.0.0]
-
+
# [v3.3.3](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.3.3) - 2024-07-12
## What's Changed
-* Cancel timeout when it is no longer needed by [@wheineman-sunrun](https://github.com/wheineman-sunrun) in [#141](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/141)
+
+- Cancel timeout when it is no longer needed by [@wheineman-sunrun](https://github.com/wheineman-sunrun) in [#141](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/141)
## New Contributors
-* [@wheineman-sunrun](https://github.com/wheineman-sunrun) made their first contribution in [#141](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/141)
+
+- [@wheineman-sunrun](https://github.com/wheineman-sunrun) made their first contribution in [#141](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/141)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.3.2...v3.3.3
[Changes][v3.3.3]
-
+
# [v3.3.2](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.3.2) - 2024-05-23
## What's Changed
-* fix: handle null traits for regex evaluations by [@matthewelwell](https://github.com/matthewelwell) in [#152](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/152)
+- fix: handle null traits for regex evaluations by [@matthewelwell](https://github.com/matthewelwell) in [#152](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/152)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.3.1...v3.3.2
[Changes][v3.3.2]
-
+
# [v3.3.1](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.3.1) - 2024-05-08
## What's Changed
-* fix: only flush analytics once if requested concurrently by [@rolodato](https://github.com/rolodato) in [#148](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/148)
-* fix: error evaluating CONTAINS / NOT_CONTAINS for null traits by [@matthewelwell](https://github.com/matthewelwell) in [#150](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/150)
-* Bump version 3.3.1 by [@matthewelwell](https://github.com/matthewelwell) in [#151](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/151)
+
+- fix: only flush analytics once if requested concurrently by [@rolodato](https://github.com/rolodato) in [#148](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/148)
+- fix: error evaluating CONTAINS / NOT_CONTAINS for null traits by [@matthewelwell](https://github.com/matthewelwell) in [#150](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/150)
+- Bump version 3.3.1 by [@matthewelwell](https://github.com/matthewelwell) in [#151](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/151)
## New Contributors
-* [@rolodato](https://github.com/rolodato) made their first contribution in [#148](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/148)
+
+- [@rolodato](https://github.com/rolodato) made their first contribution in [#148](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/148)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.3.0...v3.3.1
[Changes][v3.3.1]
-
+
# [Version 3.3.0 (v3.3.0)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.3.0) - 2024-04-19
## What's Changed
-* feat: Identity overrides in local evaluation mode by [@khvn26](https://github.com/khvn26) in [#143](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/143)
-* Bump @babel/traverse from 7.17.3 to 7.23.2 by [@dependabot](https://github.com/dependabot) in [#137](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/137)
-* chore: export FlagsmithConfig from index by [@novakzaballa](https://github.com/novakzaballa) in [#139](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/139)
-* chore: remove examples by [@dabeeeenster](https://github.com/dabeeeenster) in [#145](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/145)
+- feat: Identity overrides in local evaluation mode by [@khvn26](https://github.com/khvn26) in [#143](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/143)
+- Bump @babel/traverse from 7.17.3 to 7.23.2 by [@dependabot](https://github.com/dependabot) in [#137](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/137)
+- chore: export FlagsmithConfig from index by [@novakzaballa](https://github.com/novakzaballa) in [#139](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/139)
+- chore: remove examples by [@dabeeeenster](https://github.com/dabeeeenster) in [#145](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/145)
## New Contributors
-* [@khvn26](https://github.com/khvn26) made their first contribution in [#143](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/143)
+
+- [@khvn26](https://github.com/khvn26) made their first contribution in [#143](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/143)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.2.0...v3.3.0
[Changes][v3.3.0]
-
+
# [Version 3.2.0 (v3.2.0)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.2.0) - 2023-10-25
## What's Changed
-* feat: offline-mode by [@novakzaballa](https://github.com/novakzaballa) in [#136](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/136)
+- feat: offline-mode by [@novakzaballa](https://github.com/novakzaballa) in [#136](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/136)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.1.1...v3.2.0
[Changes][v3.2.0]
-
+
# [Version 3.1.1 (v3.1.1)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.1.1) - 2023-08-21
## What's Changed
-* fix: Default requestTimeout by [@novakzaballa](https://github.com/novakzaballa) in [#133](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/133)
+- fix: Default requestTimeout by [@novakzaballa](https://github.com/novakzaballa) in [#133](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/133)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.1.0...v3.1.1
[Changes][v3.1.1]
-
+
# [Version 3.1.0 (v3.1.0)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.1.0) - 2023-08-07
## What's Changed
-* Add 10 secs by default to requestTimeoutSeconds by [@novakzaballa](https://github.com/novakzaballa) in [#128](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/128)
-* Bump version to 3.1.0 by [@novakzaballa](https://github.com/novakzaballa) in [#129](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/129)
-* Bump word-wrap from 1.2.3 to 1.2.4 by [@dependabot](https://github.com/dependabot) in [#127](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/127)
-* Bump tough-cookie from 4.0.0 to 4.1.3 by [@dependabot](https://github.com/dependabot) in [#125](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/125)
-* Lazily calculate the hash by [@eldar-gamisoniya](https://github.com/eldar-gamisoniya) in [#130](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/130)
+
+- Add 10 secs by default to requestTimeoutSeconds by [@novakzaballa](https://github.com/novakzaballa) in [#128](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/128)
+- Bump version to 3.1.0 by [@novakzaballa](https://github.com/novakzaballa) in [#129](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/129)
+- Bump word-wrap from 1.2.3 to 1.2.4 by [@dependabot](https://github.com/dependabot) in [#127](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/127)
+- Bump tough-cookie from 4.0.0 to 4.1.3 by [@dependabot](https://github.com/dependabot) in [#125](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/125)
+- Lazily calculate the hash by [@eldar-gamisoniya](https://github.com/eldar-gamisoniya) in [#130](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/130)
## New Contributors
-* [@novakzaballa](https://github.com/novakzaballa) made their first contribution in [#128](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/128)
-* [@eldar-gamisoniya](https://github.com/eldar-gamisoniya) made their first contribution in [#130](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/130)
+
+- [@novakzaballa](https://github.com/novakzaballa) made their first contribution in [#128](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/128)
+- [@eldar-gamisoniya](https://github.com/eldar-gamisoniya) made their first contribution in [#130](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/130)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.0.1...v3.1.0
[Changes][v3.1.0]
-
+
# [Version 3.0.1 (v3.0.1)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.0.1) - 2023-06-27
## What's Changed
-* Fix deploy action by [@kyle-ssg](https://github.com/kyle-ssg) in [#121](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/121)
-* Bump semver from 7.3.7 to 7.5.2 by [@dependabot](https://github.com/dependabot) in [#122](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/122)
+- Fix deploy action by [@kyle-ssg](https://github.com/kyle-ssg) in [#121](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/121)
+- Bump semver from 7.3.7 to 7.5.2 by [@dependabot](https://github.com/dependabot) in [#122](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/122)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v3.0.0...v3.0.1
[Changes][v3.0.1]
-
+
# [Version 3.0.0 (v3.0.0)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v3.0.0) - 2023-06-15
## What's Changed
-* **BREAKING CHANGE**: Ensure percentage split evaluations are consistent by [@matthewelwell](https://github.com/matthewelwell) in [#119](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/119)
-WARNING: We modified the local evaluation behaviour. You may see different flags returned to identities attributed to your percentage split-based segments after upgrading to this version.
+- **BREAKING CHANGE**: Ensure percentage split evaluations are consistent by [@matthewelwell](https://github.com/matthewelwell) in [#119](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/119)
+WARNING: We modified the local evaluation behaviour. You may see different flags returned to identities attributed to your percentage split-based segments after upgrading to this version.
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v2.5.2...v3.0.0
[Changes][v3.0.0]
-
+
# [Version 2.5.2 (v2.5.2)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v2.5.2) - 2023-03-07
## What's Changed
-* Fix timeout not using default flags by [@matthewelwell](https://github.com/matthewelwell) in [#112](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/112)
-* Release 2.5.2 by [@matthewelwell](https://github.com/matthewelwell) in [#111](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/111)
+- Fix timeout not using default flags by [@matthewelwell](https://github.com/matthewelwell) in [#112](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/112)
+- Release 2.5.2 by [@matthewelwell](https://github.com/matthewelwell) in [#111](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/111)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v2.5.1...v2.5.2
[Changes][v2.5.2]
-
+
# [Version 2.5.1 (v2.5.1)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v2.5.1) - 2023-01-06
## What's Changed
-* Ensure local evaluation returns consistent MV values by [@matthewelwell](https://github.com/matthewelwell) in [#103](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/103)
-* Add logic to check for empty identifiers in `getIdentity___` methods by [@matthewelwell](https://github.com/matthewelwell) in [#104](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/104)
-* Bump json5 from 2.2.0 to 2.2.3 by [@dependabot](https://github.com/dependabot) in [#101](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/101)
-* Release 2.5.1 by [@matthewelwell](https://github.com/matthewelwell) in [#102](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/102)
+- Ensure local evaluation returns consistent MV values by [@matthewelwell](https://github.com/matthewelwell) in [#103](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/103)
+- Add logic to check for empty identifiers in `getIdentity___` methods by [@matthewelwell](https://github.com/matthewelwell) in [#104](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/104)
+- Bump json5 from 2.2.0 to 2.2.3 by [@dependabot](https://github.com/dependabot) in [#101](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/101)
+- Release 2.5.1 by [@matthewelwell](https://github.com/matthewelwell) in [#102](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/102)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v2.5.0...v2.5.1
[Changes][v2.5.1]
-
+
# [Version 2.5.0 (v2.5.0)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v2.5.0) - 2023-01-05
## What's Changed
-* Bump json5 from 2.1.0 to 2.2.3 in /examples/caching by [@dependabot](https://github.com/dependabot) in [#100](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/100)
-* Bump json5 from 2.1.0 to 2.2.3 in /examples/local-evaluation by [@dependabot](https://github.com/dependabot) in [#99](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/99)
-* Bump json5 from 2.1.0 to 2.2.3 in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#98](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/98)
-* Bump json5 from 2.1.0 to 2.2.3 in /examples/api-proxy by [@dependabot](https://github.com/dependabot) in [#97](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/97)
-* Bump json5 from 2.1.0 to 2.2.3 in /examples/basic by [@dependabot](https://github.com/dependabot) in [#96](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/96)
-* Bump minimatch from 3.0.4 to 3.1.2 in /examples/basic by [@dependabot](https://github.com/dependabot) in [#91](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/91)
-* Bump decode-uri-component from 0.2.0 to 0.2.2 in /examples/basic by [@dependabot](https://github.com/dependabot) in [#90](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/90)
-* Bump decode-uri-component from 0.2.0 to 0.2.2 in /examples/api-proxy by [@dependabot](https://github.com/dependabot) in [#89](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/89)
-* Bump minimatch from 3.0.4 to 3.1.2 in /examples/api-proxy by [@dependabot](https://github.com/dependabot) in [#88](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/88)
-* Swallow errors arising from fetch in analytics by [@matthewelwell](https://github.com/matthewelwell) in [#95](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/95)
-* Release/2.5.0 by [@matthewelwell](https://github.com/matthewelwell) in [#84](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/84)
+- Bump json5 from 2.1.0 to 2.2.3 in /examples/caching by [@dependabot](https://github.com/dependabot) in [#100](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/100)
+- Bump json5 from 2.1.0 to 2.2.3 in /examples/local-evaluation by [@dependabot](https://github.com/dependabot) in [#99](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/99)
+- Bump json5 from 2.1.0 to 2.2.3 in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#98](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/98)
+- Bump json5 from 2.1.0 to 2.2.3 in /examples/api-proxy by [@dependabot](https://github.com/dependabot) in [#97](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/97)
+- Bump json5 from 2.1.0 to 2.2.3 in /examples/basic by [@dependabot](https://github.com/dependabot) in [#96](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/96)
+- Bump minimatch from 3.0.4 to 3.1.2 in /examples/basic by [@dependabot](https://github.com/dependabot) in [#91](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/91)
+- Bump decode-uri-component from 0.2.0 to 0.2.2 in /examples/basic by [@dependabot](https://github.com/dependabot) in [#90](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/90)
+- Bump decode-uri-component from 0.2.0 to 0.2.2 in /examples/api-proxy by [@dependabot](https://github.com/dependabot) in [#89](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/89)
+- Bump minimatch from 3.0.4 to 3.1.2 in /examples/api-proxy by [@dependabot](https://github.com/dependabot) in [#88](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/88)
+- Swallow errors arising from fetch in analytics by [@matthewelwell](https://github.com/matthewelwell) in [#95](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/95)
+- Release/2.5.0 by [@matthewelwell](https://github.com/matthewelwell) in [#84](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/84)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v2.4.1...v2.5.0
[Changes][v2.5.0]
-
+
# [Version 2.4.1 (v2.4.1)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v2.4.1) - 2023-01-05
## What's Changed
-* Fix issue with local evaluation of multivariate flags by [@matthewelwell](https://github.com/matthewelwell) in [#87](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/87)
-* Release 2.4.1 by [@matthewelwell](https://github.com/matthewelwell) in [#86](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/86)
+- Fix issue with local evaluation of multivariate flags by [@matthewelwell](https://github.com/matthewelwell) in [#87](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/87)
+- Release 2.4.1 by [@matthewelwell](https://github.com/matthewelwell) in [#86](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/86)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v2.4.0...v2.4.1
[Changes][v2.4.1]
-
+
# [Version 2.4.0 (v2.4.0)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v2.4.0) - 2022-11-01
## What's Changed
-* Bump glob-parent and @babel/cli in /examples/local-evaluation by [@dependabot](https://github.com/dependabot) in [#67](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/67)
-* Bump ajv from 6.10.2 to 6.12.6 in /examples/local-evaluation by [@dependabot](https://github.com/dependabot) in [#69](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/69)
-* Bump ansi-regex from 3.0.0 to 3.0.1 in /examples/local-evaluation by [@dependabot](https://github.com/dependabot) in [#68](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/68)
-* Bump glob-parent and @babel/cli in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#70](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/70)
-* Bump ajv from 6.10.2 to 6.12.6 in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#73](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/73)
-* Bump browserslist from 4.6.6 to 4.21.3 in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#72](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/72)
-* Bump ansi-regex from 3.0.0 to 3.0.1 in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#71](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/71)
-* Feature/403/modulo segment operators by [@EdsnLoor](https://github.com/EdsnLoor) in [#76](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/76)
-* Feature/1145/is set is not set segment operators by [@EdsnLoor](https://github.com/EdsnLoor) in [#75](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/75)
-* Bump glob-parent and @babel/cli in /examples/caching by [@dependabot](https://github.com/dependabot) in [#74](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/74)
-* Release 2.4.0 by [@matthewelwell](https://github.com/matthewelwell) in [#77](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/77)
+
+- Bump glob-parent and @babel/cli in /examples/local-evaluation by [@dependabot](https://github.com/dependabot) in [#67](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/67)
+- Bump ajv from 6.10.2 to 6.12.6 in /examples/local-evaluation by [@dependabot](https://github.com/dependabot) in [#69](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/69)
+- Bump ansi-regex from 3.0.0 to 3.0.1 in /examples/local-evaluation by [@dependabot](https://github.com/dependabot) in [#68](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/68)
+- Bump glob-parent and @babel/cli in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#70](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/70)
+- Bump ajv from 6.10.2 to 6.12.6 in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#73](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/73)
+- Bump browserslist from 4.6.6 to 4.21.3 in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#72](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/72)
+- Bump ansi-regex from 3.0.0 to 3.0.1 in /examples/custom-fetch-agent by [@dependabot](https://github.com/dependabot) in [#71](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/71)
+- Feature/403/modulo segment operators by [@EdsnLoor](https://github.com/EdsnLoor) in [#76](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/76)
+- Feature/1145/is set is not set segment operators by [@EdsnLoor](https://github.com/EdsnLoor) in [#75](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/75)
+- Bump glob-parent and @babel/cli in /examples/caching by [@dependabot](https://github.com/dependabot) in [#74](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/74)
+- Release 2.4.0 by [@matthewelwell](https://github.com/matthewelwell) in [#77](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/77)
## New Contributors
-* [@EdsnLoor](https://github.com/EdsnLoor) made their first contribution in [#76](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/76)
+
+- [@EdsnLoor](https://github.com/EdsnLoor) made their first contribution in [#76](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/76)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v2.3.0...v2.4.0
[Changes][v2.4.0]
-
+
# [2.3.0 - Allow custom fetch agents, improve examples and types (v2.3.0)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v2.3.0) - 2022-08-31
Allows people to supply a custom agent when initialising Flagsmith, allowing for
-- Network-related config such as keep-alive / socket timeouts
-- Proxies such as https://www.npmjs.com/package/https-proxy-agent
+- Network-related config such as keep-alive / socket timeouts
+- Proxies such as https://www.npmjs.com/package/https-proxy-agent
Exports Flagsmith constructor arguments as a type.
@@ -398,12 +412,10 @@ Adds a few examples concentrating on common use cases.
Closes [#29](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/29), [#20](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/20)
-
-
[Changes][v2.3.0]
-
+
# [2.1.0 ES import support](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/2.1.0) - 2022-07-22
Closes [#42](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/42) - you can now import Flagsmith as such
@@ -414,71 +426,72 @@ import Flagsmith, {...types} from 'flagsmith-nodejs'
[Changes][2.1.0]
-
+
# [Version 2.0.4 (v2.0.4)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v2.0.4) - 2022-07-13
## What's Changed
-* Use featureName for analytics by [@matthewelwell](https://github.com/matthewelwell) in [#48](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/48)
-* Bump minimist from 1.2.5 to 1.2.6 by [@dependabot](https://github.com/dependabot) in [#38](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/38)
-* Bump node-fetch from 2.1.2 to 2.6.7 by [@dependabot](https://github.com/dependabot) in [#39](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/39)
-* Bump handlebars from 4.7.3 to 4.7.7 in /example by [@dependabot](https://github.com/dependabot) in [#17](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/17)
-* Release 2.0.4 by [@matthewelwell](https://github.com/matthewelwell) in [#47](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/47)
+- Use featureName for analytics by [@matthewelwell](https://github.com/matthewelwell) in [#48](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/48)
+- Bump minimist from 1.2.5 to 1.2.6 by [@dependabot](https://github.com/dependabot) in [#38](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/38)
+- Bump node-fetch from 2.1.2 to 2.6.7 by [@dependabot](https://github.com/dependabot) in [#39](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/39)
+- Bump handlebars from 4.7.3 to 4.7.7 in /example by [@dependabot](https://github.com/dependabot) in [#17](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/17)
+- Release 2.0.4 by [@matthewelwell](https://github.com/matthewelwell) in [#47](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/47)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/2.0.3...v2.0.4
[Changes][v2.0.4]
-
+
# [2.0.3](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/2.0.3) - 2022-07-11
Closes [#43](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/43)
[Changes][2.0.3]
-
+
# [Version 2.0.0 (v2.0.0)](https://github.com/Flagsmith/flagsmith-nodejs-client/releases/tag/v2.0.0) - 2022-06-07
## What's Changed
-* Removes console.log of response by [@muddylemon](https://github.com/muddylemon) in [#1](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/1)
-* Make bullet-train flags stateless, fix binding. by [@kyle-ssg](https://github.com/kyle-ssg) in [#2](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/2)
-* Adds getUserIdentity(), getTrait() and setTrait(). Promise rejection if identity not provided by [@lukefanning](https://github.com/lukefanning) in [#3](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/3)
-* Update client to use new api endpoints by [@matthewelwell](https://github.com/matthewelwell) in [#4](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/4)
-* Update index.js by [@obax](https://github.com/obax) in [#6](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/6)
-* Update config.js by [@obax](https://github.com/obax) in [#5](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/5)
-* Solving error in Function by [@palazari19](https://github.com/palazari19) in [#8](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/8)
-* Bump handlebars from 4.0.12 to 4.7.3 in /example by [@dependabot](https://github.com/dependabot) in [#9](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/9)
-* feat: renamed type file to vscode automatically detect bullet-train type by [@raryson](https://github.com/raryson) in [#11](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/11)
-* Rebrand by [@kyle-ssg](https://github.com/kyle-ssg) in [#14](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/14)
-* Preventing errors while using this SDK by [@eilgin](https://github.com/eilgin) in [#15](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/15)
-* Add a cache options to reduce latency by [@eilgin](https://github.com/eilgin) in [#16](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/16)
-* fallback to require('node-fetch').default by [@kyle-ssg](https://github.com/kyle-ssg) in [#21](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/21)
-* Fix setTrait Return Type by [@beeme1mr](https://github.com/beeme1mr) in [#26](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/26)
-* WIP: Node SDK v2 by [@dabeeeenster](https://github.com/dabeeeenster) in [#23](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/23)
-* Update default URL to point to Edge API by [@matthewelwell](https://github.com/matthewelwell) in [#36](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/36)
-* feat: add semver support for segment condition by [@yuriihorodnyi21](https://github.com/yuriihorodnyi21) in [#37](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/37)
-* Release 2.0.0 by [@matthewelwell](https://github.com/matthewelwell) in [#35](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/35)
+
+- Removes console.log of response by [@muddylemon](https://github.com/muddylemon) in [#1](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/1)
+- Make bullet-train flags stateless, fix binding. by [@kyle-ssg](https://github.com/kyle-ssg) in [#2](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/2)
+- Adds getUserIdentity(), getTrait() and setTrait(). Promise rejection if identity not provided by [@lukefanning](https://github.com/lukefanning) in [#3](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/3)
+- Update client to use new api endpoints by [@matthewelwell](https://github.com/matthewelwell) in [#4](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/4)
+- Update index.js by [@obax](https://github.com/obax) in [#6](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/6)
+- Update config.js by [@obax](https://github.com/obax) in [#5](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/5)
+- Solving error in Function by [@palazari19](https://github.com/palazari19) in [#8](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/8)
+- Bump handlebars from 4.0.12 to 4.7.3 in /example by [@dependabot](https://github.com/dependabot) in [#9](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/9)
+- feat: renamed type file to vscode automatically detect bullet-train type by [@raryson](https://github.com/raryson) in [#11](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/11)
+- Rebrand by [@kyle-ssg](https://github.com/kyle-ssg) in [#14](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/14)
+- Preventing errors while using this SDK by [@eilgin](https://github.com/eilgin) in [#15](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/15)
+- Add a cache options to reduce latency by [@eilgin](https://github.com/eilgin) in [#16](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/16)
+- fallback to require('node-fetch').default by [@kyle-ssg](https://github.com/kyle-ssg) in [#21](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/21)
+- Fix setTrait Return Type by [@beeme1mr](https://github.com/beeme1mr) in [#26](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/26)
+- WIP: Node SDK v2 by [@dabeeeenster](https://github.com/dabeeeenster) in [#23](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/23)
+- Update default URL to point to Edge API by [@matthewelwell](https://github.com/matthewelwell) in [#36](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/36)
+- feat: add semver support for segment condition by [@yuriihorodnyi21](https://github.com/yuriihorodnyi21) in [#37](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/37)
+- Release 2.0.0 by [@matthewelwell](https://github.com/matthewelwell) in [#35](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/35)
## New Contributors
-* [@muddylemon](https://github.com/muddylemon) made their first contribution in [#1](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/1)
-* [@lukefanning](https://github.com/lukefanning) made their first contribution in [#3](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/3)
-* [@obax](https://github.com/obax) made their first contribution in [#6](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/6)
-* [@palazari19](https://github.com/palazari19) made their first contribution in [#8](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/8)
-* [@dependabot](https://github.com/dependabot) made their first contribution in [#9](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/9)
-* [@raryson](https://github.com/raryson) made their first contribution in [#11](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/11)
-* [@eilgin](https://github.com/eilgin) made their first contribution in [#15](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/15)
-* [@beeme1mr](https://github.com/beeme1mr) made their first contribution in [#26](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/26)
-* [@dabeeeenster](https://github.com/dabeeeenster) made their first contribution in [#23](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/23)
-* [@yuriihorodnyi21](https://github.com/yuriihorodnyi21) made their first contribution in [#37](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/37)
+
+- [@muddylemon](https://github.com/muddylemon) made their first contribution in [#1](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/1)
+- [@lukefanning](https://github.com/lukefanning) made their first contribution in [#3](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/3)
+- [@obax](https://github.com/obax) made their first contribution in [#6](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/6)
+- [@palazari19](https://github.com/palazari19) made their first contribution in [#8](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/8)
+- [@dependabot](https://github.com/dependabot) made their first contribution in [#9](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/9)
+- [@raryson](https://github.com/raryson) made their first contribution in [#11](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/11)
+- [@eilgin](https://github.com/eilgin) made their first contribution in [#15](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/15)
+- [@beeme1mr](https://github.com/beeme1mr) made their first contribution in [#26](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/26)
+- [@dabeeeenster](https://github.com/dabeeeenster) made their first contribution in [#23](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/23)
+- [@yuriihorodnyi21](https://github.com/yuriihorodnyi21) made their first contribution in [#37](https://github.com/Flagsmith/flagsmith-nodejs-client/pull/37)
**Full Changelog**: https://github.com/Flagsmith/flagsmith-nodejs-client/commits/v2.0.0
[Changes][v2.0.0]
-
[v6.1.0]: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v6.0.1...v6.1.0
[v6.0.1]: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v6.0.0...v6.0.1
[v6.0.0]: https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v5.1.1...v6.0.0
diff --git a/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts b/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts
new file mode 100644
index 0000000..d105c96
--- /dev/null
+++ b/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts
@@ -0,0 +1,233 @@
+/* eslint-disable */
+/**
+ * This file was automatically generated by json-schema-to-typescript.
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
+ * and run json-schema-to-typescript to regenerate this file.
+ */
+
+/**
+ * An environment's unique identifier.
+ */
+export type Key = string;
+/**
+ * An environment's human-readable name.
+ */
+export type Name = string;
+/**
+ * A unique identifier for an identity, used for segment and multivariate feature flag targeting, and displayed in the Flagsmith UI.
+ */
+export type Identifier = string;
+/**
+ * Key used when selecting a value for a multivariate feature, or for % split segmentation. Set to an internal identifier or a composite value based on the environment key and identifier, depending on Flagsmith implementation.
+ */
+export type Key1 = string;
+/**
+ * Key used for % split segmentation.
+ */
+export type Key2 = string;
+/**
+ * The name of the segment.
+ */
+export type Name1 = string;
+/**
+ * Segment rule type. Represents a logical quantifier for the conditions and sub-rules.
+ */
+export type Type = 'ALL' | 'ANY' | 'NONE';
+export type SegmentCondition = SegmentCondition1 | InSegmentCondition;
+/**
+ * A reference to the identity trait or value in the evaluation context.
+ */
+export type Property = string;
+/**
+ * The operator to use for evaluating the condition.
+ */
+export type Operator =
+ | 'EQUAL'
+ | 'GREATER_THAN'
+ | 'LESS_THAN'
+ | 'LESS_THAN_INCLUSIVE'
+ | 'CONTAINS'
+ | 'GREATER_THAN_INCLUSIVE'
+ | 'NOT_CONTAINS'
+ | 'NOT_EQUAL'
+ | 'REGEX'
+ | 'PERCENTAGE_SPLIT'
+ | 'MODULO'
+ | 'IS_SET'
+ | 'IS_NOT_SET'
+ | 'IN';
+/**
+ * The value to compare against the trait or context value.
+ */
+export type Value = string;
+/**
+ * A reference to the identity trait or value in the evaluation context.
+ */
+export type Property1 = string;
+/**
+ * The operator to use for evaluating the condition.
+ */
+export type Operator1 = 'IN';
+/**
+ * The values to compare against the trait or context value.
+ */
+export type Value1 = string[];
+/**
+ * Conditions that must be met for the rule to apply.
+ */
+export type Conditions = SegmentCondition[];
+/**
+ * Sub-rules nested within the segment rule.
+ */
+export type SubRules = SegmentRule[];
+/**
+ * Rules that define the segment.
+ */
+export type Rules = SegmentRule[];
+/**
+ * Key used when selecting a value for a multivariate feature. Set to an internal identifier or a UUID, depending on Flagsmith implementation.
+ */
+export type Key3 = string;
+/**
+ * Unique feature identifier.
+ */
+export type FeatureKey = string;
+/**
+ * Feature name.
+ */
+export type Name2 = string;
+/**
+ * Indicates whether the feature is enabled in the environment.
+ */
+export type Enabled = boolean;
+/**
+ * A default environment value for the feature. If the feature is multivariate, this will be the control value.
+ */
+export type Value2 = string | number | boolean | null;
+/**
+ * The value of the feature.
+ */
+export type Value3 = string | number | boolean | null;
+/**
+ * The weight of the feature value variant, as a percentage number (i.e. 100.0).
+ */
+export type Weight = number;
+/**
+ * An array of environment default values associated with the feature. Empty for standard features, or contains multiple values for multivariate features.
+ */
+export type Variants = FeatureValue[];
+/**
+ * Priority of the feature context. Lower values indicate a higher priority when multiple contexts apply to the same feature.
+ */
+export type Priority = number;
+/**
+ * Feature overrides for the segment.
+ */
+export type Overrides = FeatureContext[];
+
+/**
+ * A context object containing the necessary information to evaluate Flagsmith feature flags.
+ */
+export interface EvaluationContext {
+ environment: EnvironmentContext;
+ /**
+ * Identity context used for identity-based evaluation.
+ */
+ identity?: IdentityContext | null;
+ segments?: Segments;
+ features?: Features;
+ [k: string]: unknown;
+}
+/**
+ * Environment context required for evaluation.
+ */
+export interface EnvironmentContext {
+ key: Key;
+ name: Name;
+ [k: string]: unknown;
+}
+/**
+ * Represents an identity context for feature flag evaluation.
+ */
+export interface IdentityContext {
+ identifier: Identifier;
+ key: Key1;
+ traits?: Traits;
+ [k: string]: unknown;
+}
+/**
+ * A map of traits associated with the identity, where the key is the trait name and the value is the trait value.
+ */
+export interface Traits {
+ [k: string]: string | number | boolean | null;
+}
+/**
+ * Segments applicable to the evaluation context.
+ */
+export interface Segments {
+ [k: string]: SegmentContext;
+}
+/**
+ * Represents a segment context for feature flag evaluation.
+ */
+export interface SegmentContext {
+ key: Key2;
+ name: Name1;
+ rules: Rules;
+ overrides?: Overrides;
+ [k: string]: unknown;
+}
+/**
+ * Represents a rule within a segment for feature flag evaluation.
+ */
+export interface SegmentRule {
+ type: Type;
+ conditions?: Conditions;
+ rules?: SubRules;
+ [k: string]: unknown;
+}
+/**
+ * Represents a condition within a segment rule for feature flag evaluation.
+ */
+export interface SegmentCondition1 {
+ property: Property;
+ operator: Operator;
+ value: Value;
+ [k: string]: unknown;
+}
+/**
+ * Represents an IN condition within a segment rule for feature flag evaluation.
+ */
+export interface InSegmentCondition {
+ property: Property1;
+ operator: Operator1;
+ value: Value1;
+ [k: string]: unknown;
+}
+/**
+ * Represents a feature context for feature flag evaluation.
+ */
+export interface FeatureContext {
+ key: Key3;
+ feature_key: FeatureKey;
+ name: Name2;
+ enabled: Enabled;
+ value: Value2;
+ variants?: Variants;
+ priority?: Priority;
+ [k: string]: unknown;
+}
+/**
+ * Represents a multivariate value for a feature flag.
+ */
+export interface FeatureValue {
+ value: Value3;
+ weight: Weight;
+ [k: string]: unknown;
+}
+/**
+ * Features to be evaluated in the context.
+ */
+export interface Features {
+ [k: string]: FeatureContext;
+}
diff --git a/flagsmith-engine/evaluation/evaluationContext/mappers.ts b/flagsmith-engine/evaluation/evaluationContext/mappers.ts
new file mode 100644
index 0000000..a77b4de
--- /dev/null
+++ b/flagsmith-engine/evaluation/evaluationContext/mappers.ts
@@ -0,0 +1,178 @@
+import {
+ Features,
+ Segments,
+ Traits,
+ EvaluationContext,
+ EnvironmentContext,
+ IdentityContext
+} from '../models.js';
+import { EnvironmentModel } from '../../environments/models.js';
+import { IdentityModel } from '../../identities/models.js';
+import { TraitModel } from '../../identities/traits/models.js';
+import { IDENTITY_OVERRIDE_SEGMENT_NAME } from '../../segments/constants.js';
+import { createHash } from 'node:crypto';
+
+export function getEvaluationContext(
+ environment: EnvironmentModel,
+ identity?: IdentityModel,
+ overrideTraits?: TraitModel[]
+): EvaluationContext {
+ const environmentContext = mapEnvironmentModelToEvaluationContext(environment);
+ const identityContext = identity
+ ? mapIdentityModelToIdentityContext(identity, overrideTraits)
+ : undefined;
+
+ const context = {
+ ...environmentContext,
+ ...(identityContext && { identity: identityContext })
+ };
+
+ return context;
+}
+
+function mapEnvironmentModelToEvaluationContext(environment: EnvironmentModel): EvaluationContext {
+ const environmentContext: EnvironmentContext = {
+ key: environment.apiKey,
+ name: environment.project.name
+ };
+
+ const features: Features = {};
+ for (const fs of environment.featureStates) {
+ const variants =
+ fs.multivariateFeatureStateValues.length > 0
+ ? [...fs.multivariateFeatureStateValues]
+ .sort((a, b) => (a.id ?? 0) - (b.id ?? 0))
+ .map(mv => ({
+ value: mv.multivariateFeatureOption.value,
+ weight: mv.percentageAllocation
+ }))
+ : undefined;
+ features[fs.feature.name] = {
+ key: fs.djangoID?.toString() || fs.featurestateUUID,
+ feature_key: fs.feature.id.toString(),
+ name: fs.feature.name,
+ enabled: fs.enabled,
+ value: fs.getValue(),
+ variants,
+ priority: fs.featureSegment?.priority
+ };
+ }
+
+ const segmentOverrides: Segments = {};
+ for (const segment of environment.project.segments) {
+ segmentOverrides[segment.id.toString()] = {
+ key: segment.id.toString(),
+ name: segment.name,
+ rules: segment.rules.map(rule => mapSegmentRuleModelToRule(rule)),
+ overrides:
+ segment.featureStates.length > 0
+ ? segment.featureStates.map(fs => ({
+ key: fs.djangoID?.toString() || fs.featurestateUUID,
+ feature_key: fs.feature.id.toString(),
+ name: fs.feature.name,
+ enabled: fs.enabled,
+ value: fs.getValue(),
+ priority: fs.featureSegment?.priority
+ }))
+ : []
+ };
+ }
+
+ let identityOverrideSegments: Segments = {};
+ if (environment.identityOverrides && environment.identityOverrides.length > 0) {
+ identityOverrideSegments = mapIdentityOverridesToSegments(environment.identityOverrides);
+ }
+
+ return {
+ environment: environmentContext,
+ features,
+ segments: {
+ ...segmentOverrides,
+ ...identityOverrideSegments
+ }
+ };
+}
+
+function mapIdentityModelToIdentityContext(
+ identity: IdentityModel,
+ overrideTraits?: TraitModel[]
+): IdentityContext {
+ const traits = overrideTraits || identity.identityTraits;
+ const traitsContext: Traits = {};
+
+ for (const trait of traits) {
+ traitsContext[trait.traitKey] = trait.traitValue;
+ }
+
+ return {
+ identifier: identity.identifier,
+ key: identity.djangoID?.toString() || identity.compositeKey,
+ traits: traitsContext
+ };
+}
+
+function mapSegmentRuleModelToRule(rule: any): any {
+ return {
+ type: rule.type,
+ conditions: rule.conditions.map((condition: any) => ({
+ property: condition.property,
+ operator: condition.operator,
+ value: condition.value
+ })),
+ rules: rule.rules.map((subRule: any) => mapSegmentRuleModelToRule(subRule))
+ };
+}
+
+function mapIdentityOverridesToSegments(identityOverrides: IdentityModel[]): Segments {
+ const segments: Segments = {};
+ const featuresToIdentifiers = new Map();
+
+ for (const identity of identityOverrides) {
+ if (!identity.identityFeatures || identity.identityFeatures.length === 0) {
+ continue;
+ }
+
+ const sortedFeatures = [...identity.identityFeatures].sort((a, b) =>
+ a.feature.name.localeCompare(b.feature.name)
+ );
+ const overridesKey = sortedFeatures.map(fs => ({
+ feature_key: fs.feature.id.toString(),
+ name: fs.feature.name,
+ enabled: fs.enabled,
+ value: fs.getValue(),
+ priority: -Infinity
+ }));
+
+ const overridesHash = createHash('sha1').update(JSON.stringify(overridesKey)).digest('hex');
+
+ if (!featuresToIdentifiers.has(overridesHash)) {
+ featuresToIdentifiers.set(overridesHash, { identifiers: [], overrides: overridesKey });
+ }
+
+ featuresToIdentifiers.get(overridesHash)!.identifiers.push(identity.identifier);
+ }
+ for (const [overrideHash, { identifiers, overrides }] of featuresToIdentifiers.entries()) {
+ const segmentKey = `identity_override_${overrideHash}`;
+
+ segments[segmentKey] = {
+ key: segmentKey,
+ name: IDENTITY_OVERRIDE_SEGMENT_NAME,
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'IN',
+ // TODO: Modify once new IN operator is implemented
+ value: identifiers.join(',')
+ }
+ ]
+ }
+ ],
+ overrides: overrides
+ };
+ }
+
+ return segments;
+}
diff --git a/flagsmith-engine/evaluation/evaluationContext/types.ts b/flagsmith-engine/evaluation/evaluationContext/types.ts
new file mode 100644
index 0000000..e671005
--- /dev/null
+++ b/flagsmith-engine/evaluation/evaluationContext/types.ts
@@ -0,0 +1,233 @@
+/* eslint-disable */
+/**
+ * This file was automatically generated by json-schema-to-typescript.
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
+ * and run json-schema-to-typescript to regenerate this file.
+ */
+
+/**
+ * An environment's unique identifier.
+ */
+export type Key = string;
+/**
+ * An environment's human-readable name.
+ */
+export type Name = string;
+/**
+ * A unique identifier for an identity, used for segment and multivariate feature flag targeting, and displayed in the Flagsmith UI.
+ */
+export type Identifier = string;
+/**
+ * Key used when selecting a value for a multivariate feature, or for % split segmentation. Set to an internal identifier or a composite value based on the environment key and identifier, depending on Flagsmith implementation.
+ */
+export type Key1 = string;
+/**
+ * Key used for % split segmentation.
+ */
+export type Key2 = string;
+/**
+ * The name of the segment.
+ */
+export type Name1 = string;
+/**
+ * Segment rule type. Represents a logical quantifier for the conditions and sub-rules.
+ */
+export type Type = 'ALL' | 'ANY' | 'NONE';
+export type SegmentCondition = SegmentCondition1 | InSegmentCondition;
+/**
+ * A reference to the identity trait or value in the evaluation context.
+ */
+export type Property = string;
+/**
+ * The operator to use for evaluating the condition.
+ */
+export type Operator =
+ | 'EQUAL'
+ | 'GREATER_THAN'
+ | 'LESS_THAN'
+ | 'LESS_THAN_INCLUSIVE'
+ | 'CONTAINS'
+ | 'GREATER_THAN_INCLUSIVE'
+ | 'NOT_CONTAINS'
+ | 'NOT_EQUAL'
+ | 'REGEX'
+ | 'PERCENTAGE_SPLIT'
+ | 'MODULO'
+ | 'IS_SET'
+ | 'IS_NOT_SET'
+ | 'IN';
+/**
+ * The value to compare against the trait or context value.
+ */
+export type Value = string;
+/**
+ * A reference to the identity trait or value in the evaluation context.
+ */
+export type Property1 = string;
+/**
+ * The operator to use for evaluating the condition.
+ */
+export type Operator1 = 'IN';
+/**
+ * The values to compare against the trait or context value.
+ */
+export type Value1 = string[];
+/**
+ * Conditions that must be met for the rule to apply.
+ */
+export type Conditions = SegmentCondition[];
+/**
+ * Sub-rules nested within the segment rule.
+ */
+export type SubRules = SegmentRule[];
+/**
+ * Rules that define the segment.
+ */
+export type Rules = SegmentRule[];
+/**
+ * Key used when selecting a value for a multivariate feature. Set to an internal identifier or a UUID, depending on Flagsmith implementation.
+ */
+export type Key3 = string;
+/**
+ * Unique feature identifier.
+ */
+export type FeatureKey = string;
+/**
+ * Feature name.
+ */
+export type Name2 = string;
+/**
+ * Indicates whether the feature is enabled in the environment.
+ */
+export type Enabled = boolean;
+/**
+ * A default environment value for the feature. If the feature is multivariate, this will be the control value.
+ */
+export type Value2 = string;
+/**
+ * The value of the feature.
+ */
+export type Value3 = string;
+/**
+ * The weight of the feature value variant, as a percentage number (i.e. 100.0).
+ */
+export type Weight = number;
+/**
+ * An array of environment default values associated with the feature. Contains a single value for standard features, or multiple values for multivariate features.
+ */
+export type Variants = FeatureValue[];
+/**
+ * Priority of the feature context. Lower values indicate a higher priority when multiple contexts apply to the same feature.
+ */
+export type Priority = number;
+/**
+ * Feature overrides for the segment.
+ */
+export type Overrides = FeatureContext[];
+
+/**
+ * A context object containing the necessary information to evaluate Flagsmith feature flags.
+ */
+export interface EvaluationContext {
+ environment: EnvironmentContext;
+ /**
+ * Identity context used for identity-based evaluation.
+ */
+ identity?: IdentityContext | null;
+ segments?: Segments;
+ features?: Features;
+ [k: string]: unknown;
+}
+/**
+ * Environment context required for evaluation.
+ */
+export interface EnvironmentContext {
+ key: Key;
+ name: Name;
+ [k: string]: unknown;
+}
+/**
+ * Represents an identity context for feature flag evaluation.
+ */
+export interface IdentityContext {
+ identifier: Identifier;
+ key: Key1;
+ traits?: Traits;
+ [k: string]: unknown;
+}
+/**
+ * A map of traits associated with the identity, where the key is the trait name and the value is the trait value.
+ */
+export interface Traits {
+ [k: string]: string | number | boolean | null;
+}
+/**
+ * Segments applicable to the evaluation context.
+ */
+export interface Segments {
+ [k: string]: SegmentContext;
+}
+/**
+ * Represents a segment context for feature flag evaluation.
+ */
+export interface SegmentContext {
+ key: Key2;
+ name: Name1;
+ rules: Rules;
+ overrides?: Overrides;
+ [k: string]: unknown;
+}
+/**
+ * Represents a rule within a segment for feature flag evaluation.
+ */
+export interface SegmentRule {
+ type: Type;
+ conditions?: Conditions;
+ rules?: SubRules;
+ [k: string]: unknown;
+}
+/**
+ * Represents a condition within a segment rule for feature flag evaluation.
+ */
+export interface SegmentCondition1 {
+ property: Property;
+ operator: Operator;
+ value: Value;
+ [k: string]: unknown;
+}
+/**
+ * Represents an IN condition within a segment rule for feature flag evaluation.
+ */
+export interface InSegmentCondition {
+ property: Property1;
+ operator: Operator1;
+ value: Value1;
+ [k: string]: unknown;
+}
+/**
+ * Represents a feature context for feature flag evaluation.
+ */
+export interface FeatureContext {
+ key: Key3;
+ feature_key: FeatureKey;
+ name: Name2;
+ enabled: Enabled;
+ value: Value2;
+ variants?: Variants;
+ priority?: Priority;
+ [k: string]: unknown;
+}
+/**
+ * Represents a multivariate value for a feature flag.
+ */
+export interface FeatureValue {
+ value: Value3;
+ weight: Weight;
+ [k: string]: unknown;
+}
+/**
+ * Features to be evaluated in the context.
+ */
+export interface Features {
+ [k: string]: FeatureContext;
+}
diff --git a/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts b/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts
new file mode 100644
index 0000000..6644656
--- /dev/null
+++ b/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts
@@ -0,0 +1,67 @@
+/* eslint-disable */
+/**
+ * This file was automatically generated by json-schema-to-typescript.
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
+ * and run json-schema-to-typescript to regenerate this file.
+ */
+
+/**
+ * Unique feature identifier.
+ */
+export type FeatureKey = string;
+/**
+ * Feature name.
+ */
+export type Name = string;
+/**
+ * Indicates if the feature flag is enabled.
+ */
+export type Enabled = boolean;
+/**
+ * Feature flag value.
+ */
+export type Value = string | number | boolean | null;
+/**
+ * Reason for the feature flag evaluation.
+ */
+export type Reason = string;
+/**
+ * Unique segment identifier.
+ */
+export type Key = string;
+/**
+ * Segment name.
+ */
+export type Name1 = string;
+/**
+ * List of segments which the provided context belongs to.
+ */
+export type Segments = SegmentResult[];
+
+/**
+ * Evaluation result object containing the used context, flag evaluation results, and segments used in the evaluation.
+ */
+export interface EvaluationResult {
+ flags: Flags;
+ segments: Segments;
+ [k: string]: unknown;
+}
+/**
+ * Feature flags evaluated for the context, mapped by feature names.
+ */
+export interface Flags {
+ [k: string]: FlagResult;
+}
+export interface FlagResult {
+ feature_key: FeatureKey;
+ name: Name;
+ enabled: Enabled;
+ value: Value;
+ reason: Reason;
+ [k: string]: unknown;
+}
+export interface SegmentResult {
+ key: Key;
+ name: Name1;
+ [k: string]: unknown;
+}
diff --git a/flagsmith-engine/evaluation/models.ts b/flagsmith-engine/evaluation/models.ts
new file mode 100644
index 0000000..ba9c511
--- /dev/null
+++ b/flagsmith-engine/evaluation/models.ts
@@ -0,0 +1,72 @@
+// This file is the entry point for the evaluation module types
+// All types from evaluations should be at least imported here and re-exported
+// Do not use types directly from generated files
+
+import type {
+ EnvironmentContext,
+ IdentityContext,
+ SegmentContext,
+ SegmentRule,
+ SegmentCondition,
+ InSegmentCondition,
+ FeatureContext,
+ FeatureValue as ContextFeatureValue,
+ Traits,
+ Features,
+ Segments,
+ EvaluationContext
+} from './evaluationContext/evaluationContext.types.js';
+
+import type {
+ EvaluationResult as EvaluationContextResult,
+ FlagResult as EvaluationContextResultFlagResult
+} from './evaluationResult/evaluationResult.types.js';
+
+export type EnvironmentKey = EnvironmentContext['key'];
+export type EnvironmentName = EnvironmentContext['name'];
+
+export type IdentityIdentifier = IdentityContext['identifier'];
+export type IdentityKey = IdentityContext['key'];
+
+export type SegmentKey = SegmentContext['key'];
+export type SegmentName = SegmentContext['name'];
+export type SegmentRuleType = SegmentRule['type'];
+export type ConditionOperator = SegmentCondition['operator'] | InSegmentCondition['operator'];
+export type ConditionProperty = SegmentCondition['property'] | InSegmentCondition['property'];
+export type ConditionValue = SegmentCondition['value'] | InSegmentCondition['value'];
+
+export type FeatureKey = FeatureContext['feature_key'];
+export type FeatureName = FeatureContext['name'];
+export type FeatureEnabled = FeatureContext['enabled'];
+export type FeatureValue = FeatureContext['value'];
+export type FeaturePriority = FeatureContext['priority'];
+export type FeatureVariants = FeatureContext['variants'];
+
+export type VariantValue = ContextFeatureValue['value'];
+export type VariantWeight = ContextFeatureValue['weight'];
+
+export type TraitMap = Traits;
+export type FeatureMap = Features;
+export type SegmentMap = Segments;
+
+export type SegmentConditionOperator = SegmentCondition['operator'];
+
+export type EvaluationReason = EvaluationContextResultFlagResult['reason'];
+
+export type EvaluationResultSegments = EvaluationContextResult['segments'];
+export type EvaluationResultFlags = {
+ feature_key: FeatureKey;
+ name: FeatureName;
+ enabled: FeatureEnabled;
+ value: FeatureValue;
+ reason: EvaluationReason;
+}[];
+
+export type EvaluationResult = {
+ context: EvaluationContext;
+ flags: EvaluationResultFlags;
+ segments: EvaluationResultSegments;
+};
+
+export type { FlagResult } from './evaluationResult/evaluationResult.types.js';
+export type * from './evaluationContext/evaluationContext.types.js';
diff --git a/flagsmith-engine/features/models.ts b/flagsmith-engine/features/models.ts
index 686fbed..1549e5d 100644
--- a/flagsmith-engine/features/models.ts
+++ b/flagsmith-engine/features/models.ts
@@ -1,5 +1,5 @@
import { randomUUID as uuidv4 } from 'node:crypto';
-import { getHashedPercentateForObjIds } from '../utils/hashing/index.js';
+import { getHashedPercentageForObjIds } from '../utils/hashing/index.js';
export class FeatureModel {
id: number;
@@ -103,6 +103,7 @@ export class FeatureStateModel {
const sortedF = this.multivariateFeatureStateValues.sort((a, b) => {
return a.id - b.id;
});
+
for (const myValue of sortedF) {
switch (myValue.percentageAllocation) {
case 0:
@@ -111,7 +112,7 @@ export class FeatureStateModel {
return myValue.multivariateFeatureOption.value;
default:
if (percentageValue === undefined) {
- percentageValue = getHashedPercentateForObjIds([
+ percentageValue = getHashedPercentageForObjIds([
this.djangoID || this.featurestateUUID,
identityID
]);
diff --git a/flagsmith-engine/features/types.ts b/flagsmith-engine/features/types.ts
new file mode 100644
index 0000000..8f7fad1
--- /dev/null
+++ b/flagsmith-engine/features/types.ts
@@ -0,0 +1,4 @@
+export enum TARGETING_REASONS {
+ DEFAULT = 'DEFAULT',
+ TARGETING_MATCH = 'TARGETING_MATCH'
+}
diff --git a/flagsmith-engine/features/util.ts b/flagsmith-engine/features/util.ts
index 0a19589..8136f0c 100644
--- a/flagsmith-engine/features/util.ts
+++ b/flagsmith-engine/features/util.ts
@@ -6,6 +6,9 @@ import {
MultivariateFeatureStateValueModel
} from './models.js';
+import { FeatureContext } from '../evaluation/models.js';
+import { getHashedPercentageForObjIds as getHashedPercentageForObjIds } from '../utils/hashing/index.js';
+
export function buildFeatureModel(featuresModelJSON: any): FeatureModel {
return new FeatureModel(featuresModelJSON.id, featuresModelJSON.name, featuresModelJSON.type);
}
diff --git a/flagsmith-engine/index.ts b/flagsmith-engine/index.ts
index fb641ee..98a2434 100644
--- a/flagsmith-engine/index.ts
+++ b/flagsmith-engine/index.ts
@@ -1,102 +1,181 @@
-import { EnvironmentModel } from './environments/models.js';
-import { FeatureStateModel } from './features/models.js';
-import { IdentityModel } from './identities/models.js';
-import { TraitModel } from './identities/traits/models.js';
+import { EvaluationContext, FeatureContext } from './evaluation/models.js';
import { getIdentitySegments } from './segments/evaluators.js';
-import { SegmentModel } from './segments/models.js';
-import { FeatureStateNotFound } from './utils/errors.js';
-
+import { EvaluationResult, EvaluationResultFlags } from './evaluation/models.js';
+import { TARGETING_REASONS } from './features/types.js';
+import { getHashedPercentageForObjIds } from './utils/hashing/index.js';
export { EnvironmentModel } from './environments/models.js';
-export { FeatureModel, FeatureStateModel } from './features/models.js';
export { IdentityModel } from './identities/models.js';
export { TraitModel } from './identities/traits/models.js';
export { SegmentModel } from './segments/models.js';
-export { OrganisationModel } from './organisations/models.js';
-
-function getIdentityFeatureStatesDict(
- environment: EnvironmentModel,
- identity: IdentityModel,
- overrideTraits?: TraitModel[]
-) {
- // Get feature states from the environment
- const featureStates: { [key: number]: FeatureStateModel } = {};
- for (const fs of environment.featureStates) {
- featureStates[fs.feature.id] = fs;
+
+type SegmentOverride = {
+ feature: FeatureContext;
+ segmentName: string;
+};
+
+export type SegmentOverrides = Record;
+
+/**
+ * Evaluates flags and segments for the given context.
+ *
+ * This is the main entry point for the evaluation engine. It processes segments,
+ * applies feature overrides based on segment priority, and returns the final flag states with
+ * evaluation reasons.
+ *
+ * @param context - EvaluationContext containing environment, identity, and segment data
+ * @returns EvaluationResult with flags, segments, and original context
+ */
+export function getEvaluationResult(context: EvaluationContext): EvaluationResult {
+ const { segments, segmentOverrides } = evaluateSegments(context);
+ const flags = evaluateFeatures(context, segmentOverrides);
+
+ return { context, flags, segments };
+}
+
+/**
+ * Evaluates which segments the identity belongs to and collects feature overrides.
+ *
+ * @param context - EvaluationContext containing identity and segment definitions
+ * @returns Object containing segments the identity belongs to and any feature overrides
+ */
+export function evaluateSegments(context: EvaluationContext): {
+ segments: EvaluationResult['segments'];
+ segmentOverrides: Record;
+} {
+ if (!context.identity || !context.segments) {
+ return { segments: [], segmentOverrides: {} };
}
+ const identitySegments = getIdentitySegments(context);
- // Override with any feature states defined by matching segments
- const identitySegments: SegmentModel[] = getIdentitySegments(
- environment,
- identity,
- overrideTraits
- );
- for (const matchingSegment of identitySegments) {
- for (const featureState of matchingSegment.featureStates) {
- if (featureStates[featureState.feature.id]) {
- if (featureStates[featureState.feature.id].isHigherSegmentPriority(featureState)) {
- continue;
- }
+ const segments = identitySegments.map(segment => ({
+ key: segment.key,
+ name: segment.name
+ }));
+ const segmentOverrides = processSegmentOverrides(identitySegments);
+
+ return { segments, segmentOverrides };
+}
+
+/**
+ * Processes feature overrides from segments, applying priority rules.
+ *
+ * When multiple segments override the same feature, the segment with
+ * higher priority (lower numeric value) takes precedence.
+ *
+ * @param identitySegments - Segments that the identity belongs to
+ * @returns Map of feature keys to their highest-priority segment overrides
+ */
+export function processSegmentOverrides(identitySegments: any[]): Record {
+ const segmentOverrides: Record = {};
+
+ for (const segment of identitySegments) {
+ if (!segment.overrides) continue;
+
+ const overridesList = Array.isArray(segment.overrides) ? segment.overrides : [];
+
+ for (const override of overridesList) {
+ if (shouldApplyOverride(override, segmentOverrides)) {
+ segmentOverrides[override.feature_key] = {
+ feature: override,
+ segmentName: segment.name
+ };
}
- featureStates[featureState.feature.id] = featureState;
}
}
- // Override with any feature states defined directly the identity
- for (const fs of identity.identityFeatures) {
- if (featureStates[fs.feature.id]) {
- featureStates[fs.feature.id] = fs;
- }
- }
- return featureStates;
+ return segmentOverrides;
}
-export function getIdentityFeatureState(
- environment: EnvironmentModel,
- identity: IdentityModel,
- featureName: string,
- overrideTraits?: TraitModel[]
-): FeatureStateModel {
- const featureStates = getIdentityFeatureStatesDict(environment, identity, overrideTraits);
+/**
+ * Evaluates all features in the context, applying segment overrides where applicable.
+ * For each feature:
+ * - Checks if a segment override exists
+ * - Uses override values if present, otherwise evaluates the base feature
+ * - Determines appropriate evaluation reason
+ * - Handles multivariate evaluation for features without overrides
+ *
+ * @param context - EvaluationContext containing features and identity
+ * @param segmentOverrides - Map of feature keys to their segment overrides
+ * @returns EvaluationResultFlags containing evaluated flag results
+ */
+export function evaluateFeatures(
+ context: EvaluationContext,
+ segmentOverrides: Record
+): EvaluationResultFlags {
+ const flags: EvaluationResultFlags = [];
- const matchingFeature = Object.values(featureStates).filter(
- f => f.feature.name === featureName
- );
+ for (const feature of Object.values(context.features || {})) {
+ const segmentOverride = segmentOverrides[feature.feature_key];
+ const finalFeature = segmentOverride ? segmentOverride.feature : feature;
+ const hasOverride = !!segmentOverride;
+ const reason = getTargetingMatchReason(segmentOverride);
- if (matchingFeature.length === 0) {
- throw new FeatureStateNotFound('Feature State Not Found');
+ flags.push({
+ feature_key: finalFeature.feature_key,
+ name: finalFeature.name,
+ enabled: finalFeature.enabled,
+ value: hasOverride
+ ? finalFeature.value
+ : evaluateFeatureValue(finalFeature, context.identity?.key),
+ reason
+ });
}
- return matchingFeature[0];
+ return flags;
}
-export function getIdentityFeatureStates(
- environment: EnvironmentModel,
- identity: IdentityModel,
- overrideTraits?: TraitModel[]
-): FeatureStateModel[] {
- const featureStates = Object.values(
- getIdentityFeatureStatesDict(environment, identity, overrideTraits)
- );
-
- if (environment.project.hideDisabledFlags) {
- return featureStates.filter(fs => !!fs.enabled);
+function evaluateFeatureValue(feature: FeatureContext, identityKey?: string): any {
+ if (!!feature.variants && feature.variants.length > 0 && !!identityKey) {
+ return getMultivariateFeatureValue(feature, identityKey);
}
- return featureStates;
+
+ return feature.value;
}
-export function getEnvironmentFeatureState(environment: EnvironmentModel, featureName: string) {
- const featuresStates = environment.featureStates.filter(f => f.feature.name === featureName);
+/**
+ * Evaluates a multivariate feature flag to determine which variant value to return for a given identity.
+ *
+ * Uses deterministic hashing to ensure the same identity always receives the same variant,
+ * while distributing variants according to their configured weight percentages.
+ *
+ * @param feature - The feature context containing variants and their weights
+ * @param identityKey - The identity key used for deterministic variant selection
+ * @returns The variant value if the identity falls within a variant's range, otherwise the default feature value
+ */
+function getMultivariateFeatureValue(feature: FeatureContext, identityKey?: string): any {
+ const percentageValue = getHashedPercentageForObjIds([feature.key, identityKey]);
- if (featuresStates.length === 0) {
- throw new FeatureStateNotFound('Feature State Not Found');
+ let startPercentage = 0;
+ for (const variant of feature?.variants || []) {
+ const limit = startPercentage + variant.weight;
+
+ if (startPercentage <= percentageValue && percentageValue < limit) {
+ return variant.value;
+ }
+ startPercentage = limit;
}
+ return feature.value;
+}
- return featuresStates[0];
+export function shouldApplyOverride(
+ override: any,
+ existingOverrides: Record
+): boolean {
+ const currentOverride = existingOverrides[override.feature_key];
+ return (
+ !currentOverride || isHigherPriority(override.priority, currentOverride.feature.priority)
+ );
}
-export function getEnvironmentFeatureStates(environment: EnvironmentModel): FeatureStateModel[] {
- if (environment.project.hideDisabledFlags) {
- return environment.featureStates.filter(fs => !!fs.enabled);
- }
- return environment.featureStates;
+export function isHigherPriority(
+ priorityA: number | undefined,
+ priorityB: number | undefined
+): boolean {
+ return (priorityA ?? Infinity) < (priorityB ?? Infinity);
}
+
+const getTargetingMatchReason = (segmentOverride: SegmentOverride) => {
+ return segmentOverride
+ ? `${TARGETING_REASONS.TARGETING_MATCH}; segment=${segmentOverride.segmentName}`
+ : TARGETING_REASONS.DEFAULT;
+};
diff --git a/flagsmith-engine/segments/constants.ts b/flagsmith-engine/segments/constants.ts
index d2a3e9b..fad1660 100644
--- a/flagsmith-engine/segments/constants.ts
+++ b/flagsmith-engine/segments/constants.ts
@@ -4,6 +4,7 @@ export const ANY_RULE = 'ANY';
export const NONE_RULE = 'NONE';
export const RULE_TYPES = [ALL_RULE, ANY_RULE, NONE_RULE];
+export const IDENTITY_OVERRIDE_SEGMENT_NAME = 'identity_overrides';
// Segment Condition Operators
export const EQUAL = 'EQUAL';
diff --git a/flagsmith-engine/segments/evaluators.ts b/flagsmith-engine/segments/evaluators.ts
index f5d0081..576b4d2 100644
--- a/flagsmith-engine/segments/evaluators.ts
+++ b/flagsmith-engine/segments/evaluators.ts
@@ -1,76 +1,163 @@
-import { EnvironmentModel } from '../environments/models.js';
-import { IdentityModel } from '../identities/models.js';
-import { TraitModel } from '../identities/traits/models.js';
-import { getHashedPercentateForObjIds } from '../utils/hashing/index.js';
-import { PERCENTAGE_SPLIT, IS_SET, IS_NOT_SET } from './constants.js';
-import { SegmentConditionModel, SegmentModel, SegmentRuleModel } from './models.js';
-
-export function getIdentitySegments(
- environment: EnvironmentModel,
- identity: IdentityModel,
- overrideTraits?: TraitModel[]
-): SegmentModel[] {
- return environment.project.segments.filter(segment =>
- evaluateIdentityInSegment(identity, segment, overrideTraits)
- );
+import * as jsonpath from 'jsonpath';
+import {
+ EvaluationContext,
+ InSegmentCondition,
+ SegmentCondition,
+ SegmentContext,
+ SegmentRule
+} from '../evaluation/models.js';
+import { getHashedPercentageForObjIds } from '../utils/hashing/index.js';
+import { SegmentConditionModel } from './models.js';
+import { IS_NOT_SET, IS_SET, PERCENTAGE_SPLIT } from './constants.js';
+
+/**
+ * Returns all segments that the identity belongs to based on segment rules evaluation.
+ *
+ * An identity belongs to a segment if it matches ALL of the segment's rules.
+ * If the context has no identity or segments, returns an empty array.
+ *
+ * @param context - Evaluation context containing identity and segment definitions
+ * @returns Array of segments that the identity matches
+ */
+export function getIdentitySegments(context: EvaluationContext): SegmentContext[] {
+ if (!context.identity || !context.segments) return [];
+
+ return Object.values(context.segments).filter(segment => {
+ if (segment.rules.length === 0) return false;
+ return segment.rules.every(rule => traitsMatchSegmentRule(rule, segment.key, context));
+ });
}
-export function evaluateIdentityInSegment(
- identity: IdentityModel,
- segment: SegmentModel,
- overrideTraits?: TraitModel[]
+/**
+ * Evaluates whether a segment condition matches the identity's traits or context values.
+ *
+ * Handles different types of conditions:
+ * - PERCENTAGE_SPLIT: Deterministic percentage-based bucketing using identity key
+ * - IS_SET/IS_NOT_SET: Checks for trait existence
+ * - Standard operators: EQUAL, NOT_EQUAL, etc. via SegmentConditionModel
+ * - JSONPath expressions: $.identity.identifier, $.environment.name, etc.
+ *
+ * @param condition - The condition to evaluate (property, operator, value)
+ * @param segmentKey - Key of the segment (used for percentage split hashing)
+ * @param context - Evaluation context containing identity, traits, and environment
+ * @returns true if the condition matches
+ */
+export function traitsMatchSegmentCondition(
+ condition: SegmentCondition | InSegmentCondition,
+ segmentKey: string,
+ context?: EvaluationContext
): boolean {
- return (
- segment.rules.length > 0 &&
- segment.rules.filter(rule =>
- traitsMatchSegmentRule(
- overrideTraits || identity.identityTraits,
- rule,
- segment.id,
- identity.djangoID || identity.compositeKey
- )
- ).length === segment.rules.length
- );
+ if (condition.operator === PERCENTAGE_SPLIT) {
+ const contextValueKey =
+ getContextValue(condition.property, context) || context?.identity?.key;
+ const hashedPercentage = getHashedPercentageForObjIds([segmentKey, contextValueKey]);
+ return hashedPercentage <= parseFloat(String(condition.value));
+ }
+ if (!condition.property) {
+ return false;
+ }
+
+ const traitValue = getTraitValue(condition.property, context);
+
+ if (condition.operator === IS_SET) {
+ return traitValue !== undefined && traitValue !== null;
+ }
+ if (condition.operator === IS_NOT_SET) {
+ return traitValue === undefined || traitValue === null;
+ }
+
+ if (traitValue !== undefined && traitValue !== null) {
+ const segmentCondition = new SegmentConditionModel(
+ condition.operator,
+ condition.value as string,
+ condition.property
+ );
+ return segmentCondition.matchesTraitValue(traitValue);
+ }
+
+ return false;
}
function traitsMatchSegmentRule(
- identityTraits: TraitModel[],
- rule: SegmentRuleModel,
- segmentId: number | string,
- identityId: number | string
+ rule: SegmentRule,
+ segmentKey: string,
+ context?: EvaluationContext
+): boolean {
+ const matchesConditions = evaluateConditions(rule, segmentKey, context);
+ const matchesSubRules = evaluateSubRules(rule, segmentKey, context);
+
+ return matchesConditions && matchesSubRules;
+}
+
+function evaluateConditions(
+ rule: SegmentRule,
+ segmentKey: string,
+ context?: EvaluationContext
): boolean {
- const matchesConditions =
- rule.conditions.length > 0
- ? rule.matchingFunction()(
- rule.conditions.map(condition =>
- traitsMatchSegmentCondition(identityTraits, condition, segmentId, identityId)
- )
- )
- : true;
- return (
- matchesConditions &&
- rule.rules.filter(rule =>
- traitsMatchSegmentRule(identityTraits, rule, segmentId, identityId)
- ).length === rule.rules.length
+ if (!rule.conditions || rule.conditions.length === 0) return true;
+
+ const conditionResults = rule.conditions.map((condition: SegmentCondition) =>
+ traitsMatchSegmentCondition(condition, segmentKey, context)
);
+
+ return evaluateRuleConditions(rule.type, conditionResults);
}
-export function traitsMatchSegmentCondition(
- identityTraits: TraitModel[],
- condition: SegmentConditionModel,
- segmentId: number | string,
- identityId: number | string
+function evaluateSubRules(
+ rule: SegmentRule,
+ segmentKey: string,
+ context?: EvaluationContext
): boolean {
- if (condition.operator == PERCENTAGE_SPLIT) {
- var hashedPercentage = getHashedPercentateForObjIds([segmentId, identityId]);
- return hashedPercentage <= parseFloat(String(condition.value));
+ if (!rule.rules || rule.rules.length === 0) return true;
+
+ return rule.rules.every((subRule: SegmentRule) =>
+ traitsMatchSegmentRule(subRule, segmentKey, context)
+ );
+}
+
+function evaluateRuleConditions(ruleType: string, conditionResults: boolean[]): boolean {
+ switch (ruleType) {
+ case 'ALL':
+ return conditionResults.length === 0 || conditionResults.every(result => result);
+ case 'ANY':
+ return conditionResults.length > 0 && conditionResults.some(result => result);
+ case 'NONE':
+ return conditionResults.length === 0 || conditionResults.every(result => !result);
+ default:
+ return false;
}
- const traits = identityTraits.filter(t => t.traitKey === condition.property_);
- const trait = traits.length > 0 ? traits[0] : undefined;
- if (condition.operator === IS_SET) {
- return !!trait;
- } else if (condition.operator === IS_NOT_SET) {
- return trait == undefined;
+}
+
+function getTraitValue(property: string, context?: EvaluationContext): any {
+ if (property.startsWith('$.')) {
+ return getContextValue(property, context);
+ }
+
+ const traits = context?.identity?.traits || {};
+ return traits[property];
+}
+
+/**
+ * Evaluates JSONPath expressions against the evaluation context.
+ *
+ * Supports accessing nested context values using JSONPath syntax.
+ * Commonly used paths:
+ * - $.identity.identifier - User's unique identifier
+ * - $.identity.key - User's internal key
+ * - $.environment.name - Environment name
+ * - $.environment.key - Environment key
+ *
+ * @param jsonPath - JSONPath expression starting with '$.'
+ * @param context - Evaluation context to query against
+ * @returns The resolved value, or undefined if path doesn't exist or is invalid
+ */
+export function getContextValue(jsonPath: string, context?: EvaluationContext): any {
+ if (!context || !jsonPath?.startsWith('$.')) return undefined;
+
+ try {
+ const results = jsonpath.query(context, jsonPath);
+ return results.length > 0 ? results[0] : undefined;
+ } catch (error) {
+ return undefined;
}
- return trait ? condition.matchesTraitValue(trait.traitValue) : false;
}
diff --git a/flagsmith-engine/segments/models.ts b/flagsmith-engine/segments/models.ts
index 67aca0d..8858ff9 100644
--- a/flagsmith-engine/segments/models.ts
+++ b/flagsmith-engine/segments/models.ts
@@ -1,6 +1,11 @@
import * as semver from 'semver';
-import { FeatureStateModel } from '../features/models.js';
+import {
+ FeatureModel,
+ FeatureStateModel,
+ MultivariateFeatureOptionModel,
+ MultivariateFeatureStateValueModel
+} from '../features/models.js';
import { getCastingFunction as getCastingFunction } from '../utils/index.js';
import {
ALL_RULE,
@@ -13,6 +18,12 @@ import {
CONDITION_OPERATORS
} from './constants.js';
import { isSemver } from './util.js';
+import {
+ EvaluationContext,
+ Overrides
+} from '../evaluation/evaluationContext/evaluationContext.types.js';
+import { CONSTANTS } from '../features/constants.js';
+import { EvaluationResultSegments } from '../evaluation/models.js';
export const all = (iterable: Array) => iterable.filter(e => !!e).length === iterable.length;
export const any = (iterable: Array) => iterable.filter(e => !!e).length > 0;
@@ -56,17 +67,17 @@ export class SegmentConditionModel {
};
operator: string;
- value: string | null | undefined;
- property_: string | null | undefined;
+ value: string | null | undefined | string[];
+ property: string | null | undefined;
constructor(
operator: string,
- value?: string | null | undefined,
+ value?: string | null | undefined | string[],
property?: string | null | undefined
) {
this.operator = operator;
this.value = value;
- this.property_ = property;
+ this.property = property;
}
matchesTraitValue(traitValue: any) {
@@ -79,17 +90,49 @@ export class SegmentConditionModel {
);
},
evaluateRegex: (traitValue: any) => {
- return !!this.value && !!traitValue?.toString().match(new RegExp(this.value));
+ try {
+ if (!this.value) {
+ return false;
+ }
+ const regex = new RegExp(this.value?.toString());
+ return !!traitValue?.toString().match(regex);
+ } catch {
+ return false;
+ }
},
evaluateModulo: (traitValue: any) => {
- if (isNaN(parseFloat(traitValue)) || !this.value) {
+ const parsedTraitValue = parseFloat(traitValue);
+ if (isNaN(parsedTraitValue) || !this.value) {
return false;
}
- const parts = this.value.split('|');
- const [divisor, reminder] = [parseFloat(parts[0]), parseFloat(parts[1])];
- return traitValue % divisor === reminder;
+
+ const parts = this.value.toString().split('|');
+ if (parts.length !== 2) {
+ return false;
+ }
+
+ const divisor = parseFloat(parts[0]);
+ const remainder = parseFloat(parts[1]);
+
+ if (isNaN(divisor) || isNaN(remainder) || divisor === 0) {
+ return false;
+ }
+
+ return parsedTraitValue % divisor === remainder;
},
- evaluateIn: (traitValue: any) => {
+ evaluateIn: (traitValue: string[] | string) => {
+ if (Array.isArray(this.value)) {
+ return this.value.includes(traitValue.toString());
+ }
+
+ if (typeof this.value === 'string') {
+ try {
+ const parsed = JSON.parse(this.value);
+ if (Array.isArray(parsed)) {
+ return parsed.includes(traitValue.toString());
+ }
+ } catch {}
+ }
return this.value?.split(',').includes(traitValue.toString());
}
};
@@ -144,4 +187,70 @@ export class SegmentModel {
this.id = id;
this.name = name;
}
+
+ static fromSegmentResult(
+ segmentResults: EvaluationResultSegments,
+ evaluationContext: EvaluationContext
+ ): SegmentModel[] {
+ const segmentModels: SegmentModel[] = [];
+ if (!evaluationContext.segments) {
+ return [];
+ }
+
+ for (const segmentResult of segmentResults) {
+ const segmentContext = evaluationContext.segments[segmentResult.key];
+ if (segmentContext) {
+ const segment = new SegmentModel(parseInt(segmentContext.key), segmentContext.name);
+ segment.rules = segmentContext.rules.map(rule => new SegmentRuleModel(rule.type));
+ segment.featureStates = SegmentModel.createFeatureStatesFromOverrides(
+ segmentContext.overrides || []
+ );
+ segmentModels.push(segment);
+ }
+ }
+
+ return segmentModels;
+ }
+
+ private static createFeatureStatesFromOverrides(overrides: Overrides): FeatureStateModel[] {
+ if (!overrides) return [];
+ return overrides.map(override => {
+ const feature = new FeatureModel(
+ parseInt(override.feature_key),
+ override.name,
+ override.variants?.length && override.variants.length > 0
+ ? CONSTANTS.MULTIVARIATE
+ : CONSTANTS.STANDARD
+ );
+
+ const featureState = new FeatureStateModel(
+ feature,
+ override.enabled,
+ override.priority || 0
+ );
+
+ if (override.value !== undefined) {
+ featureState.setValue(override.value);
+ }
+
+ if (override.variants && override.variants.length > 0) {
+ featureState.multivariateFeatureStateValues = this.createMultivariateValues(
+ override.variants
+ );
+ }
+
+ return featureState;
+ });
+ }
+
+ private static createMultivariateValues(variants: any[]): MultivariateFeatureStateValueModel[] {
+ return variants.map(
+ variant =>
+ new MultivariateFeatureStateValueModel(
+ new MultivariateFeatureOptionModel(variant.value, variant.id as number),
+ variant.weight as number,
+ variant.id as number
+ )
+ );
+ }
}
diff --git a/flagsmith-engine/utils/hashing/index.ts b/flagsmith-engine/utils/hashing/index.ts
index 72f3f46..1390d13 100644
--- a/flagsmith-engine/utils/hashing/index.ts
+++ b/flagsmith-engine/utils/hashing/index.ts
@@ -14,7 +14,7 @@ const makeRepeated = (arr: Array, repeats: number) =>
* @param {} iterations=1 num times to include each id in the generated string to hash
* @returns number number between 0 (inclusive) and 100 (exclusive)
*/
-export function getHashedPercentateForObjIds(objectIds: Array, iterations = 1): number {
+export function getHashedPercentageForObjIds(objectIds: Array, iterations = 1): number {
let toHash = makeRepeated(objectIds, iterations).join(',');
const hashedValue = md5(toHash);
const hashedInt = BigInt('0x' + hashedValue);
@@ -24,7 +24,7 @@ export function getHashedPercentateForObjIds(objectIds: Array, iterations =
/* istanbul ignore next */
if (value === 100) {
/* istanbul ignore next */
- return getHashedPercentateForObjIds(objectIds, iterations + 1);
+ return getHashedPercentageForObjIds(objectIds, iterations + 1);
}
return value;
diff --git a/package-lock.json b/package-lock.json
index ffdb63f..330811c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,17 +9,21 @@
"version": "6.1.0",
"license": "MIT",
"dependencies": {
+ "jsonpath": "^1.1.1",
"pino": "^8.8.0",
"semver": "^7.3.7",
"undici-types": "^6.19.8"
},
"devDependencies": {
+ "@types/jest": "^30.0.0",
+ "@types/jsonpath": "^0.2.4",
"@types/node": "^20.16.10",
"@types/semver": "^7.3.9",
"@types/uuid": "^8.3.4",
"@vitest/coverage-v8": "^2.1.2",
"esbuild": "^0.25.0",
"husky": "^7.0.4",
+ "json-schema-to-typescript": "^15.0.4",
"prettier": "^2.2.1",
"typescript": "^4.9.5",
"undici": "^6.19.8",
@@ -42,6 +46,39 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@apidevtools/json-schema-ref-parser": {
+ "version": "11.9.3",
+ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz",
+ "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jsdevtools/ono": "^7.1.3",
+ "@types/json-schema": "^7.0.15",
+ "js-yaml": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/philsturgeon"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-string-parser": {
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz",
@@ -52,10 +89,11 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz",
- "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@@ -625,6 +663,85 @@
"node": ">=8"
}
},
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "30.1.2",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.1.2.tgz",
+ "integrity": "sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz",
+ "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
@@ -673,6 +790,13 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@jsdevtools/ono": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
+ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -949,6 +1073,13 @@
"win32"
]
},
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.41",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
+ "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -956,6 +1087,65 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jsonpath": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz",
+ "integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "20.16.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
@@ -971,12 +1161,36 @@
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
+ "node_modules/@types/yargs": {
+ "version": "17.0.33",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
+ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@vitest/coverage-v8": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz",
@@ -1158,6 +1372,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -1180,7 +1401,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
@@ -1201,6 +1423,29 @@
}
]
},
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -1251,6 +1496,23 @@
"node": ">=12"
}
},
+ "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,
+ "license": "MIT",
+ "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/check-error": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
@@ -1261,6 +1523,22 @@
"node": ">= 16"
}
},
+ "node_modules/ci-info": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz",
+ "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1320,6 +1598,12 @@
"node": ">=6"
}
},
+ "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==",
+ "license": "MIT"
+ },
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -1754,6 +2038,72 @@
"node": ">=18"
}
},
+ "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,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "1.14.3",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
+ "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^4.2.0",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/escodegen/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==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz",
+ "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
@@ -1764,6 +2114,15 @@
"@types/estree": "^1.0.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==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
@@ -1780,6 +2139,24 @@
"node": ">=0.8.x"
}
},
+ "node_modules/expect": {
+ "version": "30.1.2",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.2.tgz",
+ "integrity": "sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.1.2",
+ "@jest/get-type": "30.1.0",
+ "jest-matcher-utils": "30.1.2",
+ "jest-message-util": "30.1.0",
+ "jest-mock": "30.0.5",
+ "jest-util": "30.0.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/expect-type": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
@@ -1790,6 +2167,12 @@
"node": ">=12.0.0"
}
},
+ "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==",
+ "license": "MIT"
+ },
"node_modules/fast-redact": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
@@ -1798,6 +2181,37 @@
"node": ">=6"
}
},
+ "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/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/foreground-child": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
@@ -1841,6 +2255,34 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "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/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -1890,6 +2332,16 @@
}
]
},
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -1899,6 +2351,29 @@
"node": ">=8"
}
},
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -1970,6 +2445,193 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jest-diff": {
+ "version": "30.1.2",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.2.tgz",
+ "integrity": "sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.0.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.1.2",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.1.2.tgz",
+ "integrity": "sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.1.2",
+ "pretty-format": "30.0.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz",
+ "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.0.5",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.0.5",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz",
+ "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.0.5",
+ "@types/node": "*",
+ "jest-util": "30.0.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz",
+ "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.0.5",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "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,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-to-typescript": {
+ "version": "15.0.4",
+ "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz",
+ "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@apidevtools/json-schema-ref-parser": "^11.5.5",
+ "@types/json-schema": "^7.0.15",
+ "@types/lodash": "^4.17.7",
+ "is-glob": "^4.0.3",
+ "js-yaml": "^4.1.0",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.8",
+ "prettier": "^3.2.5",
+ "tinyglobby": "^0.2.9"
+ },
+ "bin": {
+ "json2ts": "dist/src/cli.js"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/json-schema-to-typescript/node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/jsonpath": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz",
+ "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==",
+ "license": "MIT",
+ "dependencies": {
+ "esprima": "1.2.2",
+ "static-eval": "2.0.2",
+ "underscore": "1.12.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/loupe": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
@@ -2013,6 +2675,59 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -2055,6 +2770,23 @@
"node": ">=14.0.0"
}
},
+ "node_modules/optionator": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.6",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "word-wrap": "~1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.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",
@@ -2116,6 +2848,19 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/pino": {
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz",
@@ -2180,6 +2925,14 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
@@ -2195,6 +2948,34 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/pretty-format": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz",
+ "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -2213,6 +2994,13 @@
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
},
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/readable-stream": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
@@ -2341,6 +3129,16 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/sonic-boom": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz",
@@ -2349,6 +3147,16 @@
"atomic-sleep": "^1.0.0"
}
},
+ "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==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -2366,6 +3174,19 @@
"node": ">= 10.x"
}
},
+ "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,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -2373,6 +3194,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/static-eval": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz",
+ "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==",
+ "license": "MIT",
+ "dependencies": {
+ "escodegen": "^1.8.1"
+ }
+ },
"node_modules/std-env": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz",
@@ -2468,50 +3298,6 @@
"node": ">=18"
}
},
- "node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/test-exclude/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "dev": true,
- "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/test-exclude/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/thread-stream": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz",
@@ -2534,6 +3320,23 @@
"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.0.2",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
@@ -2573,6 +3376,31 @@
"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,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
@@ -2586,6 +3414,12 @@
"node": ">=4.2.0"
}
},
+ "node_modules/underscore": {
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
+ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==",
+ "license": "MIT"
+ },
"node_modules/undici": {
"version": "6.21.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz",
@@ -2838,6 +3672,15 @@
"node": ">=8"
}
},
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
diff --git a/package.json b/package.json
index aa632b6..ec45f87 100644
--- a/package.json
+++ b/package.json
@@ -57,20 +57,27 @@
"build": "tsc -b tsconfig.cjs.json tsconfig.esm.json && echo '{\"type\": \"commonjs\"}'> build/cjs/package.json",
"deploy": "npm i && npm run build && npm publish",
"deploy:beta": "npm i && npm run build && npm publish --tag beta",
- "prepare": "husky install"
+ "prepare": "husky install",
+ "generate-evaluation-result-types": "curl -o evaluation-result.json https://raw.githubusercontent.com/Flagsmith/flagsmith/main/sdk/evaluation-result.json && npx json2ts -i evaluation-result.json -o flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts && rm evaluation-result.json",
+ "generate-evaluation-context-types": "curl -o evaluation-context.json https://raw.githubusercontent.com/Flagsmith/flagsmith/main/sdk/evaluation-context.json && npx json2ts -i evaluation-context.json -o flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts && rm evaluation-context.json",
+ "generate-engine-types": "npm run generate-evaluation-result-types && npm run generate-evaluation-context-types"
},
"dependencies": {
+ "jsonpath": "^1.1.1",
"pino": "^8.8.0",
"semver": "^7.3.7",
"undici-types": "^6.19.8"
},
"devDependencies": {
+ "@types/jest": "^30.0.0",
+ "@types/jsonpath": "^0.2.4",
"@types/node": "^20.16.10",
"@types/semver": "^7.3.9",
"@types/uuid": "^8.3.4",
"@vitest/coverage-v8": "^2.1.2",
"esbuild": "^0.25.0",
"husky": "^7.0.4",
+ "json-schema-to-typescript": "^15.0.4",
"prettier": "^2.2.1",
"typescript": "^4.9.5",
"undici": "^6.19.8",
diff --git a/sdk/index.ts b/sdk/index.ts
index e299288..b543541 100644
--- a/sdk/index.ts
+++ b/sdk/index.ts
@@ -1,22 +1,21 @@
import { Dispatcher } from 'undici-types';
-import {
- getEnvironmentFeatureStates,
- getIdentityFeatureStates
-} from '../flagsmith-engine/index.js';
-import { EnvironmentModel } from '../flagsmith-engine/index.js';
+
import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js';
-import { IdentityModel } from '../flagsmith-engine/index.js';
-import { TraitModel } from '../flagsmith-engine/index.js';
import { ANALYTICS_ENDPOINT, AnalyticsProcessor } from './analytics.js';
import { BaseOfflineHandler } from './offline_handlers.js';
-import { FlagsmithAPIError } from './errors.js';
+import { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
import { DefaultFlag, Flags } from './models.js';
import { EnvironmentDataPollingManager } from './polling_manager.js';
import { Deferred, generateIdentitiesData, retryFetch } from './utils.js';
-import { SegmentModel } from '../flagsmith-engine/index.js';
-import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js';
+import {
+ SegmentModel,
+ EnvironmentModel,
+ IdentityModel,
+ TraitModel,
+ getEvaluationResult
+} from '../flagsmith-engine/index.js';
import {
Fetch,
FlagsmithCache,
@@ -25,6 +24,7 @@ import {
TraitConfig
} from './types.js';
import { pino, Logger } from 'pino';
+import { getEvaluationContext } from '../flagsmith-engine/evaluation/evaluationContext/mappers.js';
export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js';
export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
@@ -278,7 +278,13 @@ export class Flagsmith {
}))
);
- return getIdentitySegments(environment, identityModel);
+ const context = getEvaluationContext(environment, identityModel);
+ if (!context) {
+ throw new FlagsmithClientError('Local evaluation required to obtain identity segments');
+ }
+ const evaluationResult = getEvaluationResult(context);
+
+ return SegmentModel.fromSegmentResult(evaluationResult.segments, context);
}
private async fetchEnvironment(): Promise {
@@ -397,14 +403,17 @@ export class Flagsmith {
private async getEnvironmentFlagsFromDocument(): Promise {
const environment = await this.getEnvironment();
- const flags = Flags.fromFeatureStateModels({
- featureStates: getEnvironmentFeatureStates(environment),
- analyticsProcessor: this.analyticsProcessor,
- defaultFlagHandler: this.defaultFlagHandler
- });
+ const context = getEvaluationContext(environment);
+ if (!context) {
+ throw new FlagsmithClientError('Unable to get flags. No environment present.');
+ }
+ const evaluationResult = getEvaluationResult(context);
+ const flags = Flags.fromEvaluationResult(evaluationResult);
+
if (!!this.cache) {
await this.cache.set('flags', flags);
}
+
return flags;
}
@@ -422,14 +431,17 @@ export class Flagsmith {
}))
);
- const featureStates = getIdentityFeatureStates(environment, identityModel);
+ const context = getEvaluationContext(environment, identityModel);
+ if (!context) {
+ throw new FlagsmithClientError('Unable to get flags. No environment present.');
+ }
+ const evaluationResult = getEvaluationResult(context);
- const flags = Flags.fromFeatureStateModels({
- featureStates: featureStates,
- analyticsProcessor: this.analyticsProcessor,
- defaultFlagHandler: this.defaultFlagHandler,
- identityID: identityModel.djangoID || identityModel.compositeKey
- });
+ const flags = Flags.fromEvaluationResult(
+ evaluationResult,
+ this.defaultFlagHandler,
+ this.analyticsProcessor
+ );
if (!!this.cache) {
await this.cache.set(`flags-${identifier}`, flags);
diff --git a/sdk/models.ts b/sdk/models.ts
index 90cffae..2fdfbed 100644
--- a/sdk/models.ts
+++ b/sdk/models.ts
@@ -1,7 +1,8 @@
+import { EvaluationResult, FlagResult } from '../flagsmith-engine/evaluation/models.js';
import { FeatureStateModel } from '../flagsmith-engine/features/models.js';
import { AnalyticsProcessor } from './analytics.js';
-type FlagValue = string | number | boolean | undefined;
+type FlagValue = string | number | boolean | undefined | null;
/**
* A Flagsmith feature. It has an enabled/disabled state, and an optional {@link FlagValue}.
@@ -56,6 +57,7 @@ export class Flag extends BaseFlag {
isDefault?: boolean;
featureId: number;
featureName: string;
+ reason?: string;
}) {
super(params.value, params.enabled, !!params.isDefault);
this.featureId = params.featureId;
@@ -74,6 +76,15 @@ export class Flag extends BaseFlag {
});
}
+ static fromFlagResult(flagResult: FlagResult): Flag {
+ return new Flag({
+ enabled: flagResult.enabled,
+ value: flagResult.value ?? null,
+ featureId: Number(flagResult.feature_key),
+ featureName: flagResult.name
+ });
+ }
+
static fromAPIFlag(flagData: any): Flag {
return new Flag({
enabled: flagData['enabled'],
@@ -99,6 +110,28 @@ export class Flags {
this.analyticsProcessor = data.analyticsProcessor;
}
+ static fromEvaluationResult(
+ evaluationResult: EvaluationResult,
+ defaultFlagHandler?: (v: string) => DefaultFlag,
+ analyticsProcessor?: AnalyticsProcessor
+ ): Flags {
+ const flags: { [key: string]: Flag } = {};
+ for (const flag of evaluationResult.flags) {
+ flags[flag.name] = new Flag({
+ enabled: flag.enabled,
+ value: flag.value ?? null,
+ featureId: Number(flag.feature_key),
+ featureName: flag.name,
+ reason: flag.reason
+ });
+ }
+ return new Flags({
+ flags: flags,
+ defaultFlagHandler: defaultFlagHandler,
+ analyticsProcessor: analyticsProcessor
+ });
+ }
+
static fromFeatureStateModels(data: {
featureStates: FeatureStateModel[];
analyticsProcessor?: AnalyticsProcessor;
diff --git a/tests/engine/e2e/engine.test.ts b/tests/engine/e2e/engine.test.ts
index 87d045f..ff162a8 100644
--- a/tests/engine/e2e/engine.test.ts
+++ b/tests/engine/e2e/engine.test.ts
@@ -1,23 +1,16 @@
-import { getIdentityFeatureStates } from '../../../flagsmith-engine/index.js';
-import { EnvironmentModel } from '../../../flagsmith-engine/environments/models.js';
-import { buildEnvironmentModel } from '../../../flagsmith-engine/environments/util.js';
-import { IdentityModel } from '../../../flagsmith-engine/identities/models.js';
-import { buildIdentityModel } from '../../../flagsmith-engine/identities/util.js';
+import { getEvaluationResult } from '../../../flagsmith-engine/index.js';
+import { Flags } from '../../../sdk/models.js';
import * as testData from '../engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json';
+import { EvaluationContext } from '../../../flagsmith-engine/evaluationContext/evaluationContext.types.js';
function extractTestCases(data: any): {
- environment: EnvironmentModel;
- identity: IdentityModel;
response: any;
+ context: EvaluationContext;
}[] {
- const environmentModel = buildEnvironmentModel(data['environment']);
- const test_data = data['identities_and_responses'].map((test_case: any) => {
- const identity = buildIdentityModel(test_case['identity']);
-
+ const test_data = data['test_cases'].map((test_case: any) => {
return {
- environment: environmentModel,
- identity: identity,
- response: test_case['response']
+ context: test_case['context'],
+ response: test_case['result']
};
});
return test_data;
@@ -26,20 +19,21 @@ function extractTestCases(data: any): {
test('Test Engine', () => {
const testCases = extractTestCases(testData);
for (const testCase of testCases) {
- const engine_response = getIdentityFeatureStates(testCase.environment, testCase.identity);
- const sortedEngineFlags = engine_response.sort((a, b) =>
- a.feature.name > b.feature.name ? 1 : -1
- );
- const sortedAPIFlags = testCase.response['flags'].sort((a: any, b: any) =>
- a.feature.name > b.feature.name ? 1 : -1
- );
+ const engine_response = getEvaluationResult(testCase.context);
+ const flags = Flags.fromEvaluationResult(engine_response);
+ const sortedEngineFlags = flags
+ .allFlags()
+ .sort((a, b) => (a.featureName > b.featureName ? 1 : -1));
+
+ const expectedFlags = testCase.response['flags'] || {};
+ const sortedAPIFlags = Object.values(expectedFlags).sort((a: any, b: any) =>
+ a.name > b.name ? 1 : -1
+ ) as Flags[];
expect(sortedEngineFlags.length).toBe(sortedAPIFlags.length);
for (let i = 0; i < sortedEngineFlags.length; i++) {
- expect(sortedEngineFlags[i].getValue(testCase.identity.djangoID)).toBe(
- sortedAPIFlags[i]['feature_state_value']
- );
+ expect(sortedEngineFlags[i].value).toBe(sortedAPIFlags[i].value);
expect(sortedEngineFlags[i].enabled).toBe(sortedAPIFlags[i]['enabled']);
}
}
diff --git a/tests/engine/engine-tests/engine-test-data b/tests/engine/engine-tests/engine-test-data
index 933f2ba..facf33a 160000
--- a/tests/engine/engine-tests/engine-test-data
+++ b/tests/engine/engine-tests/engine-test-data
@@ -1 +1 @@
-Subproject commit 933f2ba7aa6430797afc2d053530cfd005b461f6
+Subproject commit facf33a4c50fdabdce29899b19b9ea65ea70eb18
diff --git a/tests/engine/unit/engine.test.ts b/tests/engine/unit/engine.test.ts
index 15b27d1..1d6df05 100644
--- a/tests/engine/unit/engine.test.ts
+++ b/tests/engine/unit/engine.test.ts
@@ -1,8 +1,10 @@
import {
- getEnvironmentFeatureState,
- getEnvironmentFeatureStates,
- getIdentityFeatureState,
- getIdentityFeatureStates
+ evaluateFeatures,
+ evaluateSegments,
+ getEvaluationResult,
+ isHigherPriority,
+ SegmentOverrides,
+ shouldApplyOverride
} from '../../../flagsmith-engine/index.js';
import { CONSTANTS } from '../../../flagsmith-engine/features/constants.js';
import { FeatureModel, FeatureStateModel } from '../../../flagsmith-engine/features/models.js';
@@ -11,102 +13,355 @@ import {
environment,
environmentWithSegmentOverride,
feature1,
- getEnvironmentFeatureStateForFeature,
- getEnvironmentFeatureStateForFeatureByName,
identity,
identityInSegment,
segmentConditionProperty,
segmentConditionStringValue
} from './utils.js';
+import { getEvaluationContext } from '../../../flagsmith-engine/evaluation/evaluationContext/mappers.js';
+import { TARGETING_REASONS } from '../../../flagsmith-engine/features/types.js';
+import { EvaluationContext } from '../../../flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js';
+import { IDENTITY_OVERRIDE_SEGMENT_NAME } from '../../../flagsmith-engine/segments/constants.js';
-test('test_identity_get_feature_state_without_any_override', () => {
- const feature_state = getIdentityFeatureState(environment(), identity(), feature1().name);
+test('test_get_evaluation_result_without_any_override', () => {
+ const context = getEvaluationContext(environment(), identity());
+ const result = getEvaluationResult(context);
- expect(feature_state.feature).toStrictEqual(feature1());
+ const flag = result.flags.find(f => f.name === feature1().name);
+ expect(flag).toBeDefined();
+ expect(flag?.name).toBe(feature1().name);
+ expect(flag?.feature_key).toBe(feature1().id.toString());
+ expect(flag?.reason).toBe(TARGETING_REASONS.DEFAULT);
});
-test('test_identity_get_feature_state_without_any_override_no_fs', () => {
- expect(() => {
- getIdentityFeatureState(environment(), identity(), 'nonExistentName');
- }).toThrowError('Feature State Not Found');
-});
-
-test('test_identity_get_all_feature_states_no_segments', () => {
+test('test_get_evaluation_result_with_identity_override_and_no_segment_override', () => {
const env = environment();
const ident = identity();
const overridden_feature = new FeatureModel(3, 'overridden_feature', CONSTANTS.STANDARD);
env.featureStates.push(new FeatureStateModel(overridden_feature, false, 3));
-
ident.identityFeatures = [new FeatureStateModel(overridden_feature, true, 4)];
+ env.identityOverrides = [ident];
+
+ const context = getEvaluationContext(env, ident);
+ const result = getEvaluationResult(context);
- const featureStates = getIdentityFeatureStates(env, ident);
+ expect(result.flags.length).toBe(3);
- expect(featureStates.length).toBe(3);
- for (const featuresState of featureStates) {
- const environmentFeatureState = getEnvironmentFeatureStateForFeature(
- env,
- featuresState.feature
+ for (const flag of result.flags) {
+ const environmentFeature = Object.values(context.features || {}).find(
+ f => f.name === flag.name
+ );
+
+ const expected = flag.name === 'overridden_feature' ? true : environmentFeature?.enabled;
+
+ expect(flag.enabled).toBe(expected);
+ expect(flag.reason).toBe(
+ flag.name === 'overridden_feature'
+ ? `${TARGETING_REASONS.TARGETING_MATCH}; segment=${IDENTITY_OVERRIDE_SEGMENT_NAME}`
+ : TARGETING_REASONS.DEFAULT
);
- const expected =
- environmentFeatureState?.feature == overridden_feature
- ? true
- : environmentFeatureState?.enabled;
- expect(featuresState.enabled).toBe(expected);
}
});
test('test_identity_get_all_feature_states_with_traits', () => {
const trait_models = new TraitModel(segmentConditionProperty, segmentConditionStringValue);
- const featureStates = getIdentityFeatureStates(
- environmentWithSegmentOverride(),
- identityInSegment(),
- [trait_models]
+ const context = getEvaluationContext(environmentWithSegmentOverride(), identityInSegment(), [
+ trait_models
+ ]);
+
+ const result = getEvaluationResult(context);
+
+ const overriddenFlag = result.flags.find(f => f.value === 'segment_override');
+ expect(overriddenFlag).toBeDefined();
+ expect(overriddenFlag?.value).toBe('segment_override');
+ expect(overriddenFlag?.reason).toEqual(
+ `${TARGETING_REASONS.TARGETING_MATCH}; segment=test name`
);
- expect(featureStates[0].getValue()).toBe('segment_override');
});
-test('test_identity_get_all_feature_states_with_traits_hideDisabledFlags', () => {
- const trait_models = new TraitModel(segmentConditionProperty, segmentConditionStringValue);
+test('test_environment_get_all_feature_states', () => {
+ const env = environment();
+ const context = getEvaluationContext(env);
+ const result = getEvaluationResult(context);
- const env = environmentWithSegmentOverride();
- env.project.hideDisabledFlags = true;
+ expect(result.flags.length).toBe(Object.keys(context.features || {}).length);
- const featureStates = getIdentityFeatureStates(env, identityInSegment(), [trait_models]);
- expect(featureStates.length).toBe(0);
+ result.flags.forEach(flag => {
+ expect(flag.reason).toBe(TARGETING_REASONS.DEFAULT);
+ });
+
+ for (const flag of result.flags) {
+ const envFeature = Object.values(context.features || {}).find(f => f.name === flag.name);
+ expect(flag.enabled).toBe(envFeature?.enabled);
+ expect(flag.value).toBe(envFeature?.value);
+ }
});
-test('test_environment_get_all_feature_states', () => {
- const env = environment();
- const featureStates = getEnvironmentFeatureStates(env);
+test('isHigherPriority should handle undefined priorities correctly', () => {
+ expect(isHigherPriority(1, 2)).toBe(true);
+ expect(isHigherPriority(2, 1)).toBe(false);
+ expect(isHigherPriority(undefined, 5)).toBe(false);
+ expect(isHigherPriority(5, undefined)).toBe(true);
+ expect(isHigherPriority(undefined, undefined)).toBe(false);
+});
+
+test('shouldApplyOverride with priority conflicts', () => {
+ const existingOverrides: SegmentOverrides = {
+ feature1: {
+ feature: {
+ key: 'key',
+ feature_key: 'feature1',
+ name: 'name',
+ enabled: true,
+ value: 'value',
+ priority: 5
+ },
+ segmentName: 'segment1'
+ }
+ };
- expect(featureStates).toBe(env.featureStates);
+ expect(shouldApplyOverride({ feature_key: 'feature1', priority: 2 }, existingOverrides)).toBe(
+ true
+ );
+ expect(shouldApplyOverride({ feature_key: 'feature1', priority: 10 }, existingOverrides)).toBe(
+ false
+ );
});
-test('test_environment_get_feature_states_hides_disabled_flags_if_enabled', () => {
- const env = environment();
+test('evaluateSegments handles segments with identity identifier matching', () => {
+ const context: EvaluationContext = {
+ environment: {
+ key: 'test-env',
+ name: 'Test Environment'
+ },
+ identity: {
+ key: 'test-user',
+ identifier: 'test-user'
+ },
+ segments: {
+ '1': {
+ key: '1',
+ name: 'segment_with_no_overrides',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'EQUAL',
+ value: 'test-user'
+ }
+ ]
+ }
+ ],
+ overrides: []
+ },
+ '2': {
+ key: '2',
+ name: 'segment_with_overrides',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'EQUAL',
+ value: 'test-user'
+ }
+ ]
+ }
+ ],
+ overrides: [
+ {
+ key: 'override1',
+ feature_key: 'feature1',
+ name: 'feature1',
+ enabled: true,
+ value: 'overridden_value',
+ priority: 1
+ }
+ ]
+ }
+ },
+ features: {
+ feature1: {
+ key: 'fs1',
+ feature_key: 'feature1',
+ name: 'feature1',
+ enabled: false,
+ value: 'default_value'
+ }
+ }
+ };
- env.project.hideDisabledFlags = true;
+ const result = evaluateSegments(context);
- const featureStates = getEnvironmentFeatureStates(env);
+ expect(result.segments).toHaveLength(2);
+ expect(result.segments).toEqual(
+ expect.arrayContaining([
+ { key: '1', name: 'segment_with_no_overrides' },
+ { key: '2', name: 'segment_with_overrides' }
+ ])
+ );
- expect(featureStates).not.toBe(env.featureStates);
- for (const fs of featureStates) {
- expect(fs.enabled).toBe(true);
- }
+ expect(Object.keys(result.segmentOverrides)).toEqual(['feature1']);
+ expect(result.segmentOverrides.feature1.segmentName).toBe('segment_with_overrides');
});
-test('test_environment_get_feature_state', () => {
- const env = environment();
- const feature = feature1();
- const featureState = getEnvironmentFeatureState(env, feature.name);
+test('evaluateSegments handles priority conflicts correctly', () => {
+ const context: EvaluationContext = {
+ environment: {
+ key: 'test-env',
+ name: 'Test Environment'
+ },
+ identity: {
+ key: 'test-user',
+ identifier: 'test-user'
+ },
+ segments: {
+ '1': {
+ key: '1',
+ name: 'low_priority_segment',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'EQUAL',
+ value: 'test-user'
+ }
+ ]
+ }
+ ],
+ overrides: [
+ {
+ key: 'override1',
+ feature_key: 'feature1',
+ name: 'feature1',
+ enabled: true,
+ value: 'low_priority_value',
+ priority: 10
+ }
+ ]
+ },
+ '2': {
+ key: '2',
+ name: 'high_priority_segment',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'EQUAL',
+ value: 'test-user'
+ }
+ ]
+ }
+ ],
+ overrides: [
+ {
+ key: 'override2',
+ feature_key: 'feature1',
+ name: 'feature1',
+ enabled: false,
+ value: 'high_priority_value',
+ priority: 1
+ }
+ ]
+ }
+ },
+ features: {
+ feature1: {
+ key: 'fs1',
+ feature_key: 'feature1',
+ name: 'feature1',
+ enabled: false,
+ value: 'default_value'
+ }
+ }
+ };
- expect(featureState.feature).toStrictEqual(feature);
+ const result = evaluateSegments(context);
+
+ expect(result.segments).toHaveLength(2);
+
+ expect(result.segmentOverrides.feature1.segmentName).toBe('high_priority_segment');
+ expect(result.segmentOverrides.feature1.feature.value).toBe('high_priority_value');
+ expect(result.segmentOverrides.feature1.feature.priority).toBe(1);
});
-test('test_environment_get_feature_state_raises_feature_state_not_found', () => {
- expect(() => {
- getEnvironmentFeatureState(environment(), 'not_a_feature_name');
- }).toThrowError('Feature State Not Found');
+test('evaluateSegments with non-matching identity returns empty', () => {
+ const context: EvaluationContext = {
+ environment: {
+ key: 'test-env',
+ name: 'Test Environment'
+ },
+ identity: {
+ key: 'test-user',
+ identifier: 'test-user'
+ },
+ segments: {
+ '1': {
+ key: '1',
+ name: 'segment_for_specific_user',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'EQUAL',
+ value: 'test-user-123'
+ }
+ ]
+ }
+ ],
+ overrides: [
+ {
+ key: 'override1',
+ feature_key: 'feature1',
+ name: 'feature1',
+ enabled: true,
+ value: 'overridden_value'
+ }
+ ]
+ }
+ },
+ features: {}
+ };
+
+ const result = evaluateSegments(context);
+
+ expect(result.segments).toEqual([]);
+ expect(result.segmentOverrides).toEqual({});
+});
+
+test('evaluateFeatures with multivariate evaluation', () => {
+ const context = {
+ features: {
+ mv_feature: {
+ key: 'mv',
+ feature_key: 'mv_feature',
+ name: 'Multivariate Feature',
+ enabled: true,
+ value: 'default',
+ variants: [
+ { value: 'variant_a', weight: 0 },
+ { value: 'variant_b', weight: 100 }
+ ]
+ }
+ },
+ identity: { key: 'test_user', identifier: 'test_user' },
+ environment: {
+ key: 'test_env',
+ name: 'Test Environment'
+ }
+ };
+
+ const result = evaluateFeatures(context, {});
+ expect(result[0].value).toBe('variant_b');
});
diff --git a/tests/engine/unit/segments/segment_evaluators.test.ts b/tests/engine/unit/segments/segment_evaluators.test.ts
index 1a73eec..6d260c7 100644
--- a/tests/engine/unit/segments/segment_evaluators.test.ts
+++ b/tests/engine/unit/segments/segment_evaluators.test.ts
@@ -3,19 +3,27 @@ import {
CONDITION_OPERATORS,
PERCENTAGE_SPLIT
} from '../../../../flagsmith-engine/segments/constants.js';
-import { SegmentConditionModel } from '../../../../flagsmith-engine/segments/models.js';
+
import {
traitsMatchSegmentCondition,
- evaluateIdentityInSegment
+ getContextValue,
+ getIdentitySegments
} from '../../../../flagsmith-engine/segments/evaluators.js';
import { TraitModel, IdentityModel } from '../../../../flagsmith-engine/index.js';
import { environment } from '../utils.js';
import { buildSegmentModel } from '../../../../flagsmith-engine/segments/util.js';
-import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
+import { getHashedPercentageForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
+import { getEvaluationContext } from '../../../../flagsmith-engine/evaluation/evaluationContext/mappers.js';
+import {
+ EvaluationContext,
+ InSegmentCondition,
+ SegmentCondition,
+ SegmentCondition1
+} from '../../../../flagsmith-engine/evaluation/models.js';
// todo: work out how to implement this in a test function or before hook
vi.mock('../../../../flagsmith-engine/utils/hashing', () => ({
- getHashedPercentateForObjIds: vi.fn(() => 1)
+ getHashedPercentageForObjIds: vi.fn(() => 1)
}));
let traitExistenceTestCases: [
@@ -48,14 +56,33 @@ let traitExistenceTestCases: [
test('test_traits_match_segment_condition_for_trait_existence_operators', () => {
for (const testCase of traitExistenceTestCases) {
const [operator, conditionProperty, conditionValue, traits, expectedResult] = testCase;
- let segmentModel = new SegmentConditionModel(operator, conditionValue, conditionProperty);
- expect(traitsMatchSegmentCondition(traits, segmentModel, 'any', 'any')).toBe(
- expectedResult
- );
+ let segmentConditionModel = {
+ operator,
+ value: conditionValue,
+ property: conditionProperty
+ };
+ const traitsMap = traits.reduce((acc, trait) => {
+ acc[trait.traitKey] = trait.traitValue;
+ return acc;
+ }, {});
+ const context: EvaluationContext = {
+ environment: {
+ key: 'any',
+ name: 'any'
+ },
+ identity: {
+ traits: traitsMap,
+ key: 'any',
+ identifier: 'any'
+ }
+ };
+ expect(
+ traitsMatchSegmentCondition(segmentConditionModel as SegmentCondition, 'any', context)
+ ).toBe(expectedResult);
}
});
-test('evaluateIdentityInSegment uses django ID for hashed percentage when present', () => {
+test('getIdentitySegments uses django ID for hashed percentage when present', () => {
var identityModel = new IdentityModel(
Date.now().toString(),
[],
@@ -84,13 +111,376 @@ test('evaluateIdentityInSegment uses django ID for hashed percentage when presen
feature_states: []
};
const segmentModel = buildSegmentModel(segmentDefinition);
+ const environmentModel = environment();
+ environmentModel.project.segments = [segmentModel];
+ const context = getEvaluationContext(environmentModel, identityModel);
- var result = evaluateIdentityInSegment(identityModel, segmentModel);
+ var result = getIdentitySegments(context);
- expect(result).toBe(true);
- expect(getHashedPercentateForObjIds).toHaveBeenCalledTimes(1);
- expect(getHashedPercentateForObjIds).toHaveBeenCalledWith([
- segmentModel.id,
- identityModel.djangoID
+ expect(result).toHaveLength(1);
+ expect(getHashedPercentageForObjIds).toHaveBeenCalledTimes(1);
+ expect(getHashedPercentageForObjIds).toHaveBeenCalledWith([
+ result[0].key,
+ context.identity!.key
]);
});
+
+describe('getIdentitySegments integration', () => {
+ test('returns only matching segments', () => {
+ const context: EvaluationContext = {
+ environment: { key: 'env', name: 'test' },
+ identity: {
+ key: 'user',
+ identifier: 'premium@example.com',
+ traits: { subscription: 'premium' }
+ },
+ segments: {
+ '1': {
+ key: '1',
+ name: 'premium_users',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ { property: 'subscription', operator: 'EQUAL', value: 'premium' }
+ ]
+ }
+ ],
+ overrides: []
+ },
+ '2': {
+ key: '2',
+ name: 'basic_users',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ { property: 'subscription', operator: 'EQUAL', value: 'basic' }
+ ]
+ }
+ ],
+ overrides: []
+ }
+ },
+ features: {}
+ };
+
+ const result = getIdentitySegments(context);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].name).toBe('premium_users');
+ });
+
+ test('returns empty array when no segments match', () => {
+ const context: EvaluationContext = {
+ environment: { key: 'env', name: 'test' },
+ identity: {
+ key: 'user',
+ identifier: 'test@example.com',
+ traits: { subscription: 'free' }
+ },
+ segments: {
+ '1': {
+ key: '1',
+ name: 'premium_users',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ { property: 'subscription', operator: 'EQUAL', value: 'premium' }
+ ]
+ }
+ ],
+ overrides: []
+ }
+ },
+ features: {}
+ };
+
+ const result = getIdentitySegments(context);
+ expect(result).toEqual([]);
+ });
+});
+
+describe('IN operator', () => {
+ const mockContext: EvaluationContext = {
+ environment: { key: 'env', name: 'test' },
+ identity: {
+ key: 'test-user',
+ identifier: 'test',
+ traits: { name: 'test' }
+ },
+ segments: {},
+ features: {}
+ };
+
+ test.each([
+ // Array of strings
+ [
+ {
+ property: '$.identity.identifier',
+ operator: CONDITION_OPERATORS.IN,
+ value: ['test', 'john-doe']
+ },
+ true
+ ],
+ [
+ {
+ property: '$.identity.identifier',
+ operator: CONDITION_OPERATORS.IN,
+ value: ['john-doe']
+ },
+ false
+ ],
+
+ // JSON encoded
+ [
+ {
+ property: '$.identity.identifier',
+ operator: CONDITION_OPERATORS.IN,
+ value: '["test", "john-doe"]'
+ },
+ true
+ ],
+ [
+ {
+ property: '$.identity.identifier',
+ operator: CONDITION_OPERATORS.IN,
+ value: '["john-doe"]'
+ },
+ false
+ ],
+
+ // Legacy value string to split
+ [
+ {
+ property: '$.identity.identifier',
+ operator: CONDITION_OPERATORS.IN,
+ value: 'test,john-doe'
+ },
+ true
+ ],
+ [
+ {
+ property: '$.identity.identifier',
+ operator: CONDITION_OPERATORS.IN,
+ value: 'john-doe'
+ },
+ false
+ ],
+ // Fails because the value is split in middle
+ [
+ {
+ property: '$.identity.identifier',
+ operator: CONDITION_OPERATORS.IN,
+ value: 'te,st,john-doe'
+ },
+ false
+ ],
+
+ // Edge cases
+ [{ property: '$.identity.identifier', operator: CONDITION_OPERATORS.IN, value: '' }, false],
+ [{ property: '$.identity.identifier', operator: CONDITION_OPERATORS.IN, value: [] }, false],
+ [
+ { property: '$.identity.identifier', operator: CONDITION_OPERATORS.IN, value: '[]' },
+ false
+ ]
+ ] as Array<[SegmentCondition | InSegmentCondition, boolean]>)(
+ 'evaluates IN condition %j to %s',
+ (condition: SegmentCondition | InSegmentCondition, expected: boolean) => {
+ const result = traitsMatchSegmentCondition(condition, 'segment', mockContext);
+ expect(result).toBe(expected);
+ }
+ );
+});
+
+describe('getIdentitySegments single segment evaluation', () => {
+ const baseContext: EvaluationContext = {
+ environment: { key: 'env', name: 'test' },
+ identity: { key: 'user', identifier: 'test@example.com', traits: { age: 25 } },
+ segments: {},
+ features: {}
+ };
+
+ test('returns empty array for segment with no rules', () => {
+ const context = {
+ ...baseContext,
+ segments: {
+ '1': {
+ key: '1',
+ name: 'empty_segment',
+ rules: [],
+ overrides: []
+ }
+ }
+ };
+
+ expect(getIdentitySegments(context)).toEqual([]);
+ });
+
+ test('returns segment when all rules match', () => {
+ const context: EvaluationContext = {
+ ...baseContext,
+ segments: {
+ '1': {
+ key: '1',
+ name: 'matching_segment',
+ rules: [
+ {
+ type: ALL_RULE,
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'EQUAL',
+ value: 'test@example.com'
+ }
+ ],
+ rules: []
+ },
+ {
+ type: ALL_RULE,
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'CONTAINS',
+ value: 'test@example.com'
+ }
+ ],
+ rules: []
+ }
+ ],
+ overrides: []
+ }
+ }
+ };
+
+ const result = getIdentitySegments(context);
+ expect(result).toHaveLength(1);
+ expect(result[0].name).toBe('matching_segment');
+ });
+
+ test('returns empty array when any rule fails', () => {
+ const context: EvaluationContext = {
+ ...baseContext,
+ segments: {
+ '1': {
+ key: '1',
+ name: 'failing_segment',
+ rules: [
+ {
+ type: ALL_RULE,
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'EQUAL',
+ value: 'test@example.com'
+ }
+ ],
+ rules: []
+ },
+ {
+ type: ALL_RULE,
+ conditions: [{ property: 'age', operator: 'EQUAL', value: '30' }],
+ rules: []
+ }
+ ],
+ overrides: []
+ }
+ }
+ };
+
+ expect(getIdentitySegments(context)).toEqual([]);
+ });
+});
+
+describe('getContextValue', () => {
+ const mockContext: EvaluationContext = {
+ environment: {
+ key: 'test-env-key',
+ name: 'Test Environment'
+ },
+ identity: {
+ key: 'user-123',
+ identifier: 'user@example.com'
+ },
+ segments: {},
+ features: {}
+ };
+
+ // Success cases
+ test.each([
+ ['$.identity.identifier', 'user@example.com'],
+ ['$.environment.name', 'Test Environment'],
+ ['$.environment.key', 'test-env-key']
+ ])('returns correct value for path %s', (jsonPath, expected) => {
+ const result = getContextValue(jsonPath, mockContext);
+ expect(result).toBe(expected);
+ });
+
+ // Undefined or invalid cases
+ test.each([
+ ['$.identity.traits.user_type', 'unsupported nested path'],
+ ['identity.identifier', 'missing $ prefix'],
+ ['$.invalid.path', 'completely invalid path'],
+ ['$.identity.nonexistent', 'valid structure but missing property'],
+ ['', 'empty string'],
+ ['$', 'just $ symbol']
+ ])('returns undefined for %s (%s)', jsonPath => {
+ const result = getContextValue(jsonPath, mockContext);
+ expect(result).toBeUndefined();
+ });
+
+ // Context error cases
+ test.each([
+ [undefined, '$.identity.identifier', 'undefined context'],
+ [{ segments: {}, features: {} }, '$.identity.identifier', 'missing identity'],
+ [
+ { identity: { key: 'test', identifier: 'test' }, segments: {}, features: {} },
+ '$.environment.name',
+ 'missing environment'
+ ]
+ ])('returns undefined when %s', (context, jsonPath, _) => {
+ const result = getContextValue(jsonPath, context as EvaluationContext);
+ expect(result).toBeUndefined();
+ });
+});
+
+describe('percentage split operator', () => {
+ const mockContext: EvaluationContext = {
+ environment: { key: 'env', name: 'Test Env' },
+ identity: {
+ key: 'user-123',
+ identifier: 'test@example.com',
+ traits: {
+ age: 25,
+ subscription: 'premium',
+ active: true
+ }
+ },
+ segments: {},
+ features: {}
+ };
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ test.each([
+ [25.5, 30, true],
+ [25.5, 20, false],
+ [25.5, 25.5, true],
+ [0, 0, true],
+ [100, 99.9, false]
+ ])('percentage %d with threshold %d returns %s', (hashedValue, threshold, expected) => {
+ const mockHashFn = getHashedPercentageForObjIds;
+ mockHashFn.mockReturnValue(hashedValue);
+ const condition = {
+ property: 'any',
+ operator: 'PERCENTAGE_SPLIT',
+ value: threshold.toString()
+ } as SegmentCondition1 | InSegmentCondition;
+ const result = traitsMatchSegmentCondition(condition, 'seg1', mockContext);
+
+ expect(result).toBe(expected);
+ expect(getHashedPercentageForObjIds).toHaveBeenCalledWith(['seg1', 'user-123']);
+ });
+});
diff --git a/tests/engine/unit/segments/segments_model.test.ts b/tests/engine/unit/segments/segments_model.test.ts
index 17d9166..5607f03 100644
--- a/tests/engine/unit/segments/segments_model.test.ts
+++ b/tests/engine/unit/segments/segments_model.test.ts
@@ -1,3 +1,5 @@
+import { EvaluationContext } from '../../../../flagsmith-engine/evaluationContext/evaluationContext.types';
+import { CONSTANTS } from '../../../../flagsmith-engine/features/constants';
import {
ALL_RULE,
ANY_RULE,
@@ -8,6 +10,7 @@ import {
all,
any,
SegmentConditionModel,
+ SegmentModel,
SegmentRuleModel
} from '../../../../flagsmith-engine/segments/models';
@@ -135,3 +138,78 @@ test('test_segment_rule_matching_function', () => {
expect(new SegmentRuleModel(testCase[0]).matchingFunction()).toBe(testCase[1]);
}
});
+
+test('test_fromSegmentResult_with_multiple_variants', () => {
+ const segmentResults = [{ key: '1', name: 'test_segment' }];
+
+ const evaluationContext: EvaluationContext = {
+ identity: {
+ key: 'not_exist',
+ identifier: 'not_exist'
+ },
+ environment: {
+ key: 'test',
+ name: 'test'
+ },
+ features: {},
+ segments: {
+ '1': {
+ key: '1',
+ name: 'test_segment',
+ rules: [
+ {
+ type: 'ALL',
+ conditions: [
+ {
+ property: '$.identity.identifier',
+ operator: 'EQUAL',
+ value: 'test-user'
+ }
+ ]
+ }
+ ],
+ overrides: [
+ {
+ key: 'override',
+ feature_key: '1',
+ name: 'multivariate_feature',
+ enabled: true,
+ value: 'default_value',
+ priority: 1,
+ variants: [
+ { id: 1, value: 'variant_a', weight: 30 },
+ { id: 2, value: 'variant_b', weight: 70 }
+ ]
+ }
+ ]
+ }
+ }
+ };
+
+ const result = SegmentModel.fromSegmentResult(segmentResults, evaluationContext);
+
+ expect(result).toHaveLength(1);
+
+ const segment = result[0];
+ expect(segment.name).toBe('test_segment');
+ expect(segment.featureStates).toHaveLength(1);
+
+ const featureState = segment.featureStates[0];
+ expect(featureState.feature.name).toBe('multivariate_feature');
+ expect(featureState.feature.type).toBe(CONSTANTS.MULTIVARIATE);
+ expect(featureState.enabled).toBe(true);
+ expect(featureState.getValue()).toBe('default_value');
+
+ // Test multivariate variants
+ expect(featureState.multivariateFeatureStateValues).toHaveLength(2);
+
+ const variant1 = featureState.multivariateFeatureStateValues[0];
+ expect(variant1.multivariateFeatureOption.value).toBe('variant_a');
+ expect(variant1.percentageAllocation).toBe(30);
+ expect(variant1.id).toBe(1);
+
+ const variant2 = featureState.multivariateFeatureStateValues[1];
+ expect(variant2.multivariateFeatureOption.value).toBe('variant_b');
+ expect(variant2.percentageAllocation).toBe(70);
+ expect(variant2.id).toBe(2);
+});
diff --git a/tests/engine/unit/utils.ts b/tests/engine/unit/utils.ts
index cdb73b2..4e89fca 100644
--- a/tests/engine/unit/utils.ts
+++ b/tests/engine/unit/utils.ts
@@ -20,7 +20,7 @@ export function segmentCondition() {
}
export function traitMatchingSegment() {
- return new TraitModel(segmentCondition().property_ as string, segmentCondition().value);
+ return new TraitModel(segmentCondition().property as string, segmentCondition().value);
}
export function organisation() {
diff --git a/tests/engine/unit/utils/utils.test.ts b/tests/engine/unit/utils/utils.test.ts
index 041adfc..15a1a30 100644
--- a/tests/engine/unit/utils/utils.test.ts
+++ b/tests/engine/unit/utils/utils.test.ts
@@ -1,11 +1,11 @@
import { randomUUID as uuidv4 } from 'node:crypto';
-import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
+import { getHashedPercentageForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
describe('getHashedPercentageForObjIds', () => {
it.each([[[12, 93]], [[uuidv4(), 99]], [[99, uuidv4()]], [[uuidv4(), uuidv4()]]])(
'returns x where 0 <= x < 100',
(objIds: (string | number)[]) => {
- let result = getHashedPercentateForObjIds(objIds);
+ let result = getHashedPercentageForObjIds(objIds);
expect(result).toBeLessThan(100);
expect(result).toBeGreaterThanOrEqual(0);
}
@@ -14,15 +14,15 @@ describe('getHashedPercentageForObjIds', () => {
it.each([[[12, 93]], [[uuidv4(), 99]], [[99, uuidv4()]], [[uuidv4(), uuidv4()]]])(
'returns the same value each time',
(objIds: (string | number)[]) => {
- let resultOne = getHashedPercentateForObjIds(objIds);
- let resultTwo = getHashedPercentateForObjIds(objIds);
+ let resultOne = getHashedPercentageForObjIds(objIds);
+ let resultTwo = getHashedPercentageForObjIds(objIds);
expect(resultOne).toEqual(resultTwo);
}
);
it('is unique for different object ids', () => {
- let resultOne = getHashedPercentateForObjIds([14, 106]);
- let resultTwo = getHashedPercentateForObjIds([53, 200]);
+ let resultOne = getHashedPercentageForObjIds([14, 106]);
+ let resultTwo = getHashedPercentageForObjIds([53, 200]);
expect(resultOne).not.toEqual(resultTwo);
});
@@ -40,7 +40,7 @@ describe('getHashedPercentageForObjIds', () => {
);
// When
- let values = objectIdPairs.map(objIds => getHashedPercentateForObjIds(objIds));
+ let values = objectIdPairs.map(objIds => getHashedPercentageForObjIds(objIds));
// Then
for (let i = 0; i++; i < numTestBuckets) {