Compare commits

...

81 Commits

Author SHA1 Message Date
OpenFeature Bot ee236396cd
chore(main): release react-sdk 1.0.1 (#1235)
🤖 I have created a release *beep* *boop*
---


##
[1.0.1](https://github.com/open-feature/js-sdk/compare/react-sdk-v1.0.0...react-sdk-v1.0.1)
(2025-08-18)


### 🐛 Bug Fixes

* **react:** re-evaluate flags on re-render to detect silent provider …
([#1226](https://github.com/open-feature/js-sdk/issues/1226))
([3105595](3105595926))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-08-18 16:26:25 +00:00
Michael Beemer 9aab3d053b
chore: remove hardcoded react v1
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-08-18 11:56:09 -04:00
Michael Beemer 3105595926
fix(react): re-evaluate flags on re-render to detect silent provider … (#1226)
## This PR

- Added `useEffect` that runs on re-render to re-evaluate the flag value
- Only updates state if the resolved value actually changed (using
`isEqual` comparison)
- Used lazy initialization for `useState` to avoid redundant initial
evaluation
  - Added `useCallback` memoization for event handlers
  - Fixed `AbortController` scope issue

### Notes

This resolves a subtle issue where the provider state may update without
emitting a change event, leading to confusing results. The `useFlag`
hook sets the initial evaluated value in a `useState`. Since this wasn't
in a closure, this evaluation happened any time the component using the
hook rerendered but the result was essentially ignored. Adding a logging
hook shows that the current results but since this evaluation was made
in an existing `useState`, the result had no effect.

This resolves a subtle issue where the provider state may update without
emitting a change event, leading to stale flag values being displayed.

The `useFlag` hook was evaluating the flag on every re-render (as part
of the `useState` initialization), but because `useState` only uses its
initial value on the first render, these subsequent evaluations were
being discarded. This meant that even though the hook was fetching the
correct updated value from the provider on each re-render, it was
throwing that value away and continuing to display the stale cached
value.

Adding a logging hook would show the correct evaluation happening
(proving the provider had the updated value), but the UI would remain
stuck with the old value because the `useState` was ignoring the
re-evaluated result.

The fix ensures that these re-evaluations on re-render actually update
the component's state when the resolved value changes.

The key insight is that the evaluation WAS happening on every re-render
(due to how useState works), but React was discarding the result. Your
fix makes those evaluations actually matter by checking if the value
changed and updating state accordingly.

Original thread:
https://cloud-native.slack.com/archives/C06E4DE6S07/p1754508917397519

### How to test

I created a test that reproduced the issue, and it failed. I then
implemented the changes and verified that the test passed.

---------

Signed-off-by: Developer <developer@example.com>
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Developer <developer@example.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-08-18 15:08:51 +00:00
renovate[bot] 1dbbd5161b
chore(deps): update dependency rxjs to v7.8.2 (#1230)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [rxjs](https://rxjs.dev)
([source](https://redirect.github.com/reactivex/rxjs)) | [`7.8.1` ->
`7.8.2`](https://renovatebot.com/diffs/npm/rxjs/7.8.1/7.8.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/rxjs/7.8.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/rxjs/7.8.1/7.8.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>reactivex/rxjs (rxjs)</summary>

###
[`v7.8.2`](https://redirect.github.com/reactivex/rxjs/compare/7.8.1...7.8.2)

[Compare
Source](https://redirect.github.com/reactivex/rxjs/compare/7.8.1...7.8.2)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled because a matching PR was automerged
previously.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 09:06:37 -04:00
renovate[bot] 2287083de7
chore(deps): update dependency shx to ^0.4.0 (#1213)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [shx](https://redirect.github.com/shelljs/shx) | [`^0.3.4` ->
`^0.4.0`](https://renovatebot.com/diffs/npm/shx/0.3.4/0.4.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/shx/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/shx/0.3.4/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>shelljs/shx (shx)</summary>

###
[`v0.4.0`](https://redirect.github.com/shelljs/shx/releases/tag/v0.4.0)

[Compare
Source](https://redirect.github.com/shelljs/shx/compare/v0.3.4...v0.4.0)

####  Highlighted changes

- This is based on ShellJS v0.9! This means we bumped the minimum node
version to >= v18.
- Small bash compatibility change to `shx sed`. Now if you invoke `shx
sed -i`, this will not print any output to stdout (this is for
consistency with unix `sed`). Using `shx sed` without the `-i` flag will
still print to stdout as before.

#### What's Changed

- chore: remove codecov devDependency by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/198](https://redirect.github.com/shelljs/shx/pull/198)
- chore(ci): run tests up to node v16 by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/197](https://redirect.github.com/shelljs/shx/pull/197)
- chore: rename master -> main by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/204](https://redirect.github.com/shelljs/shx/pull/204)
- chore: update deps by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/205](https://redirect.github.com/shelljs/shx/pull/205)
- chore: update CI to include v18 by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/206](https://redirect.github.com/shelljs/shx/pull/206)
- fix(lint): fixes import order lint warnings by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/215](https://redirect.github.com/shelljs/shx/pull/215)
- doc: highlight globs and emphasize double quotes by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/214](https://redirect.github.com/shelljs/shx/pull/214)
- chore: update CI to test against node v20 by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/212](https://redirect.github.com/shelljs/shx/pull/212)
- docs: change GitHub Actions README badge by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/216](https://redirect.github.com/shelljs/shx/pull/216)
- chore: keep node < 16 around longer by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/219](https://redirect.github.com/shelljs/shx/pull/219)
- Bump GitHub workflow action to latest version by
[@&#8203;deining](https://redirect.github.com/deining) in
[https://github.com/shelljs/shx/pull/220](https://redirect.github.com/shelljs/shx/pull/220)
- Update minimist for CVE-2021-44906 by
[@&#8203;tomhaines432](https://redirect.github.com/tomhaines432) in
[https://github.com/shelljs/shx/pull/218](https://redirect.github.com/shelljs/shx/pull/218)
- chore: add codecov token by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/222](https://redirect.github.com/shelljs/shx/pull/222)
- chore: remove unsupported node configs from CI by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/221](https://redirect.github.com/shelljs/shx/pull/221)
- chore: switch to codecov v4 by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/223](https://redirect.github.com/shelljs/shx/pull/223)
- chore(dependencies): update js-yaml by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/224](https://redirect.github.com/shelljs/shx/pull/224)
- doc: Fix typo in README by
[@&#8203;mpaw](https://redirect.github.com/mpaw) in
[https://github.com/shelljs/shx/pull/227](https://redirect.github.com/shelljs/shx/pull/227)
- chore: update shelljs and drop old node support by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/228](https://redirect.github.com/shelljs/shx/pull/228)
- chore: drop non-LTS node versions by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/229](https://redirect.github.com/shelljs/shx/pull/229)
- chore: drop some dependencies and simplify by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/230](https://redirect.github.com/shelljs/shx/pull/230)
- chore: update dependencies by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/231](https://redirect.github.com/shelljs/shx/pull/231)
- fix: add back ShellJS version in --version by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/232](https://redirect.github.com/shelljs/shx/pull/232)
- Adding a global --negate flag by
[@&#8203;SoTrx](https://redirect.github.com/SoTrx) in
[https://github.com/shelljs/shx/pull/189](https://redirect.github.com/shelljs/shx/pull/189)
- refactor: code cleanup for the --negate flag by
[@&#8203;nfischer](https://redirect.github.com/nfischer) in
[https://github.com/shelljs/shx/pull/233](https://redirect.github.com/shelljs/shx/pull/233)

#### New Contributors

- [@&#8203;deining](https://redirect.github.com/deining) made their
first contribution in
[https://github.com/shelljs/shx/pull/220](https://redirect.github.com/shelljs/shx/pull/220)
- [@&#8203;tomhaines432](https://redirect.github.com/tomhaines432) made
their first contribution in
[https://github.com/shelljs/shx/pull/218](https://redirect.github.com/shelljs/shx/pull/218)
- [@&#8203;mpaw](https://redirect.github.com/mpaw) made their first
contribution in
[https://github.com/shelljs/shx/pull/227](https://redirect.github.com/shelljs/shx/pull/227)
- [@&#8203;SoTrx](https://redirect.github.com/SoTrx) made their first
contribution in
[https://github.com/shelljs/shx/pull/189](https://redirect.github.com/shelljs/shx/pull/189)

**Full Changelog**:
https://github.com/shelljs/shx/compare/v0.3.4...v0.4.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40MC4zIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 09:06:26 -04:00
OpenFeature Bot 04139affcb
chore(main): release server-sdk 1.19.0 (#1181)
🤖 I have created a release *beep* *boop*
---


##
[1.19.0](https://github.com/open-feature/js-sdk/compare/server-sdk-v1.18.0...server-sdk-v1.19.0)
(2025-08-14)


###  New Features

* add evaluation-scoped hook data
([#1216](https://github.com/open-feature/js-sdk/issues/1216))
([07af3a9](07af3a9eda))


### 🐛 Bug Fixes

* update core dep
([#1228](https://github.com/open-feature/js-sdk/issues/1228))
([845d24c](845d24c5fe))


### 🧹 Chore

* update node to v20+
([#1203](https://github.com/open-feature/js-sdk/issues/1203))
([1f33453](1f33453c23))


### 📚 Documentation

* Clarify the behavior of setProviderAndWait
([#1180](https://github.com/open-feature/js-sdk/issues/1180))
([4fe8d87](4fe8d87a2e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-08-14 15:57:34 -04:00
OpenFeature Bot 1e330a2e13
chore(main): release web-sdk 1.6.1 (#1229)
🤖 I have created a release *beep* *boop*
---


##
[1.6.1](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.6.0...web-sdk-v1.6.1)
(2025-08-14)


### 🐛 Bug Fixes

* update core dep
([#1228](https://github.com/open-feature/js-sdk/issues/1228))
([845d24c](845d24c5fe))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-08-14 13:37:46 -04:00
Todd Baert 7df8a8eedc
chore: engine version in root
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-08-14 13:28:59 -04:00
Todd Baert 3b3515b601 chore: fix lockfile again
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-08-14 13:18:01 -04:00
Todd Baert 63a1feb213 chore: fix lockfile
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-08-14 13:10:38 -04:00
Todd Baert 845d24c5fe
fix: update core dep (#1228)
As astutely pointed out by @JasperJuergensen in
https://github.com/open-feature/js-sdk/issues/1227, we added API surface
in core which we use in the latest web, but did't accordingly update the
min version of core in web.

(I also updated the min core version in server, just because).

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-08-14 13:03:20 -04:00
OpenFeature Bot dc1970e717
chore(main): release web-sdk 1.6.0 (#1182)
🤖 I have created a release *beep* *boop*
---


##
[1.6.0](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.5.0...web-sdk-v1.6.0)
(2025-08-12)


###  New Features

* add evaluation-scoped hook data
([#1216](https://github.com/open-feature/js-sdk/issues/1216))
([07af3a9](07af3a9eda))
* **web-global-build:** publish web packages to unpkg and jsdelivr
([#1225](https://github.com/open-feature/js-sdk/issues/1225))
([40a512e](40a512e212))


### 📚 Documentation

* Clarify the behavior of setProviderAndWait
([#1180](https://github.com/open-feature/js-sdk/issues/1180))
([4fe8d87](4fe8d87a2e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-08-12 23:27:08 +02:00
OpenFeature Bot e6e5ff3edf
chore(main): release core 1.9.0 (#1219)
🤖 I have created a release *beep* *boop*
---


##
[1.9.0](https://github.com/open-feature/js-sdk/compare/core-v1.8.1...core-v1.9.0)
(2025-08-10)


###  New Features

* add evaluation-scoped hook data
([#1216](https://github.com/open-feature/js-sdk/issues/1216))
([07af3a9](07af3a9eda))
* support Angular 20
([#1220](https://github.com/open-feature/js-sdk/issues/1220))
([aa232a9](aa232a9d6a))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-08-12 21:08:51 +00:00
Patryk Zdunowski 40a512e212
feat(web-global-build): impl (#1225)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

## This PR

- Implements a global (IIFE) build for the web.
- Adds support for global distribution via CDN by including `unpkg` and
`jsdelivr` fields in `package.json`.

### Notes

- The global build outputs to `dist/global/index.js`.
- The global bundle is exposed under the global name `OpenFeature`.

### Follow-up Tasks

- Consider adding automated tests or validation for the global bundle.
- If this bundle should be included in a release checklist or CI, link
the appropriate issue here.

### How to test

1. Run `npm run build` and ensure `dist/global/index.js` is generated.
2. Serve the file locally or upload to a CDN and verify that
`window.OpenFeature` is available in the browser console.

### Motivation
Trying to load OpenFeature as a first dependency before browser will
resolve any modules.

---------

Signed-off-by: Patryk Zdunowski <zdunekhere@gmail.com>
2025-08-10 14:24:01 +00:00
OpenFeature Bot 28850b7f6d
chore(main): release angular-sdk 0.0.16 (#1221) 2025-07-25 21:18:43 +02:00
Lukas Reining aa232a9d6a
feat: support Angular 20 (#1220)
Adds Angular 20 support.

This also remove the custom jest setup as it was not compatible with
Angular 20 and uses builtin support for vitest in Angular 20.
For this, Angular has been removed from the jest setup and the pipeline
runs it separately now.

Fixes #1206

---------

Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
2025-07-25 18:48:38 +02:00
Michael Beemer 07af3a9eda
feat: add evaluation-scoped hook data (#1216)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-07-18 15:08:01 -04:00
OpenFeature Bot d8bd93b6d5
chore(main): release core 1.8.1 (#1208)
🤖 I have created a release *beep* *boop*
---


##
[1.8.1](https://github.com/open-feature/js-sdk/compare/core-v1.8.0...core-v1.8.1)
(2025-06-04)


### 🔄 Refactoring

* **telemetry:** update telemetry attributes and remove unused
evaluation data
([#1189](https://github.com/open-feature/js-sdk/issues/1189))
([3e6bcae](3e6bcaef0b))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-06-26 20:15:03 +00:00
renovate[bot] 1b3ac12a35
chore(deps): update dependency eslint-plugin-jsdoc to v50.7.1 (#1211)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[eslint-plugin-jsdoc](https://redirect.github.com/gajus/eslint-plugin-jsdoc)
| [`50.7.0` ->
`50.7.1`](https://renovatebot.com/diffs/npm/eslint-plugin-jsdoc/50.7.0/50.7.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-jsdoc/50.7.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-plugin-jsdoc/50.7.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-plugin-jsdoc/50.7.0/50.7.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-jsdoc/50.7.0/50.7.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>gajus/eslint-plugin-jsdoc (eslint-plugin-jsdoc)</summary>

###
[`v50.7.1`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.7.1)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.7.0...v50.7.1)

##### Bug Fixes

- revert are-docs-informative upgrade; fixes
[#&#8203;1393](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1393)
([#&#8203;1394](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1394))
([99cb131](99cb131ee4))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40MC4zIiwidXBkYXRlZEluVmVyIjoiNDAuNDAuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 18:54:59 +00:00
Todd Baert d3bca54979
chore: update workflows to node 20 (#1209)
I missed a few workflows when I updated the node version. We should
update these for consistency.

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-04 14:54:45 -04:00
renovate[bot] baf0dfbd5b
chore(deps): update dependency rollup-plugin-dts to v6.2.1 (#1196)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[rollup-plugin-dts](https://redirect.github.com/Swatinem/rollup-plugin-dts)
| [`6.1.1` ->
`6.2.1`](https://renovatebot.com/diffs/npm/rollup-plugin-dts/6.1.1/6.2.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/rollup-plugin-dts/6.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/rollup-plugin-dts/6.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/rollup-plugin-dts/6.1.1/6.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/rollup-plugin-dts/6.1.1/6.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>Swatinem/rollup-plugin-dts (rollup-plugin-dts)</summary>

###
[`v6.2.1`](https://redirect.github.com/Swatinem/rollup-plugin-dts/compare/v6.2.0...v6.2.1)

[Compare
Source](https://redirect.github.com/Swatinem/rollup-plugin-dts/compare/v6.2.0...v6.2.1)

###
[`v6.2.0`](https://redirect.github.com/Swatinem/rollup-plugin-dts/blob/HEAD/CHANGELOG.md#620)

[Compare
Source](https://redirect.github.com/Swatinem/rollup-plugin-dts/compare/v6.1.1...v6.2.0)

**Features**:

-   Support importing json modules
-   Return Source Map Differences to Rollup

**Fixes**:

- Unable to find the program when entry points exist intersection
dependency
-   Create program for `imports` input file
-   Force emit types even when there's errors
-   Preserve type members of namespace in re-exported module
-   Imports and exports follows type-only
-   Import "local types" from "global modules"
-   Correctly restore type-only import/export names

**Thank you**:

Features, fixes and improvements in this release have been contributed
by:

-   [@&#8203;NWYLZW](https://redirect.github.com/NWYLZW)
-   [@&#8203;castarco](https://redirect.github.com/castarco)
-   [@&#8203;hyrious](https://redirect.github.com/hyrious)
-   [@&#8203;andersk](https://redirect.github.com/andersk)
-   [@&#8203;kricsleo](https://redirect.github.com/kricsleo)
-   [@&#8203;alan-agius4](https://redirect.github.com/alan-agius4)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4xMS4xOCIsInVwZGF0ZWRJblZlciI6IjQwLjExLjE4IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 16:34:40 +00:00
renovate[bot] 2fd944d317
chore(deps): update dependency eslint-plugin-jsdoc to v50.7.0 (#1199)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[eslint-plugin-jsdoc](https://redirect.github.com/gajus/eslint-plugin-jsdoc)
| [`50.6.14` ->
`50.7.0`](https://renovatebot.com/diffs/npm/eslint-plugin-jsdoc/50.6.14/50.7.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-jsdoc/50.7.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-plugin-jsdoc/50.7.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-plugin-jsdoc/50.6.14/50.7.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-jsdoc/50.6.14/50.7.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>gajus/eslint-plugin-jsdoc (eslint-plugin-jsdoc)</summary>

###
[`v50.7.0`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.7.0)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.17...v50.7.0)

##### Features

- **`getJsdocProcessorPlugin`:** add `allowedLanguagesToProcess` option
([#&#8203;1392](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1392))
([0adbf43](0adbf43b6b))

###
[`v50.6.17`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.17)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.16...v50.6.17)

##### Bug Fixes

- **`require-param`:** update jsdoccomment to support exported
TSFunctionType type; fixes
[#&#8203;1386](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1386)
([#&#8203;1389](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1389))
([e26a11a](e26a11a399))

###
[`v50.6.16`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.16)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.15...v50.6.16)

##### Bug Fixes

- **`valid-types`:** fix parsing of expressions like
`[@returns](https://redirect.github.com/returns)
{[@&#8203;link](https://redirect.github.com/link) SomeType}`; fixes
[#&#8203;1381](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1381)
([#&#8203;1382](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1382))
([2bd7242](2bd7242901))

###
[`v50.6.15`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.15)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.14...v50.6.15)

##### Bug Fixes

- **`no-undefined-types`:** avoid eslint 8 error; fixes
[#&#8203;1387](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1387)
([#&#8203;1388](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1388))
([1bef636](1bef63677e))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4xNi4wIiwidXBkYXRlZEluVmVyIjoiNDAuMzMuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 16:34:07 +00:00
renovate[bot] 3598b5e822
chore(deps): update dependency rollup to v4.41.1 (#1205)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [rollup](https://rollupjs.org/)
([source](https://redirect.github.com/rollup/rollup)) | [`4.40.2` ->
`4.41.1`](https://renovatebot.com/diffs/npm/rollup/4.40.2/4.41.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/rollup/4.41.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/rollup/4.41.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/rollup/4.40.2/4.41.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/rollup/4.40.2/4.41.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>rollup/rollup (rollup)</summary>

###
[`v4.41.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4411)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.41.0...v4.41.1)

*2025-05-24*

##### Bug Fixes

- If a plugin calls `this.resolve` with `skipSelf: true`, subsequent
calls when handling this by the same plugin with same parameters will
return `null` to avoid infinite recursions
([#&#8203;5945](https://redirect.github.com/rollup/rollup/issues/5945))

##### Pull Requests

- [#&#8203;5945](https://redirect.github.com/rollup/rollup/pull/5945):
Avoid recursively calling a plugin's resolveId hook with same id and
importer ([@&#8203;younggglcy](https://redirect.github.com/younggglcy),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5963](https://redirect.github.com/rollup/rollup/pull/5963):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5964](https://redirect.github.com/rollup/rollup/pull/5964):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.41.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4410)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.40.2...v4.41.0)

*2025-05-18*

##### Features

- Detect named exports in more dynamic import scenarios
([#&#8203;5954](https://redirect.github.com/rollup/rollup/issues/5954))

##### Pull Requests

- [#&#8203;5949](https://redirect.github.com/rollup/rollup/pull/5949):
ci: use node 24 ([@&#8203;btea](https://redirect.github.com/btea),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5951](https://redirect.github.com/rollup/rollup/pull/5951):
chore(deps): update dependency pretty-bytes to v7
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5952](https://redirect.github.com/rollup/rollup/pull/5952):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5953](https://redirect.github.com/rollup/rollup/pull/5953):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5954](https://redirect.github.com/rollup/rollup/pull/5954):
enhance tree-shaking for dynamic imports
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi),
[@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5957](https://redirect.github.com/rollup/rollup/pull/5957):
chore(deps): update dependency lint-staged to v16
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5958](https://redirect.github.com/rollup/rollup/pull/5958):
fix(deps): update rust crate swc_compiler_base to v20
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5959](https://redirect.github.com/rollup/rollup/pull/5959):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5960](https://redirect.github.com/rollup/rollup/pull/5960):
Use spawn to run CLI tests
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4zMy42IiwidXBkYXRlZEluVmVyIjoiNDAuMzMuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 16:32:59 +00:00
renovate[bot] 52ed31ad96
chore(deps): update dependency esbuild to v0.25.5 (#1204)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [esbuild](https://redirect.github.com/evanw/esbuild) | [`0.25.4` ->
`0.25.5`](https://renovatebot.com/diffs/npm/esbuild/0.25.4/0.25.5) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/esbuild/0.25.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/esbuild/0.25.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/esbuild/0.25.4/0.25.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/esbuild/0.25.4/0.25.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>evanw/esbuild (esbuild)</summary>

###
[`v0.25.5`](https://redirect.github.com/evanw/esbuild/blob/HEAD/CHANGELOG.md#0255)

[Compare
Source](https://redirect.github.com/evanw/esbuild/compare/v0.25.4...v0.25.5)

- Fix a regression with `browser` in `package.json`
([#&#8203;4187](https://redirect.github.com/evanw/esbuild/issues/4187))

The fix to
[#&#8203;4144](https://redirect.github.com/evanw/esbuild/issues/4144) in
version 0.25.3 introduced a regression that caused `browser` overrides
specified in `package.json` to fail to override relative path names that
end in a trailing slash. That behavior change affected the
`axios@0.30.0` package. This regression has been fixed, and now has test
coverage.

- Add support for certain keywords as TypeScript tuple labels
([#&#8203;4192](https://redirect.github.com/evanw/esbuild/issues/4192))

Previously esbuild could incorrectly fail to parse certain keywords as
TypeScript tuple labels that are parsed by the official TypeScript
compiler if they were followed by a `?` modifier. These labels included
`function`, `import`, `infer`, `new`, `readonly`, and `typeof`. With
this release, these keywords will now be parsed correctly. Here's an
example of some affected code:

    ```ts
    type Foo = [
      value: any,
      readonly?: boolean, // This is now parsed correctly
    ]
    ```

- Add CSS prefixes for the `stretch` sizing value
([#&#8203;4184](https://redirect.github.com/evanw/esbuild/issues/4184))

This release adds support for prefixing CSS declarations such as `div {
width: stretch }`. That CSS is now transformed into this depending on
what the `--target=` setting includes:

    ```css
    div {
      width: -webkit-fill-available;
      width: -moz-available;
      width: stretch;
    }
    ```

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4zMy42IiwidXBkYXRlZEluVmVyIjoiNDAuMzMuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 12:33:54 -04:00
Michael Beemer 3e6bcaef0b
refactor(telemetry): update telemetry attributes and remove unused evaluation data (#1189)
## This PR

- update the evaluation event to match the latest OTel semcon

### Related Issues

Fixes #1188

### Notes

Update the createEvaluationEvent to match the latest OpenTelemetry spec.
Unforutnately, this is a breaking change. I'll denote this in the
release notes but not in the library version. That's because we're
following the experimental OTel spec and the method itself is currently
undocumented except from the generated JS Doc.

It's also worth noting that OTel JS SDK event API doesn't currently
support object as attribute values. @dyladan will update the OTel SDK.

### Follow-up Tasks

Update the ToggleShop.

### How to test

`npm run test`

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-06-03 13:51:28 +00:00
renovate[bot] 66a3ce05af
chore(deps): update dependency @types/node to v22.15.23 (#1198)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`22.15.17` ->
`22.15.23`](https://renovatebot.com/diffs/npm/@types%2fnode/22.15.17/22.15.23)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/22.15.23?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fnode/22.15.23?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fnode/22.15.17/22.15.23?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/22.15.17/22.15.23?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4xNi4wIiwidXBkYXRlZEluVmVyIjoiNDAuMzMuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 20:46:03 +00:00
renovate[bot] 1cb4a506aa
chore(deps): update dependency prettier to v3.5.3 (#1195)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [prettier](https://prettier.io)
([source](https://redirect.github.com/prettier/prettier)) | [`3.4.2` ->
`3.5.3`](https://renovatebot.com/diffs/npm/prettier/3.4.2/3.5.3) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/prettier/3.5.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/prettier/3.5.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/prettier/3.4.2/3.5.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/prettier/3.4.2/3.5.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>prettier/prettier (prettier)</summary>

###
[`v3.5.3`](https://redirect.github.com/prettier/prettier/compare/3.5.2...b51ba9d46765bcfab714ebca982bd04ad25ae562)

[Compare
Source](https://redirect.github.com/prettier/prettier/compare/3.5.2...3.5.3)

###
[`v3.5.2`](https://redirect.github.com/prettier/prettier/blob/HEAD/CHANGELOG.md#352)

[Compare
Source](https://redirect.github.com/prettier/prettier/compare/3.5.1...3.5.2)


[diff](https://redirect.github.com/prettier/prettier/compare/3.5.1...3.5.2)

##### Remove `module-sync` condition
([#&#8203;17156](https://redirect.github.com/prettier/prettier/pull/17156)
by [@&#8203;fisker](https://redirect.github.com/fisker))

In Prettier 3.5.0, [we added `module-sync` condition to
`package.json`](https://prettier.io/blog/2025/02/09/3.5.0#use-esm-entrypoint-for-requireesm-16958-by-tats-u),
so that `require("prettier")` can use ESM version, but turns out it
doesn't work if CommonJS and ESM plugins both imports builtin plugins.
To solve this problem, we decide simply remove the `module-sync`
condition, so `require("prettier")` will still use the CommonJS version,
we'll revisit until `require(ESM)` feature is more stable.

###
[`v3.5.1`](https://redirect.github.com/prettier/prettier/blob/HEAD/CHANGELOG.md#351)

[Compare
Source](https://redirect.github.com/prettier/prettier/compare/3.5.0...3.5.1)


[diff](https://redirect.github.com/prettier/prettier/compare/3.5.0...3.5.1)

##### Fix CLI crash when cache for old version exists
([#&#8203;17100](https://redirect.github.com/prettier/prettier/pull/17100)
by [@&#8203;sosukesuzuki](https://redirect.github.com/sosukesuzuki))

Prettier 3.5 uses a different cache format than previous versions,
Prettier 3.5.0 crashes when reading existing cache file, Prettier 3.5.1
fixed the problem.

##### Support dockercompose and github-actions-workflow in VSCode
([#&#8203;17101](https://redirect.github.com/prettier/prettier/pull/17101)
by [@&#8203;remcohaszing](https://redirect.github.com/remcohaszing))

Prettier now supports the `dockercompose` and `github-actions-workflow`
languages in Visual Studio Code.

###
[`v3.5.0`](https://redirect.github.com/prettier/prettier/blob/HEAD/CHANGELOG.md#350)

[Compare
Source](https://redirect.github.com/prettier/prettier/compare/3.4.2...3.5.0)


[diff](https://redirect.github.com/prettier/prettier/compare/3.4.2...3.5.0)

🔗 [Release Notes](https://prettier.io/blog/2025/02/09/3.5.0.html)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4xMS4xOCIsInVwZGF0ZWRJblZlciI6IjQwLjExLjE4IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 20:45:45 +00:00
Todd Baert 1f33453c23
chore: update node to v20+ (#1203)
node v18 is now EOL

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-05-29 19:38:31 +00:00
OpenFeature Bot 2838d6afce
chore(main): release nestjs-sdk 0.2.5 (#1183)
🤖 I have created a release *beep* *boop*
---


##
[0.2.5](https://github.com/open-feature/js-sdk/compare/nestjs-sdk-v0.2.4...nestjs-sdk-v0.2.5)
(2025-05-27)


###  New Features

* adds RequireFlagsEnabled decorator
([#1159](https://github.com/open-feature/js-sdk/issues/1159))
([59b8fe9](59b8fe904f))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-05-27 22:08:11 +00:00
Lukas Reining aadc7a6636
fix: environment for npm publish (#1202)
Adds the environment for NPM publish step of the pipeline to the correct
job.

Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
2025-05-27 23:51:57 +02:00
OpenFeature Bot dae36bba1f
chore(main): release angular-sdk 0.0.15 (#1201)
🤖 I have created a release *beep* *boop*
---


##
[0.0.15](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.14...angular-sdk-v0.0.15)
(2025-05-27)


### 🐛 Bug Fixes

* **angular:** update docs
([#1200](https://github.com/open-feature/js-sdk/issues/1200))
([b6ea588](b6ea5884f2))


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @openfeature/web-sdk bumped from * to 1.5.1

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-05-27 22:56:32 +02:00
Lukas Reining b6ea5884f2
fix(angular): update docs (#1200)
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
2025-05-27 22:50:11 +02:00
renovate[bot] affdecb619
chore(deps): update dependency jest-preset-angular to v14.5.5 (#1192)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [jest-preset-angular](https://thymikee.github.io/jest-preset-angular)
([source](https://redirect.github.com/thymikee/jest-preset-angular)) |
[`14.5.1` ->
`14.5.5`](https://renovatebot.com/diffs/npm/jest-preset-angular/14.5.1/14.5.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/jest-preset-angular/14.5.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/jest-preset-angular/14.5.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/jest-preset-angular/14.5.1/14.5.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jest-preset-angular/14.5.1/14.5.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>thymikee/jest-preset-angular (jest-preset-angular)</summary>

###
[`v14.5.5`](https://redirect.github.com/thymikee/jest-preset-angular/blob/HEAD/CHANGELOG.md#1455-2025-04-15)

[Compare
Source](https://redirect.github.com/thymikee/jest-preset-angular/compare/v14.5.4...v14.5.5)

##### Bug Fixes

- fix: allow name exports for `presets` subpath
([9100baf](https://redirect.github.com/thymikee/jest-preset-angular/commit/9100baf))

###
[`v14.5.4`](https://redirect.github.com/thymikee/jest-preset-angular/blob/HEAD/CHANGELOG.md#1454-2025-03-31)

[Compare
Source](https://redirect.github.com/thymikee/jest-preset-angular/compare/v14.5.3...v14.5.4)

##### Bug Fixes

- fix: warn when using both `isolatedModules` and
`emitDecoratorMetadata`
([#&#8203;3029](https://redirect.github.com/thymikee/jest-preset-angular/issues/3029))
([51db8f4](https://redirect.github.com/thymikee/jest-preset-angular/commit/51db8f4)),
closes
[#&#8203;3029](https://redirect.github.com/thymikee/jest-preset-angular/issues/3029)
- update dependency ts-jest to v29.3.0
([1d8415d](https://redirect.github.com/thymikee/jest-preset-angular/commit/1d8415d))

###
[`v14.5.3`](https://redirect.github.com/thymikee/jest-preset-angular/blob/HEAD/CHANGELOG.md#1453-2025-02-24)

[Compare
Source](https://redirect.github.com/thymikee/jest-preset-angular/compare/v14.5.2...v14.5.3)

##### Bug Fixes

- build: update bundle `jit_transform.js`, closes
[#&#8203;2979](https://redirect.github.com/thymikee/jest-preset-angular/issues/2979)

###
[`v14.5.2`](https://redirect.github.com/thymikee/jest-preset-angular/blob/HEAD/CHANGELOG.md#1452-2025-02-21)

[Compare
Source](https://redirect.github.com/thymikee/jest-preset-angular/compare/v14.5.1...v14.5.2)

##### Bug Fixes

- fix: transform `js` ESM file from `node_modules`
([b2b3934](https://redirect.github.com/thymikee/jest-preset-angular/commit/b2b3934)),
closes
[#&#8203;2913](https://redirect.github.com/thymikee/jest-preset-angular/issues/2913)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4xMS4xOCIsInVwZGF0ZWRJblZlciI6IjQwLjExLjE4IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-26 17:57:01 +00:00
renovate[bot] f2121671fa
chore(deps): update dependency rxjs to v7.8.2 (#1193)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [rxjs](https://rxjs.dev)
([source](https://redirect.github.com/reactivex/rxjs)) | [`7.8.1` ->
`7.8.2`](https://renovatebot.com/diffs/npm/rxjs/7.8.1/7.8.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/rxjs/7.8.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/rxjs/7.8.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/rxjs/7.8.1/7.8.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/rxjs/7.8.1/7.8.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>reactivex/rxjs (rxjs)</summary>

###
[`v7.8.2`](https://redirect.github.com/reactivex/rxjs/compare/7.8.1...7.8.2)

[Compare
Source](https://redirect.github.com/reactivex/rxjs/compare/7.8.1...7.8.2)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4xMS4xOCIsInVwZGF0ZWRJblZlciI6IjQwLjExLjE4IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-26 17:53:11 +00:00
OpenFeature Bot 9f887a965c
chore(main): release angular-sdk 0.0.14 (#1178)
🤖 I have created a release *beep* *boop*
---


##
[0.0.14](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.13...angular-sdk-v0.0.14)
(2025-05-25)


### 🐛 Bug Fixes

* **angular:** add license and url field to package.json
([b2784f5](b2784f53b8))


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @openfeature/web-sdk bumped from * to 1.5.1

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-05-25 12:07:48 +02:00
Lukas Reining b2784f53b8 fix(angular): add license and url field to package.json
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
2025-05-25 12:00:05 +02:00
renovate[bot] 14761998bf
chore(deps): update dependency eslint-plugin-jsdoc to v50.6.14 (#1191)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[eslint-plugin-jsdoc](https://redirect.github.com/gajus/eslint-plugin-jsdoc)
| [`50.6.3` ->
`50.6.14`](https://renovatebot.com/diffs/npm/eslint-plugin-jsdoc/50.6.3/50.6.14)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-jsdoc/50.6.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-plugin-jsdoc/50.6.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-plugin-jsdoc/50.6.3/50.6.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-jsdoc/50.6.3/50.6.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>gajus/eslint-plugin-jsdoc (eslint-plugin-jsdoc)</summary>

###
[`v50.6.14`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.14)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.13...v50.6.14)

##### Bug Fixes

- **lines-before-block:** Switch to a whitelist of punctuators
([#&#8203;1385](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1385))
([0a30832](0a30832b41))

###
[`v50.6.13`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.13)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.12...v50.6.13)

##### Bug Fixes

- **`lines-before-block`:** Only trigger after ';', '}', '|', and '&'
([#&#8203;1383](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1383))
([19fa3dc](19fa3dcb32)),
closes
[#&#8203;1379](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1379)
[#&#8203;1343](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1343)

###
[`v50.6.12`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.12)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.11...v50.6.12)

##### Bug Fixes

- **`no-undefined-types`:** workaround `parse-imports-exports` bug in
handling trailing whitespace; fixes
[#&#8203;1373](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1373)
([#&#8203;1384](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1384))
([f32989c](f32989c259))

###
[`v50.6.11`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.11)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.10...v50.6.11)

##### Bug Fixes

- **`check-values`:** workaround `parse-imports-exports` bug in handling
trailing whitespace; fixes
[#&#8203;1373](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1373)
([#&#8203;1374](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1374))
([65b0dc0](65b0dc0f58))

###
[`v50.6.10`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.10)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.9...v50.6.10)

##### Bug Fixes

- **`check-values`, `no-undefined-types`:** avoid need for worker; fixes
[#&#8203;1371](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1371)
([#&#8203;1372](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1372))
([6d5c9fb](6d5c9fb650))

###
[`v50.6.9`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.9)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.8...v50.6.9)

##### Reverts

- Revert "refactor: replace `synckit` with `make-synchronized`
([#&#8203;1366](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1366))"
([#&#8203;1367](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1367))
([771eadf](771eadfa44))

###
[`v50.6.8`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.8)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.7...v50.6.8)

##### Bug Fixes

- add missing config type(s)
([#&#8203;1365](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1365))
([ed62262](ed622628fc))

###
[`v50.6.7`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.7)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.6...v50.6.7)

##### Bug Fixes

- **no-undefined-types:** allow any available identifier; fixes
[#&#8203;178](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/178),[#&#8203;1342](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1342)
([1c38930](1c38930dd1))

###
[`v50.6.6`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.6)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.5...v50.6.6)

##### Bug Fixes

- **`empty-tags`:** allow for JSDoc-block final asterisks; fixes
[#&#8203;670](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/670)
([23b4bfa](23b4bfa1a8))

###
[`v50.6.5`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.5)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.4...v50.6.5)

##### Bug Fixes

- **`text-escaping`:** always allow content in example tags; fixes
[#&#8203;1360](https://redirect.github.com/gajus/eslint-plugin-jsdoc/issues/1360)
([6baad05](6baad05aab))

###
[`v50.6.4`](https://redirect.github.com/gajus/eslint-plugin-jsdoc/releases/tag/v50.6.4)

[Compare
Source](https://redirect.github.com/gajus/eslint-plugin-jsdoc/compare/v50.6.3...v50.6.4)

##### Bug Fixes

- force release
([9edf4b1](9edf4b18f9))
- force release
([b08733a](b08733a127))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4xMS4xOCIsInVwZGF0ZWRJblZlciI6IjQwLjExLjE4IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 16:11:58 +00:00
renovate[bot] 43b14b4869
chore(deps): update dependency @types/node to v22.15.17 (#1190)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`22.15.3` ->
`22.15.17`](https://renovatebot.com/diffs/npm/@types%2fnode/22.15.3/22.15.17)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/22.15.17?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fnode/22.15.17?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fnode/22.15.3/22.15.17?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/22.15.3/22.15.17?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4xMS4xOCIsInVwZGF0ZWRJblZlciI6IjQwLjExLjE4IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 16:10:34 +00:00
renovate[bot] cd8017d537
chore(deps): update dependency esbuild to v0.25.4 (#1185)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [esbuild](https://redirect.github.com/evanw/esbuild) | [`0.25.2` ->
`0.25.4`](https://renovatebot.com/diffs/npm/esbuild/0.25.2/0.25.4) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/esbuild/0.25.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/esbuild/0.25.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/esbuild/0.25.2/0.25.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/esbuild/0.25.2/0.25.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>evanw/esbuild (esbuild)</summary>

###
[`v0.25.4`](https://redirect.github.com/evanw/esbuild/blob/HEAD/CHANGELOG.md#0254)

[Compare
Source](https://redirect.github.com/evanw/esbuild/compare/v0.25.3...v0.25.4)

- Add simple support for CORS to esbuild's development server
([#&#8203;4125](https://redirect.github.com/evanw/esbuild/issues/4125))

Starting with version 0.25.0, esbuild's development server is no longer
configured to serve cross-origin requests. This was a deliberate change
to prevent any website you visit from accessing your running esbuild
development server. However, this change prevented (by design) certain
use cases such as "debugging in production" by having your production
website load code from `localhost` where the esbuild development server
is running.

To enable this use case, esbuild is adding a feature to allow
[Cross-Origin Resource
Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)
(a.k.a. CORS) for [simple
requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#simple_requests).
Specifically, passing your origin to the new `cors` option will now set
the `Access-Control-Allow-Origin` response header when the request has a
matching `Origin` header. Note that this currently only works for
requests that don't send a preflight `OPTIONS` request, as esbuild's
development server doesn't currently support `OPTIONS` requests.

    Some examples:

    -   **CLI:**

            esbuild --servedir=. --cors-origin=https://example.com

    -   **JS:**

        ```js
        const ctx = await esbuild.context({})
        await ctx.serve({
          servedir: '.',
          cors: {
            origin: 'https://example.com',
          },
        })
        ```

    -   **Go:**

        ```go
        ctx, _ := api.Context(api.BuildOptions{})
        ctx.Serve(api.ServeOptions{
          Servedir: ".",
          CORS: api.CORSOptions{
            Origin: []string{"https://example.com"},
          },
        })
        ```

The special origin `*` can be used to allow any origin to access
esbuild's development server. Note that this means any website you visit
will be able to read everything served by esbuild.

- Pass through invalid URLs in source maps unmodified
([#&#8203;4169](https://redirect.github.com/evanw/esbuild/issues/4169))

This fixes a regression in version 0.25.0 where `sources` in source maps
that form invalid URLs were not being passed through to the output.
Version 0.25.0 changed the interpretation of `sources` from file paths
to URLs, which means that URL parsing can now fail. Previously URLs that
couldn't be parsed were replaced with the empty string. With this
release, invalid URLs in `sources` should now be passed through
unmodified.

- Handle exports named `__proto__` in ES modules
([#&#8203;4162](https://redirect.github.com/evanw/esbuild/issues/4162),
[#&#8203;4163](https://redirect.github.com/evanw/esbuild/pull/4163))

In JavaScript, the special property name `__proto__` sets the prototype
when used inside an object literal. Previously esbuild's ESM-to-CommonJS
conversion didn't special-case the property name of exports named
`__proto__` so the exported getter accidentally became the prototype of
the object literal. It's unclear what this affects, if anything, but
it's better practice to avoid this by using a computed property name in
this case.

This fix was contributed by
[@&#8203;magic-akari](https://redirect.github.com/magic-akari).

###
[`v0.25.3`](https://redirect.github.com/evanw/esbuild/blob/HEAD/CHANGELOG.md#0253)

[Compare
Source](https://redirect.github.com/evanw/esbuild/compare/v0.25.2...v0.25.3)

- Fix lowered `async` arrow functions before `super()`
([#&#8203;4141](https://redirect.github.com/evanw/esbuild/issues/4141),
[#&#8203;4142](https://redirect.github.com/evanw/esbuild/pull/4142))

This change makes it possible to call an `async` arrow function in a
constructor before calling `super()` when targeting environments without
`async` support, as long as the function body doesn't reference `this`.
Here's an example (notice the change from `this` to `null`):

    ```js
    // Original code
    class Foo extends Object {
      constructor() {
        (async () => await foo())()
        super()
      }
    }

    // Old output (with --target=es2016)
    class Foo extends Object {
      constructor() {
        (() => __async(this, null, function* () {
          return yield foo();
        }))();
        super();
      }
    }

    // New output (with --target=es2016)
    class Foo extends Object {
      constructor() {
        (() => __async(null, null, function* () {
          return yield foo();
        }))();
        super();
      }
    }
    ```

Some background: Arrow functions with the `async` keyword are
transformed into generator functions for older language targets such as
`--target=es2016`. Since arrow functions capture `this`, the generated
code forwards `this` into the body of the generator function. However,
JavaScript class syntax forbids using `this` in a constructor before
calling `super()`, and this forwarding was problematic since previously
happened even when the function body doesn't use `this`. Starting with
this release, esbuild will now only forward `this` if it's used within
the function body.

This fix was contributed by
[@&#8203;magic-akari](https://redirect.github.com/magic-akari).

- Fix memory leak with `--watch=true`
([#&#8203;4131](https://redirect.github.com/evanw/esbuild/issues/4131),
[#&#8203;4132](https://redirect.github.com/evanw/esbuild/pull/4132))

This release fixes a memory leak with esbuild when `--watch=true` is
used instead of `--watch`. Previously using `--watch=true` caused
esbuild to continue to use more and more memory for every rebuild, but
`--watch=true` should now behave like `--watch` and not leak memory.

This bug happened because esbuild disables the garbage collector when
it's not run as a long-lived process for extra speed, but esbuild's
checks for which arguments cause esbuild to be a long-lived process
weren't updated for the new `--watch=true` style of boolean command-line
flags. This has been an issue since this boolean flag syntax was added
in version 0.14.24 in 2022. These checks are unfortunately separate from
the regular argument parser because of how esbuild's internals are
organized (the command-line interface is exposed as a separate [Go
API](https://pkg.go.dev/github.com/evanw/esbuild/pkg/cli) so you can
build your own custom esbuild CLI).

This fix was contributed by
[@&#8203;mxschmitt](https://redirect.github.com/mxschmitt).

- More concise output for repeated legal comments
([#&#8203;4139](https://redirect.github.com/evanw/esbuild/issues/4139))

Some libraries have many files and also use the same legal comment text
in all files. Previously esbuild would copy each legal comment to the
output file. Starting with this release, legal comments duplicated
across separate files will now be grouped in the output file by unique
comment content.

- Allow a custom host with the development server
([#&#8203;4110](https://redirect.github.com/evanw/esbuild/issues/4110))

With this release, you can now use a custom non-IP `host` with esbuild's
local development server (either with `--serve=` for the CLI or with the
`serve()` call for the API). This was previously possible, but was
intentionally broken in [version
0.25.0](https://redirect.github.com/evanw/esbuild/releases/v0.25.0) to
fix a security issue. This change adds the functionality back except
that it's now opt-in and only for a single domain name that you provide.

For example, if you add a mapping in your `/etc/hosts` file from
`local.example.com` to `127.0.0.1` and then use `esbuild
--serve=local.example.com:8000`, you will now be able to visit
http://local.example.com:8000/ in your browser and successfully connect
to esbuild's development server (doing that would previously have been
blocked by the browser). This should also work with HTTPS if it's
enabled (see esbuild's documentation for how to do that).

- Add a limit to CSS nesting expansion
([#&#8203;4114](https://redirect.github.com/evanw/esbuild/issues/4114))

With this release, esbuild will now fail with an error if there is too
much CSS nesting expansion. This can happen when nested CSS is converted
to CSS without nesting for older browsers as expanding CSS nesting is
inherently exponential due to the resulting combinatorial explosion. The
expansion limit is currently hard-coded and cannot be changed, but is
extremely unlikely to trigger for real code. It exists to prevent
esbuild from using too much time and/or memory. Here's an example:

    ```css

a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{color:red}}}}}}}}}}}}}}}}}}}}
    ```

Previously, transforming this file with `--target=safari1` took 5
seconds and generated 40mb of CSS. Trying to do that will now generate
the following error instead:

        ✘ [ERROR] CSS nesting is causing too much expansion

            example.css:1:60:
1 │
a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{a,b{color:red}}}}}}}}}}}}}}}}}}}}
╵ ^

CSS nesting expansion was terminated because a rule was generated with
65536 selectors. This limit
exists to prevent esbuild from using too much time and/or memory. Please
change your CSS to use
          fewer levels of nesting.

- Fix path resolution edge case
([#&#8203;4144](https://redirect.github.com/evanw/esbuild/issues/4144))

This fixes an edge case where esbuild's path resolution algorithm could
deviate from node's path resolution algorithm. It involves a confusing
situation where a directory shares the same file name as a file (but
without the file extension). See the linked issue for specific details.
This appears to be a case where esbuild is correctly following [node's
published resolution
algorithm](https://nodejs.org/api/modules.html#all-together) but where
node itself is doing something different. Specifically the step
`LOAD_AS_FILE` appears to be skipped when the input ends with `..`. This
release changes esbuild's behavior for this edge case to match node's
behavior.

- Update Go from 1.23.7 to 1.23.8
([#&#8203;4133](https://redirect.github.com/evanw/esbuild/issues/4133),
[#&#8203;4134](https://redirect.github.com/evanw/esbuild/pull/4134))

This should have no effect on existing code as this version change does
not change Go's operating system support. It may remove certain reports
from vulnerability scanners that detect which version of the Go compiler
esbuild uses, such as for CVE-2025-22871.

As a reminder, esbuild's development server is intended for development,
not for production, so I do not consider most networking-related
vulnerabilities in Go to be vulnerabilities in esbuild. Please do not
use esbuild's development server in production.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yNjQuMCIsInVwZGF0ZWRJblZlciI6IjQwLjExLjE4IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 12:11:42 -04:00
renovate[bot] 273af9e20b
chore(deps): update dependency @rollup/plugin-typescript to v12 (#1059)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@rollup/plugin-typescript](https://redirect.github.com/rollup/plugins/tree/master/packages/typescript/#readme)
([source](https://redirect.github.com/rollup/plugins/tree/HEAD/packages/typescript))
| [`^11.1.6` ->
`^12.0.0`](https://renovatebot.com/diffs/npm/@rollup%2fplugin-typescript/11.1.6/12.1.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@rollup%2fplugin-typescript/12.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@rollup%2fplugin-typescript/12.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@rollup%2fplugin-typescript/11.1.6/12.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@rollup%2fplugin-typescript/11.1.6/12.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>rollup/plugins (@&#8203;rollup/plugin-typescript)</summary>

###
[`v12.1.2`](https://redirect.github.com/rollup/plugins/blob/HEAD/packages/typescript/CHANGELOG.md#v1212)

*2024-12-15*

##### Bugfixes

- fix: path validation issue in validatePaths function
([#&#8203;1800](https://redirect.github.com/rollup/plugins/issues/1800))

###
[`v12.1.1`](https://redirect.github.com/rollup/plugins/blob/HEAD/packages/typescript/CHANGELOG.md#v1211)

*2024-10-16*

##### Bugfixes

- fix: allow for files to be nested in folders within outDir
([#&#8203;1783](https://redirect.github.com/rollup/plugins/issues/1783))

###
[`v12.1.0`](https://redirect.github.com/rollup/plugins/blob/HEAD/packages/typescript/CHANGELOG.md#v1210)

*2024-09-22*

##### Features

- feat: add transformers factory.
([#&#8203;1668](https://redirect.github.com/rollup/plugins/issues/1668))

###
[`v12.0.0`](https://redirect.github.com/rollup/plugins/blob/HEAD/packages/typescript/CHANGELOG.md#v1200)

*2024-09-22*

##### Breaking Changes

- fix!: correctly resolve filenames of declaration files for
`output.file`
([#&#8203;1728](https://redirect.github.com/rollup/plugins/issues/1728))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjQwLjExLjE4IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 12:10:18 -04:00
renovate[bot] 8f9b1ae34f
chore(deps): update dependency rollup to v4.40.2 (#1131)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [rollup](https://rollupjs.org/)
([source](https://redirect.github.com/rollup/rollup)) | [`4.24.2` ->
`4.40.2`](https://renovatebot.com/diffs/npm/rollup/4.24.2/4.40.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/rollup/4.40.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/rollup/4.40.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/rollup/4.24.2/4.40.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/rollup/4.24.2/4.40.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>rollup/rollup (rollup)</summary>

###
[`v4.40.2`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4402)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.40.1...v4.40.2)

*2025-05-06*

##### Bug Fixes

- Create correct IIFE/AMD/UMD bundles when using a mutable default
export
([#&#8203;5934](https://redirect.github.com/rollup/rollup/issues/5934))
- Fix execution order when using top-level await for dynamic imports
with inlineDynamicImports
([#&#8203;5937](https://redirect.github.com/rollup/rollup/issues/5937))
- Throw when the output is watched in watch mode
([#&#8203;5939](https://redirect.github.com/rollup/rollup/issues/5939))

##### Pull Requests

- [#&#8203;5934](https://redirect.github.com/rollup/rollup/pull/5934):
fix(exports): avoid "exports is not defined" `ReferenceError`
([@&#8203;dasa](https://redirect.github.com/dasa))
- [#&#8203;5937](https://redirect.github.com/rollup/rollup/pull/5937):
consider TLA imports have higher execution priority
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5939](https://redirect.github.com/rollup/rollup/pull/5939):
fix: watch mode input should not be an output subpath
([@&#8203;btea](https://redirect.github.com/btea))
- [#&#8203;5940](https://redirect.github.com/rollup/rollup/pull/5940):
chore(deps): update dependency vite to v6.3.4 \[security]
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5941](https://redirect.github.com/rollup/rollup/pull/5941):
chore(deps): update dependency eslint-plugin-unicorn to v59
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5942](https://redirect.github.com/rollup/rollup/pull/5942):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5943](https://redirect.github.com/rollup/rollup/pull/5943):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.40.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4401)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.40.0...v4.40.1)

*2025-04-28*

##### Bug Fixes

- Limit hash size for asset file names to the supported 21
([#&#8203;5921](https://redirect.github.com/rollup/rollup/issues/5921))
- Do not inline user-defined entry chunks or chunks with explicit file
name
([#&#8203;5923](https://redirect.github.com/rollup/rollup/issues/5923))
- Avoid top-level-await cycles when non-entry chunks use top-level await
([#&#8203;5930](https://redirect.github.com/rollup/rollup/issues/5930))
- Expose package.json via exports
([#&#8203;5931](https://redirect.github.com/rollup/rollup/issues/5931))

##### Pull Requests

- [#&#8203;5921](https://redirect.github.com/rollup/rollup/pull/5921):
fix(assetFileNames): reduce max hash size to 21
([@&#8203;shulaoda](https://redirect.github.com/shulaoda))
- [#&#8203;5923](https://redirect.github.com/rollup/rollup/pull/5923):
fix: generate the separate chunk for the entry module with explicated
chunk filename or name
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5926](https://redirect.github.com/rollup/rollup/pull/5926):
fix(deps): update rust crate swc_compiler_base to v18
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5927](https://redirect.github.com/rollup/rollup/pull/5927):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5928](https://redirect.github.com/rollup/rollup/pull/5928):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5930](https://redirect.github.com/rollup/rollup/pull/5930):
Avoid chunks TLA dynamic import circular when TLA dynamic import used in
non-entry modules
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5931](https://redirect.github.com/rollup/rollup/pull/5931):
chore: add new `./package.json` entry
([@&#8203;JounQin](https://redirect.github.com/JounQin),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5936](https://redirect.github.com/rollup/rollup/pull/5936):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.40.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4400)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.39.0...v4.40.0)

*2025-04-12*

##### Features

- Only show `eval` warnings on first render and only when the call is
not tree-shaken
([#&#8203;5892](https://redirect.github.com/rollup/rollup/issues/5892))
- Tree-shake non-included dynamic import members when the handler just
maps to one named export
([#&#8203;5898](https://redirect.github.com/rollup/rollup/issues/5898))

##### Bug Fixes

- Consider dynamic imports nested within top-level-awaited dynamic
import expressions to be awaited as well
([#&#8203;5900](https://redirect.github.com/rollup/rollup/issues/5900))
- Fix namespace rendering when tree-shaking is disabled
([#&#8203;5908](https://redirect.github.com/rollup/rollup/issues/5908))
- When using multiple transform hook filters, all of them need to be
satisfied together
([#&#8203;5909](https://redirect.github.com/rollup/rollup/issues/5909))

##### Pull Requests

- [#&#8203;5892](https://redirect.github.com/rollup/rollup/pull/5892):
Warn when eval or namespace calls are rendered, not when they are parsed
([@&#8203;SunsetFi](https://redirect.github.com/SunsetFi),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5898](https://redirect.github.com/rollup/rollup/pull/5898):
feat: treeshake dynamic import chained member expression
([@&#8203;privatenumber](https://redirect.github.com/privatenumber),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5900](https://redirect.github.com/rollup/rollup/pull/5900):
consider the dynamic import within a TLA call expression as a TLA import
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5904](https://redirect.github.com/rollup/rollup/pull/5904):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5905](https://redirect.github.com/rollup/rollup/pull/5905):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5908](https://redirect.github.com/rollup/rollup/pull/5908):
Fix `treeshake: false` breaking destructured namespace imports
([@&#8203;Skn0tt](https://redirect.github.com/Skn0tt))
- [#&#8203;5909](https://redirect.github.com/rollup/rollup/pull/5909):
Correct the behavior when multiple transform filter options are
specified ([@&#8203;sapphi-red](https://redirect.github.com/sapphi-red))
- [#&#8203;5915](https://redirect.github.com/rollup/rollup/pull/5915):
chore(deps): update dependency
[@&#8203;types/picomatch](https://redirect.github.com/types/picomatch)
to v4 ([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5916](https://redirect.github.com/rollup/rollup/pull/5916):
fix(deps): update rust crate swc_compiler_base to v17
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5917](https://redirect.github.com/rollup/rollup/pull/5917):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5918](https://redirect.github.com/rollup/rollup/pull/5918):
chore(deps): update dependency vite to v6.2.6 \[security]
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.39.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4390)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.38.0...v4.39.0)

*2025-04-02*

##### Features

- Do not create separate facade chunks if a chunk would contain several
entry modules that allow export extension if there are no export name
conflicts
([#&#8203;5891](https://redirect.github.com/rollup/rollup/issues/5891))

##### Bug Fixes

- Mark the `id` property as optional in the filter for the `resolveId`
hook
([#&#8203;5896](https://redirect.github.com/rollup/rollup/issues/5896))

##### Pull Requests

- [#&#8203;5891](https://redirect.github.com/rollup/rollup/pull/5891):
chunk: merge allow-extension modules
([@&#8203;wmertens](https://redirect.github.com/wmertens),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5893](https://redirect.github.com/rollup/rollup/pull/5893):
chore(deps): update dependency vite to v6.2.4 \[security]
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5896](https://redirect.github.com/rollup/rollup/pull/5896):
fix: resolveId id filter is optional
([@&#8203;sapphi-red](https://redirect.github.com/sapphi-red))

###
[`v4.38.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4380)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.37.0...v4.38.0)

*2025-03-29*

##### Features

- Support `.filter` option in `resolveId`, `load` and `transform` hooks
([#&#8203;5882](https://redirect.github.com/rollup/rollup/issues/5882))

##### Pull Requests

- [#&#8203;5882](https://redirect.github.com/rollup/rollup/pull/5882):
Add support for hook filters
([@&#8203;sapphi-red](https://redirect.github.com/sapphi-red))
- [#&#8203;5894](https://redirect.github.com/rollup/rollup/pull/5894):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5895](https://redirect.github.com/rollup/rollup/pull/5895):
chore(deps): update dependency eslint-plugin-unicorn to v58
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.37.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4370)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.36.0...v4.37.0)

*2025-03-23*

##### Features

- Support Musl Linux on Riscv64 architectures
([#&#8203;5726](https://redirect.github.com/rollup/rollup/issues/5726))
- Handles class decorators placed before the `export` keyword
([#&#8203;5871](https://redirect.github.com/rollup/rollup/issues/5871))

##### Bug Fixes

- Log Rust panic messages to the console when using the WASM build
([#&#8203;5875](https://redirect.github.com/rollup/rollup/issues/5875))

##### Pull Requests

- [#&#8203;5726](https://redirect.github.com/rollup/rollup/pull/5726):
Add support for linux riscv64 musl
([@&#8203;fossdd](https://redirect.github.com/fossdd),
[@&#8203;leso-kn](https://redirect.github.com/leso-kn))
- [#&#8203;5871](https://redirect.github.com/rollup/rollup/pull/5871):
feat: support decorators before or after export
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5875](https://redirect.github.com/rollup/rollup/pull/5875):
capture Rust panic messages and output them to the console.
([@&#8203;luyahan](https://redirect.github.com/luyahan),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5883](https://redirect.github.com/rollup/rollup/pull/5883):
Pin digest of 3rd party actions
([@&#8203;re-taro](https://redirect.github.com/re-taro))
- [#&#8203;5885](https://redirect.github.com/rollup/rollup/pull/5885):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.36.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4360)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.35.0...v4.36.0)

*2025-03-17*

##### Features

- Extend `renderDynamicImport` hook to provide information about static
dependencies of the imported module
([#&#8203;5870](https://redirect.github.com/rollup/rollup/issues/5870))
- Export several additional types used by Vite
([#&#8203;5879](https://redirect.github.com/rollup/rollup/issues/5879))

##### Bug Fixes

- Do not merge chunks if that would create a top-level await cycle
between chunks
([#&#8203;5843](https://redirect.github.com/rollup/rollup/issues/5843))

##### Pull Requests

- [#&#8203;5843](https://redirect.github.com/rollup/rollup/pull/5843):
avoiding top level await circular
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5870](https://redirect.github.com/rollup/rollup/pull/5870):
draft for extended renderDynamicImport hook
([@&#8203;iczero](https://redirect.github.com/iczero),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5876](https://redirect.github.com/rollup/rollup/pull/5876):
Update axios overrides to 1.8.2
([@&#8203;vadym-khodak](https://redirect.github.com/vadym-khodak))
- [#&#8203;5877](https://redirect.github.com/rollup/rollup/pull/5877):
chore(deps): update dependency eslint-plugin-vue to v10
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5878](https://redirect.github.com/rollup/rollup/pull/5878):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5879](https://redirect.github.com/rollup/rollup/pull/5879):
fix: export types ([@&#8203;sxzz](https://redirect.github.com/sxzz))

###
[`v4.35.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4350)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.9...v4.35.0)

*2025-03-08*

##### Features

- Pass build errors to the closeBundle hook
([#&#8203;5867](https://redirect.github.com/rollup/rollup/issues/5867))

##### Pull Requests

- [#&#8203;5852](https://redirect.github.com/rollup/rollup/pull/5852):
chore(deps): update dependency eslint-plugin-unicorn to v57
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5862](https://redirect.github.com/rollup/rollup/pull/5862):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5867](https://redirect.github.com/rollup/rollup/pull/5867):
feat(5858): make closeBundle hook receive the last error
([@&#8203;GauBen](https://redirect.github.com/GauBen))
- [#&#8203;5872](https://redirect.github.com/rollup/rollup/pull/5872):
chore(deps): update dependency builtin-modules to v5
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5873](https://redirect.github.com/rollup/rollup/pull/5873):
chore(deps): update uraimo/run-on-arch-action action to v3
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5874](https://redirect.github.com/rollup/rollup/pull/5874):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.34.9`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4349)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.8...v4.34.9)

*2025-03-01*

##### Bug Fixes

- Support JSX modes in WASM
([#&#8203;5866](https://redirect.github.com/rollup/rollup/issues/5866))
- Allow the CustomPluginOptions to be extended
([#&#8203;5850](https://redirect.github.com/rollup/rollup/issues/5850))

##### Pull Requests

- [#&#8203;5850](https://redirect.github.com/rollup/rollup/pull/5850):
Revert CustomPluginOptions to be an interface
([@&#8203;sapphi-red](https://redirect.github.com/sapphi-red),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5851](https://redirect.github.com/rollup/rollup/pull/5851):
Javascript to JavaScript
([@&#8203;dasa](https://redirect.github.com/dasa),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5853](https://redirect.github.com/rollup/rollup/pull/5853):
chore(deps): update dependency pinia to v3
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5854](https://redirect.github.com/rollup/rollup/pull/5854):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5855](https://redirect.github.com/rollup/rollup/pull/5855):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5860](https://redirect.github.com/rollup/rollup/pull/5860):
chore(deps): update dependency
[@&#8203;shikijs/vitepress-twoslash](https://redirect.github.com/shikijs/vitepress-twoslash)
to v3 ([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5861](https://redirect.github.com/rollup/rollup/pull/5861):
chore(deps): update dependency globals to v16
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5863](https://redirect.github.com/rollup/rollup/pull/5863):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5864](https://redirect.github.com/rollup/rollup/pull/5864):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5866](https://redirect.github.com/rollup/rollup/pull/5866):
Add jsx parameter to parseAsync in native.wasm.js
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.34.8`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4348)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.7...v4.34.8)

*2025-02-17*

##### Bug Fixes

- Do not make assumptions about the value of nested paths in logical
expressions if the expression cannot be simplified
([#&#8203;5846](https://redirect.github.com/rollup/rollup/issues/5846))

##### Pull Requests

- [#&#8203;5846](https://redirect.github.com/rollup/rollup/pull/5846):
return UnknownValue if the usedbranch is unkown and the path is not
empty ([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.34.7`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4347)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.6...v4.34.7)

*2025-02-14*

##### Bug Fixes

- Ensure that calls to parameters are included correctly when using
try-catch deoptimization
([#&#8203;5842](https://redirect.github.com/rollup/rollup/issues/5842))

##### Pull Requests

- [#&#8203;5840](https://redirect.github.com/rollup/rollup/pull/5840):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5842](https://redirect.github.com/rollup/rollup/pull/5842):
Fix prop inclusion with try-catch-deoptimization
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.34.6`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4346)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.5...v4.34.6)

*2025-02-07*

##### Bug Fixes

- Retain "void 0" in the output for smaller output and fewer surprises
([#&#8203;5838](https://redirect.github.com/rollup/rollup/issues/5838))

##### Pull Requests

- [#&#8203;5835](https://redirect.github.com/rollup/rollup/pull/5835):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5838](https://redirect.github.com/rollup/rollup/pull/5838):
replace undefined with void 0 for operator void
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.34.5`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4345)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.4...v4.34.5)

*2025-02-07*

##### Bug Fixes

- Ensure namespace reexports always include all properties of all
exports
([#&#8203;5837](https://redirect.github.com/rollup/rollup/issues/5837))

##### Pull Requests

- [#&#8203;5836](https://redirect.github.com/rollup/rollup/pull/5836):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5837](https://redirect.github.com/rollup/rollup/pull/5837):
Include all paths of reexports if namespace is used
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.34.4`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4344)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.3...v4.34.4)

*2025-02-05*

##### Bug Fixes

- Do not tree-shake properties if a rest element is used in
destructuring
([#&#8203;5833](https://redirect.github.com/rollup/rollup/issues/5833))

##### Pull Requests

- [#&#8203;5833](https://redirect.github.com/rollup/rollup/pull/5833):
include all properties if a rest element is destructed
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.34.3`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4343)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.2...v4.34.3)

*2025-02-05*

##### Bug Fixes

- Ensure properties of "this" are included in getters
([#&#8203;5831](https://redirect.github.com/rollup/rollup/issues/5831))

##### Pull Requests

- [#&#8203;5831](https://redirect.github.com/rollup/rollup/pull/5831):
include the properties that accessed by this
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.34.2`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4342)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.1...v4.34.2)

*2025-02-04*

##### Bug Fixes

- Fix an issue where not all usages of a function were properly detected
([#&#8203;5827](https://redirect.github.com/rollup/rollup/issues/5827))

##### Pull Requests

- [#&#8203;5827](https://redirect.github.com/rollup/rollup/pull/5827):
Ensure that functions provided to a constructor are properly deoptimized
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.34.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4341)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.34.0...v4.34.1)

*2025-02-03*

##### Bug Fixes

- Ensure throwing objects includes the entire object
([#&#8203;5825](https://redirect.github.com/rollup/rollup/issues/5825))

##### Pull Requests

- [#&#8203;5825](https://redirect.github.com/rollup/rollup/pull/5825):
Ensure that all properties of throw statements are included
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.34.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4340)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.33.0...v4.34.0)

*2025-02-01*

##### Features

- Tree-shake unused properties in object literals (re-implements
[#&#8203;5420](https://redirect.github.com/rollup/rollup/issues/5420))
([#&#8203;5737](https://redirect.github.com/rollup/rollup/issues/5737))

##### Pull Requests

- [#&#8203;5737](https://redirect.github.com/rollup/rollup/pull/5737):
Reapply object tree-shaking
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert),
[@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.33.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4330)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.32.1...v4.33.0)

*2025-02-01*

##### Features

- Correctly detect literal value of more negated expressions
([#&#8203;5812](https://redirect.github.com/rollup/rollup/issues/5812))

##### Bug Fixes

- Use the correct with/assert attribute key in dynamic imports
([#&#8203;5818](https://redirect.github.com/rollup/rollup/issues/5818))
- Fix an issue where logical expressions were considered to have the
wrong value
([#&#8203;5819](https://redirect.github.com/rollup/rollup/issues/5819))

##### Pull Requests

- [#&#8203;5812](https://redirect.github.com/rollup/rollup/pull/5812):
feat: optimize the literal value of unary expressions
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5816](https://redirect.github.com/rollup/rollup/pull/5816):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5817](https://redirect.github.com/rollup/rollup/pull/5817):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot],
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5818](https://redirect.github.com/rollup/rollup/pull/5818):
support for changing the attributes key for dynamic imports
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5819](https://redirect.github.com/rollup/rollup/pull/5819):
Return UnknownValue if getLiteralValueAtPath is called recursively
within logical expressions
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5820](https://redirect.github.com/rollup/rollup/pull/5820):
return null
([@&#8203;kingma-sbw](https://redirect.github.com/kingma-sbw))

###
[`v4.32.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4321)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.32.0...v4.32.1)

*2025-01-28*

##### Bug Fixes

- Fix possible crash when optimizing logical expressions
([#&#8203;5804](https://redirect.github.com/rollup/rollup/issues/5804))

##### Pull Requests

- [#&#8203;5804](https://redirect.github.com/rollup/rollup/pull/5804):
fix: set hasDeoptimizedCache to true as early as possible
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5813](https://redirect.github.com/rollup/rollup/pull/5813):
Fix typo ([@&#8203;kantuni](https://redirect.github.com/kantuni))

###
[`v4.32.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4320)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.31.0...v4.32.0)

*2025-01-24*

##### Features

- Add watch.onInvalidate option to trigger actions immediately when a
file is changed
([#&#8203;5799](https://redirect.github.com/rollup/rollup/issues/5799))

##### Bug Fixes

- Fix incorrect urls in CLI warnings
([#&#8203;5809](https://redirect.github.com/rollup/rollup/issues/5809))

##### Pull Requests

- [#&#8203;5799](https://redirect.github.com/rollup/rollup/pull/5799):
Feature/watch on invalidate
([@&#8203;drebrez](https://redirect.github.com/drebrez),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5808](https://redirect.github.com/rollup/rollup/pull/5808):
chore(deps): update dependency vite to v6.0.9 \[security]
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5809](https://redirect.github.com/rollup/rollup/pull/5809):
fix: avoid duplicate rollupjs.org prefix
([@&#8203;GauBen](https://redirect.github.com/GauBen),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5810](https://redirect.github.com/rollup/rollup/pull/5810):
chore(deps): update dependency
[@&#8203;shikijs/vitepress-twoslash](https://redirect.github.com/shikijs/vitepress-twoslash)
to v2 ([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5811](https://redirect.github.com/rollup/rollup/pull/5811):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.31.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4310)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.30.1...v4.31.0)

*2025-01-19*

##### Features

- Do not immediately quit when trying to use watch mode from within
non-TTY environments
([#&#8203;5803](https://redirect.github.com/rollup/rollup/issues/5803))

##### Bug Fixes

- Handle files with more than one UTF-8 BOM header
([#&#8203;5806](https://redirect.github.com/rollup/rollup/issues/5806))

##### Pull Requests

- [#&#8203;5792](https://redirect.github.com/rollup/rollup/pull/5792):
fix(deps): update rust crate swc_compiler_base to v8
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5793](https://redirect.github.com/rollup/rollup/pull/5793):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5794](https://redirect.github.com/rollup/rollup/pull/5794):
chore(deps): lock file maintenance
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5801](https://redirect.github.com/rollup/rollup/pull/5801):
chore(deps): update dependency eslint-config-prettier to v10
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5802](https://redirect.github.com/rollup/rollup/pull/5802):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5803](https://redirect.github.com/rollup/rollup/pull/5803):
Support watch mode in yarn, gradle and containers
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5806](https://redirect.github.com/rollup/rollup/pull/5806):
fix: strip all BOMs
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.30.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4301)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.30.0...v4.30.1)

*2025-01-07*

##### Bug Fixes

- Prevent invalid code when simplifying unary expressions in switch
cases
([#&#8203;5786](https://redirect.github.com/rollup/rollup/issues/5786))

##### Pull Requests

- [#&#8203;5786](https://redirect.github.com/rollup/rollup/pull/5786):
fix: consider that literals cannot following switch case.
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.30.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4300)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.29.2...v4.30.0)

*2025-01-06*

##### Features

- Inline values of resolvable unary expressions for improved
tree-shaking
([#&#8203;5775](https://redirect.github.com/rollup/rollup/issues/5775))

##### Pull Requests

- [#&#8203;5775](https://redirect.github.com/rollup/rollup/pull/5775):
feat: enhance the treehshaking for unary expression
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5783](https://redirect.github.com/rollup/rollup/pull/5783):
Improve CI caching for node_modules
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.29.2`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4292)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.29.1...v4.29.2)

*2025-01-05*

##### Bug Fixes

- Keep import attributes when using dynamic ESM `import()` expressions
from CommonJS
([#&#8203;5781](https://redirect.github.com/rollup/rollup/issues/5781))

##### Pull Requests

- [#&#8203;5772](https://redirect.github.com/rollup/rollup/pull/5772):
Improve caching on CI
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5773](https://redirect.github.com/rollup/rollup/pull/5773):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5780](https://redirect.github.com/rollup/rollup/pull/5780):
feat: use picocolors instead of colorette
([@&#8203;re-taro](https://redirect.github.com/re-taro))
- [#&#8203;5781](https://redirect.github.com/rollup/rollup/pull/5781):
fix: keep import attributes for cjs format
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.29.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4291)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.29.0...v4.29.1)

*2024-12-21*

##### Bug Fixes

- Fix crash from deoptimized logical expressions
([#&#8203;5771](https://redirect.github.com/rollup/rollup/issues/5771))

##### Pull Requests

- [#&#8203;5769](https://redirect.github.com/rollup/rollup/pull/5769):
Remove unnecessary lifetimes
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5771](https://redirect.github.com/rollup/rollup/pull/5771):
fix: do not optimize the literal value if the cache is deoptimized
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.29.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4290)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.28.1...v4.29.0)

*2024-12-20*

##### Features

- Treat objects as truthy and always check second argument to better
simplify logical expressions
([#&#8203;5763](https://redirect.github.com/rollup/rollup/issues/5763))

##### Pull Requests

- [#&#8203;5759](https://redirect.github.com/rollup/rollup/pull/5759):
docs: add utf-8 encoding to JSON file reading
([@&#8203;chouchouji](https://redirect.github.com/chouchouji))
- [#&#8203;5760](https://redirect.github.com/rollup/rollup/pull/5760):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5763](https://redirect.github.com/rollup/rollup/pull/5763):
fix: introduce UnknownFalsyValue for enhancing if statement tree-shaking
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;5766](https://redirect.github.com/rollup/rollup/pull/5766):
chore(deps): update dependency
[@&#8203;rollup/plugin-node-resolve](https://redirect.github.com/rollup/plugin-node-resolve)
to v16 ([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5767](https://redirect.github.com/rollup/rollup/pull/5767):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.28.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4281)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.28.0...v4.28.1)

*2024-12-06*

##### Bug Fixes

- Support running Rollup natively on LoongArch
([#&#8203;5749](https://redirect.github.com/rollup/rollup/issues/5749))
- Add optional `debugId` to `SourceMap` types
([#&#8203;5751](https://redirect.github.com/rollup/rollup/issues/5751))

##### Pull Requests

- [#&#8203;5749](https://redirect.github.com/rollup/rollup/pull/5749):
feat: add support for LoongArch
([@&#8203;darkyzhou](https://redirect.github.com/darkyzhou))
- [#&#8203;5751](https://redirect.github.com/rollup/rollup/pull/5751):
feat: Add `debugId` to `SourceMap` types
([@&#8203;timfish](https://redirect.github.com/timfish),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5752](https://redirect.github.com/rollup/rollup/pull/5752):
chore(deps): update dependency mocha to v11
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5753](https://redirect.github.com/rollup/rollup/pull/5753):
chore(deps): update dependency vite to v6
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5754](https://redirect.github.com/rollup/rollup/pull/5754):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5755](https://redirect.github.com/rollup/rollup/pull/5755):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5756](https://redirect.github.com/rollup/rollup/pull/5756):
Test if saving the Cargo cache can speed up FreeBSD
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.28.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4280)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.27.4...v4.28.0)

*2024-11-30*

##### Features

- Allow to specify how to handle import attributes when transpiling
Rollup config files
([#&#8203;5743](https://redirect.github.com/rollup/rollup/issues/5743))

##### Pull Requests

- [#&#8203;5743](https://redirect.github.com/rollup/rollup/pull/5743):
fix: supports modify the import attributes key in the config file
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5747](https://redirect.github.com/rollup/rollup/pull/5747):
chore(deps): update codecov/codecov-action action to v5
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5748](https://redirect.github.com/rollup/rollup/pull/5748):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.27.4`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4274)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.27.3...v4.27.4)

*2024-11-23*

##### Bug Fixes

- Update bundled magic-string to support sourcemap debug ids
([#&#8203;5740](https://redirect.github.com/rollup/rollup/issues/5740))

##### Pull Requests

- [#&#8203;5740](https://redirect.github.com/rollup/rollup/pull/5740):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.27.3`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4273)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.27.2...v4.27.3)

*2024-11-18*

##### Bug Fixes

- Revert object property tree-shaking for now
([#&#8203;5736](https://redirect.github.com/rollup/rollup/issues/5736))

##### Pull Requests

- [#&#8203;5736](https://redirect.github.com/rollup/rollup/pull/5736):
Revert object tree-shaking until some issues have been resolved
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.27.2`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4272)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.27.1...v4.27.2)

*2024-11-15*

##### Bug Fixes

- Ensure unused variables in patterns are always deconflicted if
rendered
([#&#8203;5728](https://redirect.github.com/rollup/rollup/issues/5728))

##### Pull Requests

- [#&#8203;5728](https://redirect.github.com/rollup/rollup/pull/5728):
Fix more variable deconflicting issues
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.27.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4271)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.27.0...v4.27.1)

*2024-11-15*

##### Bug Fixes

- Fix some situations where parameter declarations could put Rollup into
an infinite loop
([#&#8203;5727](https://redirect.github.com/rollup/rollup/issues/5727))

##### Pull Requests

- [#&#8203;5727](https://redirect.github.com/rollup/rollup/pull/5727):
Debug out-of-memory issues with Rollup v4.27.0
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.27.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4270)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.26.0...v4.27.0)

*2024-11-15*

##### Features

- Tree-shake unused properties in object literals
([#&#8203;5420](https://redirect.github.com/rollup/rollup/issues/5420))

##### Bug Fixes

- Change hash length limit to 21 to avoid inconsistent hash length
([#&#8203;5423](https://redirect.github.com/rollup/rollup/issues/5423))

##### Pull Requests

- [#&#8203;5420](https://redirect.github.com/rollup/rollup/pull/5420):
feat: implement object tree-shaking
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5723](https://redirect.github.com/rollup/rollup/pull/5723):
Reduce max hash size to 21
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5724](https://redirect.github.com/rollup/rollup/pull/5724):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5725](https://redirect.github.com/rollup/rollup/pull/5725):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.26.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4260)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.25.0...v4.26.0)

*2024-11-13*

##### Features

- Allow to avoid `await bundle.close()` via explicit resource management
in TypeScript
([#&#8203;5721](https://redirect.github.com/rollup/rollup/issues/5721))

##### Pull Requests

- [#&#8203;5721](https://redirect.github.com/rollup/rollup/pull/5721):
feat: support `using` for `RollupBuild`
([@&#8203;shulaoda](https://redirect.github.com/shulaoda))

###
[`v4.25.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4250)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.24.4...v4.25.0)

*2024-11-09*

##### Features

- Add `output.sourcemapDebugIds` option to add matching debug ids to
sourcemaps and code for tools like Sentry or Rollbar
([#&#8203;5712](https://redirect.github.com/rollup/rollup/issues/5712))

##### Bug Fixes

- Make it easier to manually reproduce base16 hashes by using a more
standard base16 conversion algorithm
([#&#8203;5719](https://redirect.github.com/rollup/rollup/issues/5719))

##### Pull Requests

- [#&#8203;5712](https://redirect.github.com/rollup/rollup/pull/5712):
feat: Add support for injecting Debug IDs
([@&#8203;timfish](https://redirect.github.com/timfish))
- [#&#8203;5717](https://redirect.github.com/rollup/rollup/pull/5717):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5718](https://redirect.github.com/rollup/rollup/pull/5718):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5719](https://redirect.github.com/rollup/rollup/pull/5719):
Use a less surprising base-16 encoding
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.24.4`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4244)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.24.3...v4.24.4)

*2024-11-04*

##### Bug Fixes

- Ensure mutations by handlers in Proxy definitions are always respected
when tree-shaking
([#&#8203;5713](https://redirect.github.com/rollup/rollup/issues/5713))

##### Pull Requests

- [#&#8203;5708](https://redirect.github.com/rollup/rollup/pull/5708):
Update configuration-options document
([@&#8203;sacru2red](https://redirect.github.com/sacru2red),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))
- [#&#8203;5711](https://redirect.github.com/rollup/rollup/pull/5711):
chore(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;5713](https://redirect.github.com/rollup/rollup/pull/5713):
fix: Deoptimize the proxied object if its property is reassigned in the
handler functions
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))

###
[`v4.24.3`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4243)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.24.2...v4.24.3)

*2024-10-29*

##### Bug Fixes

- Slightly reduce memory consumption by specifying fixed array sizes
where possible
([#&#8203;5703](https://redirect.github.com/rollup/rollup/issues/5703))

##### Pull Requests

- [#&#8203;5703](https://redirect.github.com/rollup/rollup/pull/5703):
perf: use pre-allocated arrays for known result sizes
([@&#8203;GalacticHypernova](https://redirect.github.com/GalacticHypernova))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNDUuMCIsInVwZGF0ZWRJblZlciI6IjQwLjcuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 15:48:41 +00:00
renovate[bot] cb14827639
chore(deps): update dependency @types/supertest to v6.0.3 (#1169)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@types/supertest](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/supertest)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/supertest))
| [`6.0.2` ->
`6.0.3`](https://renovatebot.com/diffs/npm/@types%2fsupertest/6.0.2/6.0.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fsupertest/6.0.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fsupertest/6.0.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fsupertest/6.0.2/6.0.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fsupertest/6.0.2/6.0.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMzguMCIsInVwZGF0ZWRJblZlciI6IjM5LjIzOC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 15:48:17 +00:00
renovate[bot] c00675c9bd
chore(deps): update dependency @types/node to v22 (#1067)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`^20.11.16` ->
`^22.0.0`](https://renovatebot.com/diffs/npm/@types%2fnode/20.17.31/22.15.17)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/22.15.17?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fnode/22.15.17?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fnode/20.17.31/22.15.17?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/20.17.31/22.15.17?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMzMuMSIsInVwZGF0ZWRJblZlciI6IjQwLjcuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 15:48:00 +00:00
renovate[bot] 1e58d2b65e
chore(deps): update dependency @types/node to v20.17.31 (#1138)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`20.17.16` ->
`20.17.31`](https://renovatebot.com/diffs/npm/@types%2fnode/20.17.16/20.17.31)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/20.17.31?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fnode/20.17.31?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fnode/20.17.16/20.17.31?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/20.17.16/20.17.31?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNDUuMCIsInVwZGF0ZWRJblZlciI6IjM5LjI1Ny4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-30 13:01:21 -04:00
Michael Beemer 59c2f5df76
chore: regenerate package lock (#1184)
Attempting to address the E2E failure:
https://github.com/open-feature/js-sdk/actions/runs/14643009193/job/41089818968?pr=1181

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-04-24 12:25:37 -04:00
Kaushal Kapasi 59b8fe904f
feat: adds RequireFlagsEnabled decorator (#1159)
## This PR
- Feature: Adds a `RequireFlagsEnabled` decorator to allow a simple,
reusable way to block access to a specific controller or endpoint based
on the value of a list of one, or many, boolean flags

### Notes
- Discussions on the approach & implementation are welcome!

### Follow-up Tasks
- Update OpenFeature NestJS docs to include new `RequireFlagsEnabled`
decorator & usage examples

### How to test
`npx jest --selectProject=nest`

---------

Signed-off-by: Kaushal Kapasi <kaushal.kapasi@taplytics.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-24 13:32:55 +00:00
Michael Beemer 4fe8d87a2e
docs: Clarify the behavior of setProviderAndWait (#1180)
## This PR

- Updates readme examples to include a try/catch around
setProviderAndWait
- Improves the setProviderAndWait JS Doc

### Related Issues

Fixes #1179

### Notes

https://cloud-native.slack.com/archives/C0344AANLA1/p1745326882304199

---------

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-04-23 13:16:18 -04:00
OpenFeature Bot a259b9097b
chore(main): release angular-sdk 0.0.13 (#1175) 2025-04-20 19:07:29 +02:00
OpenFeature Bot d13adca9cd
chore(main): release nestjs-sdk 0.2.4 (#1177) 2025-04-20 19:07:06 +02:00
Lukas Reining 42a3b39c24
chore(nest): allow nestjs version 11 (#1176) 2025-04-20 09:26:13 +02:00
Todd Baert 2f55a36dca
chore: use publish env
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-16 12:27:46 -04:00
Lukas Reining 21a32ec92e
docs: fix readme typo (#1174)
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
2025-04-14 16:41:22 -04:00
OpenFeature Bot 07a3d85742
chore(main): release react-sdk 1.0.0 (#1154)
🤖 I have created a release *beep* *boop*
---


##
[1.0.0](https://github.com/open-feature/js-sdk/compare/react-sdk-v0.4.11...react-sdk-v1.0.0)
(2025-04-14)


###  New Features

* add polyfill for react use hook
([#1157](https://github.com/open-feature/js-sdk/issues/1157))
([5afe61f](5afe61f9e3))
* add support for abort controllers to event handlers
([#1151](https://github.com/open-feature/js-sdk/issues/1151))
([6a22483](6a224830fa))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-14 17:03:15 +00:00
OpenFeature Bot 37bcf3cf48
chore(main): release web-sdk 1.5.0 (#1156)
🤖 I have created a release *beep* *boop*
---


##
[1.5.0](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.4.1...web-sdk-v1.5.0)
(2025-04-11)


###  New Features

* add a top-level method for accessing providers
([#1152](https://github.com/open-feature/js-sdk/issues/1152))
([ae8fce8](ae8fce8753))
* add support for abort controllers to event handlers
([#1151](https://github.com/open-feature/js-sdk/issues/1151))
([6a22483](6a224830fa))


### 🐛 Bug Fixes

* Typo in name of the function
([2c5b37c](2c5b37c79d))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-14 15:48:45 +00:00
OpenFeature Bot aff60f6776
chore(main): release server-sdk 1.18.0 (#1153)
🤖 I have created a release *beep* *boop*
---


##
[1.18.0](https://github.com/open-feature/js-sdk/compare/server-sdk-v1.17.1...server-sdk-v1.18.0)
(2025-04-11)


###  New Features

* add a top-level method for accessing providers
([#1152](https://github.com/open-feature/js-sdk/issues/1152))
([ae8fce8](ae8fce8753))
* add support for abort controllers to event handlers
([#1151](https://github.com/open-feature/js-sdk/issues/1151))
([6a22483](6a224830fa))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-04-11 14:30:17 +02:00
OpenFeature Bot 7864bd704c
chore(main): release nestjs-sdk 0.2.3 (#1144)
🤖 I have created a release *beep* *boop*
---


##
[0.2.3](https://github.com/open-feature/js-sdk/compare/nestjs-sdk-v0.2.2...nestjs-sdk-v0.2.3)
(2025-04-11)


### 🧹 Chore

* update sdk peer
([#1142](https://github.com/open-feature/js-sdk/issues/1142))
([8bb6206](8bb620601e))


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @openfeature/server-sdk bumped from * to 1.18.0

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-04-11 14:17:48 +02:00
OpenFeature Bot 191433e705
chore(main): release angular-sdk 0.0.12 (#1171)
🤖 I have created a release *beep* *boop*
---


##
[0.0.12](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.11...angular-sdk-v0.0.12)
(2025-04-11)


###  New Features

* **angular:** add docs for setting evaluation context in angular
([#1170](https://github.com/open-feature/js-sdk/issues/1170))
([24f1b23](24f1b230bf))


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @openfeature/web-sdk bumped from * to 1.5.0

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-04-11 14:04:30 +02:00
Lukas Reining 24f1b230bf
feat(angular): add docs for setting evaluation context in angular (#1170)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

Adds docs for setting evaluation context in angular

Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
2025-04-11 13:56:24 +02:00
renovate[bot] 5f85a56362
chore(deps): update dependency esbuild to ^0.25.0 [security] (#1145)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [esbuild](https://redirect.github.com/evanw/esbuild) | [`^0.24.0` ->
`^0.25.0`](https://renovatebot.com/diffs/npm/esbuild/0.24.2/0.25.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/esbuild/0.25.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/esbuild/0.25.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/esbuild/0.24.2/0.25.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/esbuild/0.24.2/0.25.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

### GitHub Vulnerability Alerts

####
[GHSA-67mh-4wv8-2f99](https://redirect.github.com/evanw/esbuild/security/advisories/GHSA-67mh-4wv8-2f99)

### Summary

esbuild allows any websites to send any request to the development
server and read the response due to default CORS settings.

### Details

esbuild sets `Access-Control-Allow-Origin: *` header to all requests,
including the SSE connection, which allows any websites to send any
request to the development server and read the response.


df815ac27b/pkg/api/serve_other.go (L121)

df815ac27b/pkg/api/serve_other.go (L363)

**Attack scenario**:

1. The attacker serves a malicious web page
(`http://malicious.example.com`).
1. The user accesses the malicious web page.
1. The attacker sends a `fetch('http://127.0.0.1:8000/main.js')` request
by JS in that malicious web page. This request is normally blocked by
same-origin policy, but that's not the case for the reasons above.
1. The attacker gets the content of `http://127.0.0.1:8000/main.js`.

In this scenario, I assumed that the attacker knows the URL of the
bundle output file name. But the attacker can also get that information
by

- Fetching `/index.html`: normally you have a script tag here
- Fetching `/assets`: it's common to have a `assets` directory when you
have JS files and CSS files in a different directory and the directory
listing feature tells the attacker the list of files
- Connecting `/esbuild` SSE endpoint: the SSE endpoint sends the URL
path of the changed files when the file is changed (`new
EventSource('/esbuild').addEventListener('change', e =>
console.log(e.type, e.data))`)
- Fetching URLs in the known file: once the attacker knows one file, the
attacker can know the URLs imported from that file

The scenario above fetches the compiled content, but if the victim has
the source map option enabled, the attacker can also get the
non-compiled content by fetching the source map file.

### PoC

1. Download
[reproduction.zip](https://redirect.github.com/user-attachments/files/18561484/reproduction.zip)
2. Extract it and move to that directory
1. Run `npm i`
1. Run `npm run watch`
1. Run `fetch('http://127.0.0.1:8000/app.js').then(r =>
r.text()).then(content => console.log(content))` in a different
website's dev tools.


![image](https://redirect.github.com/user-attachments/assets/08fc2e4d-e1ec-44ca-b0ea-78a73c3c40e9)

### Impact

Users using the serve feature may get the source code stolen by
malicious websites.

---

### Release Notes

<details>
<summary>evanw/esbuild (esbuild)</summary>

###
[`v0.25.0`](https://redirect.github.com/evanw/esbuild/blob/HEAD/CHANGELOG.md#v0250)

[Compare
Source](https://redirect.github.com/evanw/esbuild/compare/v0.24.2...v0.25.0)

**This release deliberately contains backwards-incompatible changes.**
To avoid automatically picking up releases like this, you should either
be pinning the exact version of `esbuild` in your `package.json` file
(recommended) or be using a version range syntax that only accepts patch
upgrades such as `^0.24.0` or `~0.24.0`. See npm's documentation about
[semver](https://docs.npmjs.com/cli/v6/using-npm/semver/) for more
information.

- Restrict access to esbuild's development server
([GHSA-67mh-4wv8-2f99](https://redirect.github.com/evanw/esbuild/security/advisories/GHSA-67mh-4wv8-2f99))

This change addresses esbuild's first security vulnerability report.
Previously esbuild set the `Access-Control-Allow-Origin` header to `*`
to allow esbuild's development server to be flexible in how it's used
for development. However, this allows the websites you visit to make
HTTP requests to esbuild's local development server, which gives
read-only access to your source code if the website were to fetch your
source code's specific URL. You can read more information in [the
report](https://redirect.github.com/evanw/esbuild/security/advisories/GHSA-67mh-4wv8-2f99).

Starting with this release,
[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) will now
be disabled, and requests will now be denied if the host does not match
the one provided to `--serve=`. The default host is `0.0.0.0`, which
refers to all of the IP addresses that represent the local machine (e.g.
both `127.0.0.1` and `192.168.0.1`). If you want to customize anything
about esbuild's development server, you can [put a proxy in front of
esbuild](https://esbuild.github.io/api/#serve-proxy) and modify the
incoming and/or outgoing requests.

In addition, the `serve()` API call has been changed to return an array
of `hosts` instead of a single `host` string. This makes it possible to
determine all of the hosts that esbuild's development server will
accept.

Thanks to [@&#8203;sapphi-red](https://redirect.github.com/sapphi-red)
for reporting this issue.

- Delete output files when a build fails in watch mode
([#&#8203;3643](https://redirect.github.com/evanw/esbuild/issues/3643))

It has been requested for esbuild to delete files when a build fails in
watch mode. Previously esbuild left the old files in place, which could
cause people to not immediately realize that the most recent build
failed. With this release, esbuild will now delete all output files if a
rebuild fails. Fixing the build error and triggering another rebuild
will restore all output files again.

- Fix correctness issues with the CSS nesting transform
([#&#8203;3620](https://redirect.github.com/evanw/esbuild/issues/3620),
[#&#8203;3877](https://redirect.github.com/evanw/esbuild/issues/3877),
[#&#8203;3933](https://redirect.github.com/evanw/esbuild/issues/3933),
[#&#8203;3997](https://redirect.github.com/evanw/esbuild/issues/3997),
[#&#8203;4005](https://redirect.github.com/evanw/esbuild/issues/4005),
[#&#8203;4037](https://redirect.github.com/evanw/esbuild/pull/4037),
[#&#8203;4038](https://redirect.github.com/evanw/esbuild/pull/4038))

    This release fixes the following problems:

- Naive expansion of CSS nesting can result in an exponential blow-up of
generated CSS if each nesting level has multiple selectors. Previously
esbuild sometimes collapsed individual nesting levels using `:is()` to
limit expansion. However, this collapsing wasn't correct in some cases,
so it has been removed to fix correctness issues.

        ```css
        /* Original code */
        .parent {
          > .a,
          > .b1 > .b2 {
            color: red;
          }
        }

        /* Old output (with --supported:nesting=false) */
        .parent > :is(.a, .b1 > .b2) {
          color: red;
        }

        /* New output (with --supported:nesting=false) */
        .parent > .a,
        .parent > .b1 > .b2 {
          color: red;
        }
        ```

Thanks to [@&#8203;tim-we](https://redirect.github.com/tim-we) for
working on a fix.

- The `&` CSS nesting selector can be repeated multiple times to
increase CSS specificity. Previously esbuild ignored this possibility
and incorrectly considered `&&` to have the same specificity as `&`.
With this release, this should now work correctly:

        ```css
        /* Original code (color should be red) */
        div {
          && { color: red }
          & { color: blue }
        }

        /* Old output (with --supported:nesting=false) */
        div {
          color: red;
        }
        div {
          color: blue;
        }

        /* New output (with --supported:nesting=false) */
        div:is(div) {
          color: red;
        }
        div {
          color: blue;
        }
        ```

Thanks to [@&#8203;CPunisher](https://redirect.github.com/CPunisher) for
working on a fix.

- Previously transforming nested CSS incorrectly removed leading
combinators from within pseudoclass selectors such as `:where()`. This
edge case has been fixed and how has test coverage.

        ```css
        /* Original code */
        a b:has(> span) {
          a & {
            color: green;
          }
        }

        /* Old output (with --supported:nesting=false) */
        a :is(a b:has(span)) {
          color: green;
        }

        /* New output (with --supported:nesting=false) */
        a :is(a b:has(> span)) {
          color: green;
        }
        ```

This fix was contributed by
[@&#8203;NoremacNergfol](https://redirect.github.com/NoremacNergfol).

- The CSS minifier contains logic to remove the `&` selector when it can
be implied, which happens when there is only one and it's the leading
token. However, this logic was incorrectly also applied to selector
lists inside of pseudo-class selectors such as `:where()`. With this
release, the minifier will now avoid applying this logic in this edge
case:

        ```css
        /* Original code */
        .a {
          & .b { color: red }
          :where(& .b) { color: blue }
        }

        /* Old output (with --minify) */
        .a{.b{color:red}:where(.b){color:#&#8203;00f}}

        /* New output (with --minify) */
        .a{.b{color:red}:where(& .b){color:#&#8203;00f}}
        ```

- Fix some correctness issues with source maps
([#&#8203;1745](https://redirect.github.com/evanw/esbuild/issues/1745),
[#&#8203;3183](https://redirect.github.com/evanw/esbuild/issues/3183),
[#&#8203;3613](https://redirect.github.com/evanw/esbuild/issues/3613),
[#&#8203;3982](https://redirect.github.com/evanw/esbuild/issues/3982))

Previously esbuild incorrectly treated source map path references as
file paths instead of as URLs. With this release, esbuild will now treat
source map path references as URLs. This fixes the following problems
with source maps:

- File names in `sourceMappingURL` that contained a space previously did
not encode the space as `%20`, which resulted in JavaScript tools
(including esbuild) failing to read that path back in when consuming the
generated output file. This should now be fixed.

- Absolute URLs in `sourceMappingURL` that use the `file://` scheme
previously attempted to read from a folder called `file:`. These URLs
should now be recognized and parsed correctly.

- Entries in the `sources` array in the source map are now treated as
URLs instead of file paths. The correct behavior for this is much more
clear now that source maps has a [formal
specification](https://tc39.es/ecma426/). Many thanks to those who
worked on the specification.

- Fix incorrect package for `@esbuild/netbsd-arm64`
([#&#8203;4018](https://redirect.github.com/evanw/esbuild/issues/4018))

Due to a copy+paste typo, the binary published to
`@esbuild/netbsd-arm64` was not actually for `arm64`, and didn't run in
that environment. This release should fix running esbuild in that
environment (NetBSD on 64-bit ARM). Sorry about the mistake.

- Fix a minification bug with bitwise operators and bigints
([#&#8203;4065](https://redirect.github.com/evanw/esbuild/issues/4065))

This change removes an incorrect assumption in esbuild that all bitwise
operators result in a numeric integer. That assumption was correct up
until the introduction of bigints in ES2020, but is no longer correct
because almost all bitwise operators now operate on both numbers and
bigints. Here's an example of the incorrect minification:

    ```js
    // Original code
    if ((a & b) !== 0) found = true

    // Old output (with --minify)
    a&b&&(found=!0);

    // New output (with --minify)
    (a&b)!==0&&(found=!0);
    ```

- Fix esbuild incorrectly rejecting valid TypeScript edge case
([#&#8203;4027](https://redirect.github.com/evanw/esbuild/issues/4027))

    The following TypeScript code is valid:

    ```ts
    export function open(async?: boolean): void {
      console.log(async as boolean)
    }
    ```

Before this version, esbuild would fail to parse this with a syntax
error as it expected the token sequence `async as ...` to be the start
of an async arrow function expression `async as => ...`. This edge case
should be parsed correctly by esbuild starting with this release.

- Transform BigInt values into constructor calls when unsupported
([#&#8203;4049](https://redirect.github.com/evanw/esbuild/issues/4049))

Previously esbuild would refuse to compile the BigInt literals (such as
`123n`) if they are unsupported in the configured target environment
(such as with `--target=es6`). The rationale was that they cannot be
polyfilled effectively because they change the behavior of JavaScript's
arithmetic operators and JavaScript doesn't have operator overloading.

However, this prevents using esbuild with certain libraries that would
otherwise work if BigInt literals were ignored, such as with old
versions of the [`buffer`
library](https://redirect.github.com/feross/buffer) before the library
fixed support for running in environments without BigInt support. So
with this release, esbuild will now turn BigInt literals into BigInt
constructor calls (so `123n` becomes `BigInt(123)`) and generate a
warning in this case. You can turn off the warning with
`--log-override:bigint=silent` or restore the warning to an error with
`--log-override:bigint=error` if needed.

- Change how `console` API dropping works
([#&#8203;4020](https://redirect.github.com/evanw/esbuild/issues/4020))

Previously the `--drop:console` feature replaced all method calls off of
the `console` global with `undefined` regardless of how long the
property access chain was (so it applied to `console.log()` and
`console.log.call(console)` and `console.log.not.a.method()`). However,
it was pointed out that this breaks uses of `console.log.bind(console)`.
That's also incompatible with Terser's implementation of the feature,
which is where this feature originally came from (it does support
`bind`). So with this release, using this feature with esbuild will now
only replace one level of method call (unless extended by `call` or
`apply`) and will replace the method being called with an empty function
in complex cases:

    ```js
    // Original code
    const x = console.log('x')
    const y = console.log.call(console, 'y')
    const z = console.log.bind(console)('z')

    // Old output (with --drop-console)
    const x = void 0;
    const y = void 0;
    const z = (void 0)("z");

    // New output (with --drop-console)
    const x = void 0;
    const y = void 0;
    const z = (() => {
    }).bind(console)("z");
    ```

    This should more closely match Terser's existing behavior.

-   Allow BigInt literals as `define` values

With this release, you can now use BigInt literals as define values,
such as with `--define:FOO=123n`. Previously trying to do this resulted
in a syntax error.

- Fix a bug with resolve extensions in `node_modules`
([#&#8203;4053](https://redirect.github.com/evanw/esbuild/issues/4053))

The `--resolve-extensions=` option lets you specify the order in which
to try resolving implicit file extensions. For complicated reasons,
esbuild reorders TypeScript file extensions after JavaScript ones inside
of `node_modules` so that JavaScript source code is always preferred to
TypeScript source code inside of dependencies. However, this reordering
had a bug that could accidentally change the relative order of
TypeScript file extensions if one of them was a prefix of the other.
That bug has been fixed in this release. You can see the issue for
details.

- Better minification of statically-determined `switch` cases
([#&#8203;4028](https://redirect.github.com/evanw/esbuild/issues/4028))

With this release, esbuild will now try to trim unused code within
`switch` statements when the test expression and `case` expressions are
primitive literals. This can arise when the test expression is an
identifier that is substituted for a primitive literal at compile time.
For example:

    ```js
    // Original code
    switch (MODE) {
      case 'dev':
        installDevToolsConsole()
        break
      case 'prod':
        return
      default:
        throw new Error
    }

    // Old output (with --minify '--define:MODE="prod"')

switch("prod"){case"dev":installDevToolsConsole();break;case"prod":return;default:throw
new Error}

    // New output (with --minify '--define:MODE="prod"')
    return;
    ```

- Emit `/* @&#8203;__KEY__ */` for string literals derived from property
names
([#&#8203;4034](https://redirect.github.com/evanw/esbuild/issues/4034))

Property name mangling is an advanced feature that shortens certain
property names for better minification (I say "advanced feature" because
it's very easy to break your code with it). Sometimes you need to store
a property name in a string, such as `obj.get('foo')` instead of
`obj.foo`. JavaScript minifiers such as esbuild and
[Terser](https://terser.org/) have a convention where a `/*
@&#8203;__KEY__ */` comment before the string makes it behave like a
property name. So `obj.get(/* @&#8203;__KEY__ */ 'foo')` allows the
contents of the string `'foo'` to be shortened.

However, esbuild sometimes itself generates string literals containing
property names when transforming code, such as when lowering class
fields to ES6 or when transforming TypeScript decorators. Previously
esbuild didn't generate its own `/* @&#8203;__KEY__ */` comments in this
case, which means that minifying your code by running esbuild again on
its own output wouldn't work correctly (this does not affect people that
both minify and transform their code in a single step).

With this release, esbuild will now generate `/* @&#8203;__KEY__ */`
comments for property names in generated string literals. To avoid lots
of unnecessary output for people that don't use this advanced feature,
the generated comments will only be present when the feature is active.
If you want to generate the comments but not actually mangle any
property names, you can use a flag that has no effect such as
`--reserve-props=.`, which tells esbuild to not mangle any property
names (but still activates this feature).

- The `text` loader now strips the UTF-8 BOM if present
([#&#8203;3935](https://redirect.github.com/evanw/esbuild/issues/3935))

Some software (such as Notepad on Windows) can create text files that
start with the three bytes `0xEF 0xBB 0xBF`, which is referred to as the
"byte order mark". This prefix is intended to be removed before using
the text. Previously esbuild's `text` loader included this byte sequence
in the string, which turns into a prefix of `\uFEFF` in a JavaScript
string when decoded from UTF-8. With this release, esbuild's `text`
loader will now remove these bytes when they occur at the start of the
file.

- Omit legal comment output files when empty
([#&#8203;3670](https://redirect.github.com/evanw/esbuild/issues/3670))

Previously configuring esbuild with `--legal-comment=external` or
`--legal-comment=linked` would always generate a `.LEGAL.txt` output
file even if it was empty. Starting with this release, esbuild will now
only do this if the file will be non-empty. This should result in a more
organized output directory in some cases.

- Update Go from 1.23.1 to 1.23.5
([#&#8203;4056](https://redirect.github.com/evanw/esbuild/issues/4056),
[#&#8203;4057](https://redirect.github.com/evanw/esbuild/pull/4057))

This should have no effect on existing code as this version change does
not change Go's operating system support. It may remove certain reports
from vulnerability scanners that detect which version of the Go compiler
esbuild uses.

This PR was contributed by
[@&#8203;MikeWillCook](https://redirect.github.com/MikeWillCook).

- Allow passing a port of 0 to the development server
([#&#8203;3692](https://redirect.github.com/evanw/esbuild/issues/3692))

Unix sockets interpret a port of 0 to mean "pick a random unused port in
the [ephemeral port](https://en.wikipedia.org/wiki/Ephemeral_port)
range". However, esbuild's default behavior when the port is not
specified is to pick the first unused port starting from 8000 and
upward. This is more convenient because port 8000 is typically free, so
you can for example restart the development server and reload your app
in the browser without needing to change the port in the URL. Since
esbuild is written in Go (which does not have optional fields like
JavaScript), not specifying the port in Go means it defaults to 0, so
previously passing a port of 0 to esbuild caused port 8000 to be picked.

Starting with this release, passing a port of 0 to esbuild when using
the CLI or the JS API will now pass port 0 to the OS, which will pick a
random ephemeral port. To make this possible, the `Port` option in the
Go API has been changed from `uint16` to `int` (to allow for additional
sentinel values) and passing a port of -1 in Go now picks a random port.
Both the CLI and JS APIs now remap an explicitly-provided port of 0 into
-1 for the internal Go API.

Another option would have been to change `Port` in Go from `uint16` to
`*uint16` (Go's closest equivalent of `number | undefined`). However,
that would make the common case of providing an explicit port in Go very
awkward as Go doesn't support taking the address of integer constants.
This tradeoff isn't worth it as picking a random ephemeral port is a
rare use case. So the CLI and JS APIs should now match standard Unix
behavior when the port is 0, but you need to use -1 instead with Go API.

- Minification now avoids inlining constants with direct `eval`
([#&#8203;4055](https://redirect.github.com/evanw/esbuild/issues/4055))

    Direct `eval` can be used to introduce a new variable like this:

    ```js
    const variable = false
    ;(function () {
      eval("var variable = true")
      console.log(variable)
    })()
    ```

Previously esbuild inlined `variable` here (which became `false`), which
changed the behavior of the code. This inlining is now avoided, but
please keep in mind that direct `eval` breaks many assumptions that
JavaScript tools hold about normal code (especially when bundling) and I
do not recommend using it. There are usually better alternatives that
have a more localized impact on your code. You can read more about this
here: https://esbuild.github.io/link/direct-eval/

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "" (UTC), Automerge - At any time (no
schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNjQuMSIsInVwZGF0ZWRJblZlciI6IjM5LjE2NC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 10:21:28 +00:00
renovate[bot] 9218e987a0
chore(deps): update angular-eslint monorepo to v19 (major) (#1140)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@angular-eslint/builder](https://redirect.github.com/angular-eslint/angular-eslint)
([source](https://redirect.github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder))
| [`18.4.3` ->
`19.3.0`](https://renovatebot.com/diffs/npm/@angular-eslint%2fbuilder/18.4.3/19.3.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@angular-eslint%2fbuilder/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@angular-eslint%2fbuilder/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@angular-eslint%2fbuilder/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@angular-eslint%2fbuilder/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@angular-eslint/eslint-plugin](https://redirect.github.com/angular-eslint/angular-eslint)
([source](https://redirect.github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin))
| [`18.4.3` ->
`19.3.0`](https://renovatebot.com/diffs/npm/@angular-eslint%2feslint-plugin/18.4.3/19.3.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@angular-eslint%2feslint-plugin/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@angular-eslint%2feslint-plugin/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@angular-eslint%2feslint-plugin/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@angular-eslint%2feslint-plugin/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@angular-eslint/eslint-plugin-template](https://redirect.github.com/angular-eslint/angular-eslint)
([source](https://redirect.github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin-template))
| [`18.4.3` ->
`19.3.0`](https://renovatebot.com/diffs/npm/@angular-eslint%2feslint-plugin-template/18.4.3/19.3.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@angular-eslint%2feslint-plugin-template/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@angular-eslint%2feslint-plugin-template/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@angular-eslint%2feslint-plugin-template/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@angular-eslint%2feslint-plugin-template/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@angular-eslint/schematics](https://redirect.github.com/angular-eslint/angular-eslint)
([source](https://redirect.github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics))
| [`18.4.3` ->
`19.3.0`](https://renovatebot.com/diffs/npm/@angular-eslint%2fschematics/18.4.3/19.3.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@angular-eslint%2fschematics/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@angular-eslint%2fschematics/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@angular-eslint%2fschematics/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@angular-eslint%2fschematics/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@angular-eslint/template-parser](https://redirect.github.com/angular-eslint/angular-eslint)
([source](https://redirect.github.com/angular-eslint/angular-eslint/tree/HEAD/packages/template-parser))
| [`18.4.3` ->
`19.3.0`](https://renovatebot.com/diffs/npm/@angular-eslint%2ftemplate-parser/18.4.3/19.3.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@angular-eslint%2ftemplate-parser/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@angular-eslint%2ftemplate-parser/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@angular-eslint%2ftemplate-parser/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@angular-eslint%2ftemplate-parser/18.4.3/19.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>angular-eslint/angular-eslint
(@&#8203;angular-eslint/builder)</summary>

###
[`v19.3.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/builder/CHANGELOG.md#1930-2025-03-22)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.1...v19.3.0)

This was a version bump only for builder to align it with other
projects, there were no code changes.

###
[`v19.2.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/builder/CHANGELOG.md#1921-2025-03-08)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.0...v19.2.1)

This was a version bump only for builder to align it with other
projects, there were no code changes.

###
[`v19.2.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/builder/CHANGELOG.md#1920-2025-03-02)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.1.0...v19.2.0)

##### 🩹 Fixes

- **eslint-plugin-template:** find inline templates on components in
blocks
([#&#8203;2238](https://redirect.github.com/angular-eslint/angular-eslint/pull/2238))

##### ❤️ Thank You

-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)

###
[`v19.1.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/builder/CHANGELOG.md#1910-2025-02-09)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.2...v19.1.0)

This was a version bump only for builder to align it with other
projects, there were no code changes.

###
[`v19.0.2`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/builder/CHANGELOG.md#1902-2024-12-10)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.1...v19.0.2)

This was a version bump only for builder to align it with other
projects, there were no code changes.

###
[`v19.0.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/builder/CHANGELOG.md#1901-2024-12-06)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.0...v19.0.1)

This was a version bump only for builder to align it with other
projects, there were no code changes.

###
[`v19.0.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/builder/CHANGELOG.md#1900-2024-11-29)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v18.4.3...v19.0.0)

##### 🚀 Features

- update angular packages to the stable v19
([#&#8203;2120](https://redirect.github.com/angular-eslint/angular-eslint/pull/2120))

##### ❤️ Thank You

- Leosvel Pérez Espinosa
[@&#8203;leosvelperez](https://redirect.github.com/leosvelperez)

#### 18.4.3 (2024-11-29)

##### 🩹 Fixes

- yarn pnp issues
([#&#8203;2143](https://redirect.github.com/angular-eslint/angular-eslint/pull/2143))

##### ❤️ Thank You

- James Henry
[@&#8203;JamesHenry](https://redirect.github.com/JamesHenry)

#### 18.4.2 (2024-11-23)

This was a version bump only for builder to align it with other
projects, there were no code changes.

#### 18.4.1 (2024-11-18)

This was a version bump only for builder to align it with other
projects, there were no code changes.

#### 18.4.0 (2024-10-21)

##### 🚀 Features

- support ESM configs and .cjs and .mjs extensions
([#&#8203;2068](https://redirect.github.com/angular-eslint/angular-eslint/pull/2068))

##### 🩹 Fixes

- update dependency eslint to v9.13.0, support noConfigLookup
([#&#8203;2045](https://redirect.github.com/angular-eslint/angular-eslint/pull/2045))

##### ❤️ Thank You

- James Henry
[@&#8203;JamesHenry](https://redirect.github.com/JamesHenry)

#### 18.3.1 (2024-09-11)

This was a version bump only for builder to align it with other
projects, there were no code changes.

#### 18.3.0 (2024-08-13)

##### 🩹 Fixes

-   ensure consistent nx dependency versions

##### ❤️ Thank You

-   James Henry

#### 18.2.0 (2024-07-31)

This was a version bump only for builder to align it with other
projects, there were no code changes.

#### 18.1.0 (2024-07-01)

This was a version bump only for builder to align it with other
projects, there were no code changes.

#### 18.0.1 (2024-05-30)

##### 🩹 Fixes

- move typescript-eslint packages to peerDeps, consistently allow v7 and
v8

##### ❤️ Thank You

-   James Henry

</details>

<details>
<summary>angular-eslint/angular-eslint
(@&#8203;angular-eslint/eslint-plugin)</summary>

###
[`v19.3.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#1930-2025-03-22)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.1...v19.3.0)

This was a version bump only for eslint-plugin to align it with other
projects, there were no code changes.

###
[`v19.2.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#1921-2025-03-08)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.0...v19.2.1)

This was a version bump only for eslint-plugin to align it with other
projects, there were no code changes.

###
[`v19.2.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#1920-2025-03-02)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.1.0...v19.2.0)

##### 🚀 Features

- **eslint-plugin:** add rule require-lifecycle-on-prototype
([#&#8203;2259](https://redirect.github.com/angular-eslint/angular-eslint/pull/2259))

##### 🩹 Fixes

- **eslint-plugin-template:** find inline templates on components in
blocks
([#&#8203;2238](https://redirect.github.com/angular-eslint/angular-eslint/pull/2238))

##### ❤️ Thank You

-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)

###
[`v19.1.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#1910-2025-02-09)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.2...v19.1.0)

##### 🚀 Features

- **eslint-plugin:** prefer-signals now checks .asReadonly() calls
([#&#8203;2218](https://redirect.github.com/angular-eslint/angular-eslint/pull/2218))
- **eslint-plugin:** prefer-signals read-only suggestion is now a fix
([#&#8203;2175](https://redirect.github.com/angular-eslint/angular-eslint/pull/2175))

##### 🩹 Fixes

- **eslint-plugin:** \[no-input-prefix] false positive on input
initializer value
([#&#8203;2184](https://redirect.github.com/angular-eslint/angular-eslint/pull/2184))
- **eslint-plugin:** \[prefer-signals] support linkedSignal
([#&#8203;2213](https://redirect.github.com/angular-eslint/angular-eslint/pull/2213))

##### ❤️ Thank You

- Cédric Exbrayat
[@&#8203;cexbrayat](https://redirect.github.com/cexbrayat)
-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)
-   Lucas Neto Moreira

###
[`v19.0.2`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#1902-2024-12-10)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.1...v19.0.2)

##### 🩹 Fixes

- **eslint-plugin:** \[prefer-standalone] error range should only
include property and value
([#&#8203;2172](https://redirect.github.com/angular-eslint/angular-eslint/pull/2172))

##### ❤️ Thank You

- James Henry
[@&#8203;JamesHenry](https://redirect.github.com/JamesHenry)

###
[`v19.0.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#1901-2024-12-06)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.0...v19.0.1)

##### 🩹 Fixes

- **eslint-plugin:** adding prefer-signals rule to exported config
([#&#8203;2150](https://redirect.github.com/angular-eslint/angular-eslint/pull/2150))

##### ❤️ Thank You

- Quentin Deroubaix
[@&#8203;quentinderoubaix](https://redirect.github.com/quentinderoubaix)

###
[`v19.0.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#1900-2024-11-29)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v18.4.3...v19.0.0)

##### 🚀 Features

- ⚠️ **eslint-plugin:** promote prefer-standalone to recommended
([8dfdc4f4](https://redirect.github.com/angular-eslint/angular-eslint/commit/8dfdc4f4))
- **eslint-plugin:** new rule prefer-signals
([#&#8203;1872](https://redirect.github.com/angular-eslint/angular-eslint/pull/1872))
- ⚠️ **eslint-plugin:** remove deprecated no-host-metadata-property rule
([#&#8203;2113](https://redirect.github.com/angular-eslint/angular-eslint/pull/2113))
- ⚠️ **eslint-plugin:** remove deprecated sort-ngmodule-metadata-arrays
rule
([#&#8203;2114](https://redirect.github.com/angular-eslint/angular-eslint/pull/2114))
- ⚠️ **eslint-plugin:** prefer-standalone recognizes that standalone is
the default
([#&#8203;2096](https://redirect.github.com/angular-eslint/angular-eslint/pull/2096))
- ⚠️ **eslint-plugin:** remove deprecated prefer-standalone-component
rule
([#&#8203;2112](https://redirect.github.com/angular-eslint/angular-eslint/pull/2112))

##### ⚠️ Breaking Changes

- ⚠️ **eslint-plugin:** promote prefer-standalone to recommended
([8dfdc4f4](https://redirect.github.com/angular-eslint/angular-eslint/commit/8dfdc4f4))
- ⚠️ **eslint-plugin:** remove deprecated no-host-metadata-property rule
([#&#8203;2113](https://redirect.github.com/angular-eslint/angular-eslint/pull/2113))
- ⚠️ **eslint-plugin:** remove deprecated sort-ngmodule-metadata-arrays
rule
([#&#8203;2114](https://redirect.github.com/angular-eslint/angular-eslint/pull/2114))
- ⚠️ **eslint-plugin:** prefer-standalone recognizes that standalone is
the default
([#&#8203;2096](https://redirect.github.com/angular-eslint/angular-eslint/pull/2096))
- ⚠️ **eslint-plugin:** remove deprecated prefer-standalone-component
rule
([#&#8203;2112](https://redirect.github.com/angular-eslint/angular-eslint/pull/2112))

##### ❤️ Thank You

- Daniel Kimmich
[@&#8203;json-derulo](https://redirect.github.com/json-derulo)
-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)
- James Henry
[@&#8203;JamesHenry](https://redirect.github.com/JamesHenry)
- JamesHenry
[@&#8203;JamesHenry](https://redirect.github.com/JamesHenry)

#### 18.4.3 (2024-11-29)

This was a version bump only for eslint-plugin to align it with other
projects, there were no code changes.

#### 18.4.2 (2024-11-23)

##### 🩹 Fixes

- **eslint-plugin:** fix placement of lifecycle interface for subclasses
([#&#8203;1965](https://redirect.github.com/angular-eslint/angular-eslint/pull/1965))
- **eslint-plugin:** handle `output()` and `input()` functions in
various rules
([#&#8203;2098](https://redirect.github.com/angular-eslint/angular-eslint/pull/2098))

##### ❤️ Thank You

-   Aleksandr Martirosyan
-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)

#### 18.4.1 (2024-11-18)

This was a version bump only for eslint-plugin to align it with other
projects, there were no code changes.

#### 18.4.0 (2024-10-21)

This was a version bump only for eslint-plugin to align it with other
projects, there were no code changes.

#### 18.3.1 (2024-09-11)

This was a version bump only for eslint-plugin to align it with other
projects, there were no code changes.

#### 18.3.0 (2024-08-13)

##### 🚀 Features

-   **eslint-plugin:** new rule runtime-localize

##### ❤️ Thank You

-   m-akinc

#### 18.2.0 (2024-07-31)

##### 🚀 Features

-   update typescript-eslint to v8 stable, eslint v9.8.0

##### 🩹 Fixes

- **eslint-plugin:** \[prefer-standalone] ignore empty Directive
decorators

##### ❤️ Thank You

-   James Henry
-   Paweł Maniecki

#### 18.1.0 (2024-07-01)

##### 🚀 Features

- **eslint-plugin:** \[prefer-output-readonly] support output() function

##### 🩹 Fixes

-   update typescript-eslint packages to v8.0.0-alpha.37

##### ❤️ Thank You

-   Christian Svensson
-   Daniel Kimmich
-   Dave
-   Martijn van der Meij
-   Maximilian Main

#### 18.0.1 (2024-05-30)

##### 🩹 Fixes

- move typescript-eslint packages to peerDeps, consistently allow v7 and
v8

##### ❤️ Thank You

-   James Henry

</details>

<details>
<summary>angular-eslint/angular-eslint
(@&#8203;angular-eslint/eslint-plugin-template)</summary>

###
[`v19.3.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin-template/CHANGELOG.md#1930-2025-03-22)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.1...v19.3.0)

##### 🚀 Features

- use
[@&#8203;angular/compiler](https://redirect.github.com/angular/compiler)
19.2.3 and rename some AST nodes to match
([#&#8203;2320](https://redirect.github.com/angular-eslint/angular-eslint/pull/2320))
- **eslint-plugin-template:** add rule prefer-contextual-for-variables
([#&#8203;2311](https://redirect.github.com/angular-eslint/angular-eslint/pull/2311))
- **eslint-plugin-template:** \[button-has-type] add option to ignore
missing type
([#&#8203;2326](https://redirect.github.com/angular-eslint/angular-eslint/pull/2326))

##### 🩹 Fixes

- **eslint-plugin-template:** \[attributes-order] treat inputs without
square brackets as attributes
([#&#8203;2316](https://redirect.github.com/angular-eslint/angular-eslint/pull/2316))
- **eslint-plugin-template:** \[attributes-order] order i18n attributes
([#&#8203;2307](https://redirect.github.com/angular-eslint/angular-eslint/pull/2307))
- **eslint-plugin-template:** \[i18n] Avoid exception in i18n rule with
allowMarkupInContent=false
([#&#8203;2327](https://redirect.github.com/angular-eslint/angular-eslint/pull/2327))

##### ❤️ Thank You

-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)
-   m-akinc [@&#8203;m-akinc](https://redirect.github.com/m-akinc)

###
[`v19.2.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin-template/CHANGELOG.md#1921-2025-03-08)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.0...v19.2.1)

##### 🩹 Fixes

- **eslint-plugin-template:** \[prefer-self-closing-tags] resolve wrong
reports when structural directive + no content + no self-closing
([#&#8203;2287](https://redirect.github.com/angular-eslint/angular-eslint/pull/2287))

##### ❤️ Thank You

-   Guillaume DROUARD

###
[`v19.2.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin-template/CHANGELOG.md#1920-2025-03-02)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.1.0...v19.2.0)

##### 🩹 Fixes

- **prefer-static-string-properties:** exclude special attributes
([#&#8203;2273](https://redirect.github.com/angular-eslint/angular-eslint/pull/2273))
- **prefer-static-string-properties:** resolve bug with directives
([#&#8203;2271](https://redirect.github.com/angular-eslint/angular-eslint/pull/2271))
- **eslint-plugin-template:** find inline templates on components in
blocks
([#&#8203;2238](https://redirect.github.com/angular-eslint/angular-eslint/pull/2238))
- **eslint-plugin-template:** \[prefer-static-string-properties] do not
check structural directives
([#&#8203;2253](https://redirect.github.com/angular-eslint/angular-eslint/pull/2253))
- **eslint-plugin-template:** \[prefer-self-closing-tags] allow nested
ng-content
([#&#8203;2257](https://redirect.github.com/angular-eslint/angular-eslint/pull/2257))
- **eslint-plugin-template:** \[prefer-self-closing-tags] do not treat
comments as whitespace
([#&#8203;2256](https://redirect.github.com/angular-eslint/angular-eslint/pull/2256))

##### ❤️ Thank You

-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)
- Marie Briand
[@&#8203;mbriand-lucca](https://redirect.github.com/mbriand-lucca)

###
[`v19.1.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin-template/CHANGELOG.md#1910-2025-02-09)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.2...v19.1.0)

##### 🚀 Features

- **eslint-plugin-template:** add rule prefer-static-string-properties
([#&#8203;2228](https://redirect.github.com/angular-eslint/angular-eslint/pull/2228))

##### 🩹 Fixes

- **eslint-plugin-template:** \[attribute-order] check for ng-template
within svg
([#&#8203;2223](https://redirect.github.com/angular-eslint/angular-eslint/pull/2223))
- **eslint-plugin-template:** \[prefer-self-closing-tags] do not remove
HTML-encoded whitespace
([#&#8203;2229](https://redirect.github.com/angular-eslint/angular-eslint/pull/2229))

##### ❤️ Thank You

-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)

###
[`v19.0.2`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin-template/CHANGELOG.md#1902-2024-12-10)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.1...v19.0.2)

This was a version bump only for eslint-plugin-template to align it with
other projects, there were no code changes.

###
[`v19.0.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin-template/CHANGELOG.md#1901-2024-12-06)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.0...v19.0.1)

##### 🩹 Fixes

- **eslint-plugin-template:** prevent the slot tag from being
self-closing
([#&#8203;2088](https://redirect.github.com/angular-eslint/angular-eslint/pull/2088))

##### ❤️ Thank You

- Joan Llenas
[@&#8203;joanllenas](https://redirect.github.com/joanllenas)

###
[`v19.0.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/eslint-plugin-template/CHANGELOG.md#1900-2024-11-29)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v18.4.3...v19.0.0)

This was a version bump only for eslint-plugin-template to align it with
other projects, there were no code changes.

#### 18.4.3 (2024-11-29)

This was a version bump only for eslint-plugin-template to align it with
other projects, there were no code changes.

#### 18.4.2 (2024-11-23)

This was a version bump only for eslint-plugin-template to align it with
other projects, there were no code changes.

#### 18.4.1 (2024-11-18)

This was a version bump only for eslint-plugin-template to align it with
other projects, there were no code changes.

#### 18.4.0 (2024-10-21)

##### 🩹 Fixes

- update typescript-eslint packages to v8.10.0
([#&#8203;2046](https://redirect.github.com/angular-eslint/angular-eslint/pull/2046))
- update dependency aria-query to v5.3.2
([#&#8203;2053](https://redirect.github.com/angular-eslint/angular-eslint/pull/2053))
- update dependency aria-query to v5.3.1
([#&#8203;2043](https://redirect.github.com/angular-eslint/angular-eslint/pull/2043))

#### 18.3.1 (2024-09-11)

##### 🩹 Fixes

-   **template-parser:** visit receiver of Call expression

-   **template-parser:** visit receiver of Call expression"

-   **template-parser:** visit receiver of Call expression

##### ❤️ Thank You

-   James Henry
-   Paweł Maniecki

#### 18.3.0 (2024-08-13)

##### 🩹 Fixes

- **eslint-plugin-template:** \[interactive-supports-focus] allowList
with form as default option to support event bubbling

- **eslint-plugin-template:** \[prefer-self-closing-tags] fix ng-content
with rich default content

-   **prefer-self-closing-tags:** handle both forward and backward slash

##### ❤️ Thank You

-   Daniel Kimmich
-   Sandi Barr
-   Simon

#### 18.2.0 (2024-07-31)

##### 🚀 Features

-   update typescript-eslint to v8 stable, eslint v9.8.0

##### 🩹 Fixes

-   update dependency axobject-query to v4.1.0

- **eslint-plugin-template:** add meta to preprocessor to resolve eslint
cache error

##### ❤️ Thank You

-   James Henry
-   kwiateusz

#### 18.1.0 (2024-07-01)

##### 🚀 Features

- **eslint-plugin:** \[no-call-expression] add allowPrefix and
allowSuffix

##### 🩹 Fixes

-   update typescript-eslint packages to v8.0.0-alpha.37

- **eslint-plugin-template:** \[prefer-self-closing-tags] always ignore
index.html files

- **eslint-plugin-template:** \[prefer-self-closing-tags] support
ng-content with fallback content

##### ❤️ Thank You

-   Christian Svensson
-   Daniel Kimmich
-   Dave
-   Martijn van der Meij
-   Maximilian Main

#### 18.0.1 (2024-05-30)

##### 🩹 Fixes

- move typescript-eslint packages to peerDeps, consistently allow v7 and
v8

##### ❤️ Thank You

-   James Henry

</details>

<details>
<summary>angular-eslint/angular-eslint
(@&#8203;angular-eslint/schematics)</summary>

###
[`v19.3.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/schematics/CHANGELOG.md#1930-2025-03-22)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.1...v19.3.0)

##### 🩹 Fixes

- update dependency eslint to v9.23.0
([#&#8203;2331](https://redirect.github.com/angular-eslint/angular-eslint/pull/2331))
- update typescript-eslint packages to v8.27.0
([#&#8203;2328](https://redirect.github.com/angular-eslint/angular-eslint/pull/2328))
- update typescript-eslint packages to v8.26.1
([#&#8203;2313](https://redirect.github.com/angular-eslint/angular-eslint/pull/2313))

###
[`v19.2.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/schematics/CHANGELOG.md#1921-2025-03-08)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.0...v19.2.1)

##### 🩹 Fixes

- update dependency eslint to v9.22.0
([#&#8203;2294](https://redirect.github.com/angular-eslint/angular-eslint/pull/2294))
- update typescript-eslint packages to v8.26.0
([#&#8203;2282](https://redirect.github.com/angular-eslint/angular-eslint/pull/2282))

###
[`v19.2.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/schematics/CHANGELOG.md#1920-2025-03-02)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.1.0...v19.2.0)

##### 🩹 Fixes

- update typescript-eslint packages to v8.25.0
([#&#8203;2263](https://redirect.github.com/angular-eslint/angular-eslint/pull/2263))
- update dependency eslint to v9.21.0
([#&#8203;2243](https://redirect.github.com/angular-eslint/angular-eslint/pull/2243))
- **eslint-plugin-template:** find inline templates on components in
blocks
([#&#8203;2238](https://redirect.github.com/angular-eslint/angular-eslint/pull/2238))
- update typescript-eslint packages to v8.24.0
([#&#8203;2240](https://redirect.github.com/angular-eslint/angular-eslint/pull/2240))

##### ❤️ Thank You

-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)

###
[`v19.1.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/schematics/CHANGELOG.md#1910-2025-02-09)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.2...v19.1.0)

##### 🩹 Fixes

- update dependency eslint to v9.20.0
([#&#8203;2217](https://redirect.github.com/angular-eslint/angular-eslint/pull/2217))
- update typescript-eslint packages to v8.23.0
([#&#8203;2212](https://redirect.github.com/angular-eslint/angular-eslint/pull/2212))
- update dependency semver to v7.7.1
([#&#8203;2225](https://redirect.github.com/angular-eslint/angular-eslint/pull/2225))
- update typescript-eslint packages to v8.20.0
([#&#8203;2185](https://redirect.github.com/angular-eslint/angular-eslint/pull/2185))
- update dependency eslint to v9.18.0
([#&#8203;2181](https://redirect.github.com/angular-eslint/angular-eslint/pull/2181))
- update dependency ignore to v7
([#&#8203;2200](https://redirect.github.com/angular-eslint/angular-eslint/pull/2200))

###
[`v19.0.2`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/schematics/CHANGELOG.md#1902-2024-12-10)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.1...v19.0.2)

##### 🩹 Fixes

- update typescript-eslint packages to v8.18.0
([#&#8203;2171](https://redirect.github.com/angular-eslint/angular-eslint/pull/2171))

###
[`v19.0.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/schematics/CHANGELOG.md#1901-2024-12-06)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.0...v19.0.1)

##### 🩹 Fixes

- update typescript-eslint packages to v8.17.0
([#&#8203;2153](https://redirect.github.com/angular-eslint/angular-eslint/pull/2153))
- update dependency eslint to v9.16.0
([#&#8203;2148](https://redirect.github.com/angular-eslint/angular-eslint/pull/2148))

###
[`v19.0.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/schematics/CHANGELOG.md#1900-2024-11-29)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v18.4.3...v19.0.0)

##### 🚀 Features

- allow referencing angular-eslint as the schematics collection
([2be3107b](https://redirect.github.com/angular-eslint/angular-eslint/commit/2be3107b))
- update angular packages to the stable v19
([#&#8203;2120](https://redirect.github.com/angular-eslint/angular-eslint/pull/2120))

##### ❤️ Thank You

- JamesHenry
[@&#8203;JamesHenry](https://redirect.github.com/JamesHenry)
- Leosvel Pérez Espinosa
[@&#8203;leosvelperez](https://redirect.github.com/leosvelperez)

#### 18.4.3 (2024-11-29)

##### 🩹 Fixes

- update typescript-eslint packages to v8.16.0
([#&#8203;2135](https://redirect.github.com/angular-eslint/angular-eslint/pull/2135))
- yarn pnp issues
([#&#8203;2143](https://redirect.github.com/angular-eslint/angular-eslint/pull/2143))

##### ❤️ Thank You

- James Henry
[@&#8203;JamesHenry](https://redirect.github.com/JamesHenry)

#### 18.4.2 (2024-11-23)

This was a version bump only for schematics to align it with other
projects, there were no code changes.

#### 18.4.1 (2024-11-18)

##### 🩹 Fixes

- update dependency ignore to v6
([#&#8203;2047](https://redirect.github.com/angular-eslint/angular-eslint/pull/2047))

#### 18.4.0 (2024-10-21)

##### 🚀 Features

- support ESM configs and .cjs and .mjs extensions
([#&#8203;2068](https://redirect.github.com/angular-eslint/angular-eslint/pull/2068))

##### 🩹 Fixes

- update dependency eslint to v9.13.0, support noConfigLookup
([#&#8203;2045](https://redirect.github.com/angular-eslint/angular-eslint/pull/2045))
- update typescript-eslint packages to v8.10.0
([#&#8203;2046](https://redirect.github.com/angular-eslint/angular-eslint/pull/2046))
- update typescript-eslint packages to v8.5.0
([#&#8203;2020](https://redirect.github.com/angular-eslint/angular-eslint/pull/2020))

##### ❤️ Thank You

- James Henry
[@&#8203;JamesHenry](https://redirect.github.com/JamesHenry)

#### 18.3.1 (2024-09-11)

##### 🩹 Fixes

-   update dependency eslint to v9.9.1

-   update typescript-eslint packages to v8.2.0

#### 18.3.0 (2024-08-13)

##### 🩹 Fixes

-   ensure consistent nx dependency versions

-   update dependency eslint to v9.9.0

-   update dependency ignore to v5.3.2

-   update typescript-eslint packages to v8.0.1

-   update typescript-eslint packages to v8.1.0

##### ❤️ Thank You

-   James Henry

#### 18.2.0 (2024-07-31)

##### 🚀 Features

-   update typescript-eslint to v8 stable, eslint v9.8.0

##### 🩹 Fixes

-   update dependency semver to v7.6.3

##### ❤️ Thank You

-   James Henry

#### 18.1.0 (2024-07-01)

##### 🩹 Fixes

-   update dependency eslint to v9.4.0

-   update dependency eslint to v9.5.0

-   update dependency eslint to v9.6.0

-   update typescript-eslint packages to v8.0.0-alpha.37

-   update typescript-eslint packages to v8.0.0-alpha.38

##### ❤️ Thank You

-   Christian Svensson
-   Daniel Kimmich
-   Dave
-   Martijn van der Meij
-   Maximilian Main

#### 18.0.1 (2024-05-30)

This was a version bump only for schematics to align it with other
projects, there were no code changes.

</details>

<details>
<summary>angular-eslint/angular-eslint
(@&#8203;angular-eslint/template-parser)</summary>

###
[`v19.3.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/template-parser/CHANGELOG.md#1930-2025-03-22)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.1...v19.3.0)

##### 🚀 Features

- use
[@&#8203;angular/compiler](https://redirect.github.com/angular/compiler)
19.2.3 and rename some AST nodes to match
([#&#8203;2320](https://redirect.github.com/angular-eslint/angular-eslint/pull/2320))
- **template-parser:** visit
[@&#8203;let](https://redirect.github.com/let) child nodes
([#&#8203;2312](https://redirect.github.com/angular-eslint/angular-eslint/pull/2312))

##### ❤️ Thank You

-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)

###
[`v19.2.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/template-parser/CHANGELOG.md#1921-2025-03-08)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.2.0...v19.2.1)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

###
[`v19.2.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/template-parser/CHANGELOG.md#1920-2025-03-02)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.1.0...v19.2.0)

##### 🩹 Fixes

- **eslint-plugin-template:** find inline templates on components in
blocks
([#&#8203;2238](https://redirect.github.com/angular-eslint/angular-eslint/pull/2238))

##### ❤️ Thank You

-   Dave [@&#8203;reduckted](https://redirect.github.com/reduckted)

###
[`v19.1.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/template-parser/CHANGELOG.md#1910-2025-02-09)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.2...v19.1.0)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

###
[`v19.0.2`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/template-parser/CHANGELOG.md#1902-2024-12-10)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.1...v19.0.2)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

###
[`v19.0.1`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/template-parser/CHANGELOG.md#1901-2024-12-06)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v19.0.0...v19.0.1)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

###
[`v19.0.0`](https://redirect.github.com/angular-eslint/angular-eslint/blob/HEAD/packages/template-parser/CHANGELOG.md#1900-2024-11-29)

[Compare
Source](https://redirect.github.com/angular-eslint/angular-eslint/compare/v18.4.3...v19.0.0)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

#### 18.4.3 (2024-11-29)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

#### 18.4.2 (2024-11-23)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

#### 18.4.1 (2024-11-18)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

#### 18.4.0 (2024-10-21)

##### 🩹 Fixes

- **template-parser:** traverse ng-content fallback content
([#&#8203;2031](https://redirect.github.com/angular-eslint/angular-eslint/pull/2031))

##### ❤️ Thank You

- Matt Lewis
[@&#8203;mattlewis92](https://redirect.github.com/mattlewis92)

#### 18.3.1 (2024-09-11)

##### 🩹 Fixes

-   **template-parser:** visit receiver of Call expression

-   **template-parser:** visit receiver of Call expression"

-   **template-parser:** visit receiver of Call expression

##### ❤️ Thank You

-   James Henry
-   Paweł Maniecki

#### 18.3.0 (2024-08-13)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

#### 18.2.0 (2024-07-31)

##### 🚀 Features

-   update typescript-eslint to v8 stable, eslint v9.8.0

##### ❤️ Thank You

-   James Henry

#### 18.1.0 (2024-07-01)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

#### 18.0.1 (2024-05-30)

This was a version bump only for template-parser to align it with other
projects, there were no code changes.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNDUuMCIsInVwZGF0ZWRJblZlciI6IjM5LjIwNy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 12:20:57 +02:00
OpenFeature Bot 7f81917226
chore(main): release angular-sdk 0.0.11 (#1167)
🤖 I have created a release *beep* *boop*
---


##
[0.0.11](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.10...angular-sdk-v0.0.11)
(2025-04-11)


###  New Features

* **angular:** add option for initial context injection
([aafdb43](aafdb4382f))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-04-11 12:08:35 +02:00
Lukas Reining aafdb4382f feat(angular): add option for initial context injection
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
2025-04-11 12:02:02 +02:00
OpenFeature Bot b60c3df372
chore(main): release core 1.8.0 (#1155)
🤖 I have created a release *beep* *boop*
---


##
[1.8.0](https://github.com/open-feature/js-sdk/compare/core-v1.7.2...core-v1.8.0)
(2025-04-10)


###  New Features

* add support for abort controllers to event handlers
([#1151](https://github.com/open-feature/js-sdk/issues/1151))
([6a22483](6a224830fa))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-04-10 13:52:28 +00:00
Todd Baert 5b19eb035a
chore: use server src not dist in nest tests (#1166)
Fixes issue where nest test suite was running using dist not source of
server sdk.

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-10 14:39:39 +02:00
Michael Beemer 5afe61f9e3
feat: add polyfill for react use hook (#1157)
## This PR

- adds an internal `use` polyfill
- refactors suspense support to maintain state across rerenders

### Notes

Previously, the Next.JS build process would timeout when using a
suspense flag hook. The reason for this is because the noop provider
(which is used during a build) is never ready. That meant that the
promise thrown to initiate suspense never resolved. To address this,
I've added global state using a weak map that's keyed off a provider. A
`useRef` won't help because the value is only retained if the component
renders successfully.

> [!NOTE]
> This unblocks suspense in our demo app but does not mean NextJS is
officially support (yet).

### How to test

I've added some tests and manually tested in the Toggle Shop.

---------

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-10 12:06:19 +00:00
Todd Baert 60401b6cec
chore: prompt release of react v1 (#1162)
This proposes prompting the release of `@openfeature/react-sdk` 1.0.0.

My reasoning:

- as far as I know, there's no breaking changes on the horizon
- this is being used in production by multiple orgs
- it only depends on other 1.0+ deps

Please let me know what you think, especially if you have objections.

⚠️ this will not release 1.0, but will mark the NEXT release of
the react SDK as 1.0

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-07 06:46:59 +00:00
Simon Schrottner 4482c2b33a
chore: add global maintainers to codeowners (#1163)
Global maintainers approval should also work as a codeowner approval

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-04-05 16:18:44 +00:00
Thomas Poignant 2c5b37c79d
fix: Typo in name of the function
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2025-03-28 17:37:58 +01:00
Todd Baert cf89e7da24
chore: add nest to test script (#1160)
Somehow we forgot to add this module here.

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-25 13:29:46 -04:00
Michael Beemer ae8fce8753
feat: add a top-level method for accessing providers (#1152)
## This PR

- add a top-level method for accessing providers

### Notes

While working on some improvements to the way Suspense works in the
React SDK, I ran into a few scenarios were having access to the provider
itself was important. I needed a way to confidently tell that a provider
was the noop provider since it has special properties like never being
in a ready state. There are a few ways could could achieve this but I
noticed that the Java SDK [already has
methods](https://github.com/open-feature/java-sdk/blob/main/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java#L279)
that expose the provider. It also allowed me to improve some of our
existing tests.

### How to test

Unit tests have been updated accordingly.

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-03-06 20:50:06 +00:00
renovate[bot] eec21dda82
chore(deps): update dependency uuid to v11 (#1073)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [uuid](https://redirect.github.com/uuidjs/uuid) | [`^9.0.1` ->
`^11.0.0`](https://renovatebot.com/diffs/npm/uuid/9.0.1/11.1.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/uuid/11.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/uuid/11.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/uuid/9.0.1/11.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/uuid/9.0.1/11.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>uuidjs/uuid (uuid)</summary>

###
[`v11.1.0`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1110-2025-02-19)

[Compare
Source](https://redirect.github.com/uuidjs/uuid/compare/v11.0.5...v11.1.0)

##### Features

- update TS types to allow`Uint8Array` subtypes for `buffer` option
([#&#8203;865](https://redirect.github.com/uuidjs/uuid/issues/865))
([a5231e7](a5231e7e1b))

###
[`v11.0.5`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1105-2025-01-09)

[Compare
Source](https://redirect.github.com/uuidjs/uuid/compare/v11.0.4...v11.0.5)

##### Bug Fixes

- add TS unit test, pin to typescript@5.0.4
([#&#8203;860](https://redirect.github.com/uuidjs/uuid/issues/860))
([24ac2fd](24ac2fd067))

###
[`v11.0.4`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1104-2025-01-05)

[Compare
Source](https://redirect.github.com/uuidjs/uuid/compare/v11.0.3...v11.0.4)

##### Bug Fixes

- **docs:** insure -> ensure
([#&#8203;843](https://redirect.github.com/uuidjs/uuid/issues/843))
([d2a61e1](d2a61e154d))
- exclude tests from published package
([#&#8203;840](https://redirect.github.com/uuidjs/uuid/issues/840))
([f992ff4](f992ff4780))
- Test for invalid byte array sizes and ranges in `v1()`, `v4()`, and
`v7()`
([#&#8203;845](https://redirect.github.com/uuidjs/uuid/issues/845))
([e0ee900](e0ee90051e))

###
[`v11.0.3`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1103-2024-11-04)

[Compare
Source](https://redirect.github.com/uuidjs/uuid/compare/v11.0.2...v11.0.3)

##### Bug Fixes

- apply stricter typing to the v\* signatures
([#&#8203;831](https://redirect.github.com/uuidjs/uuid/issues/831))
([c2d3fed](c2d3fed22c))
- export internal uuid types
([#&#8203;833](https://redirect.github.com/uuidjs/uuid/issues/833))
([341edf4](341edf444c))
- remove sourcemaps
([#&#8203;827](https://redirect.github.com/uuidjs/uuid/issues/827))
([b93ea10](b93ea101af))
- revert "simplify type for v3 and v5"
([#&#8203;835](https://redirect.github.com/uuidjs/uuid/issues/835))
([e2dee69](e2dee691e9))

###
[`v11.0.2`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1102-2024-10-28)

[Compare
Source](https://redirect.github.com/uuidjs/uuid/compare/v11.0.1...v11.0.2)

##### Bug Fixes

- remove wrapper.mjs
([2a18871](2a18871f00))
- remove wrapper.mjs
([#&#8203;822](https://redirect.github.com/uuidjs/uuid/issues/822))
([6683ad3](6683ad38b0))

###
[`v11.0.1`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1101-2024-10-27)

[Compare
Source](https://redirect.github.com/uuidjs/uuid/compare/v11.0.0...v11.0.1)

##### Bug Fixes

- restore package.json#browser field
([#&#8203;817](https://redirect.github.com/uuidjs/uuid/issues/817))
([ae8f386](ae8f38657b))

###
[`v11.0.0`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1100-2024-10-27)

[Compare
Source](https://redirect.github.com/uuidjs/uuid/compare/v10.0.0...v11.0.0)

##### ⚠ BREAKING CHANGES

- refactor v1 internal state and options logic
([#&#8203;780](https://redirect.github.com/uuidjs/uuid/issues/780))
- refactor v7 internal state and options logic, fixes
[#&#8203;764](https://redirect.github.com/uuidjs/uuid/issues/764)
([#&#8203;779](https://redirect.github.com/uuidjs/uuid/issues/779))
- Port to TypeScript, closes
[#&#8203;762](https://redirect.github.com/uuidjs/uuid/issues/762)
([#&#8203;763](https://redirect.github.com/uuidjs/uuid/issues/763))
- update node support matrix (only support node 16-20)
([#&#8203;750](https://redirect.github.com/uuidjs/uuid/issues/750))
- This library always aims at supporting one EOLed LTS release which by
this time now is 12.x which has reached EOL 30 Apr 2022.
-   Remove the minified UMD build from the package.
- Drop support for browsers that don't correctly implement const/let and
default arguments, and no longer transpile the browser build to ES2015.
- Although in practice this is currently a noop since the resulting
build does not change, the build will no longer transpiles future
changes for Node.js 8.x targets, so semantically this is still a
breaking change.
- Deep requiring specific algorithms of this library like
require('uuid/v4'), which has been deprecated in uuid@7, is no longer
supported.
- The default export, which used to be the v4() method but which was
already discouraged in v3.x of this library, has been removed.
- Explicitly note that deep imports of the different uuid version
functions are deprecated and no longer encouraged and that ECMAScript
module named imports should be used instead. Emit a deprecation warning
for people who deep-require the different algorithm variants.
- Remove builtin support for insecure random number generators in the
browser. Users who want that will have to supply their own random number
generator function.
-   Remove support for generating v3 and v5 UUIDs in Node.js<4.x
- Convert code base to ECMAScript Modules (ESM) and release CommonJS
build for node and ESM build for browser bundlers.

##### Features

- add parse/stringify/validate/version/NIL APIs
([#&#8203;479](https://redirect.github.com/uuidjs/uuid/issues/479))
([0e6c10b](0e6c10ba1b))
- add support for MAX uuid (new in RFC9562)
([#&#8203;714](https://redirect.github.com/uuidjs/uuid/issues/714))
([0385cd3](0385cd3f18))
- add UMD build to npm package
([#&#8203;357](https://redirect.github.com/uuidjs/uuid/issues/357))
([4e75adf](4e75adf435))
- add various es module and CommonJS examples
([b238510](b238510bf3))
- enforce Conventional Commit style commit messages
([#&#8203;282](https://redirect.github.com/uuidjs/uuid/issues/282))
([0705cd5](0705cd5bae))
- ensure that docs are up-to-date in CI
([ee5e77d](ee5e77db54))
- hybrid CommonJS & ECMAScript modules build
([a3f078f](a3f078faa0))
- improve performance of v1 string representation
([#&#8203;453](https://redirect.github.com/uuidjs/uuid/issues/453))
([0ee0b67](0ee0b67c37))
- improve v4 performance by reusing random number array
([#&#8203;435](https://redirect.github.com/uuidjs/uuid/issues/435))
([bf4af0d](bf4af0d711))
- optimize uuid.v1 by 1.3x uuid.v4 by 4.3x (430%)
([#&#8203;597](https://redirect.github.com/uuidjs/uuid/issues/597))
([3a033f6](3a033f6bab))
- optimize V8 performance of bytesToUuid
([#&#8203;434](https://redirect.github.com/uuidjs/uuid/issues/434))
([e156415](e156415448))
- Port to TypeScript, closes
[#&#8203;762](https://redirect.github.com/uuidjs/uuid/issues/762)
([#&#8203;763](https://redirect.github.com/uuidjs/uuid/issues/763))
([1e0f987](1e0f9870db))
- remove deep requires
([#&#8203;426](https://redirect.github.com/uuidjs/uuid/issues/426))
([daf72b8](daf72b84ce))
- remove deprecated v4 string parameter
([#&#8203;454](https://redirect.github.com/uuidjs/uuid/issues/454))
([88ce3ca](88ce3ca0ba))
- remove insecure fallback random number generator
([3a5842b](3a5842b141))
- remove support for pre Node.js v4 Buffer API
([#&#8203;356](https://redirect.github.com/uuidjs/uuid/issues/356))
([b59b5c5](b59b5c5eca))
- remove UMD build
([#&#8203;645](https://redirect.github.com/uuidjs/uuid/issues/645))
([e948a0f](e948a0f22b)),
closes [#&#8203;620](https://redirect.github.com/uuidjs/uuid/issues/620)
- rename repository to github:uuidjs/uuid
([#&#8203;351](https://redirect.github.com/uuidjs/uuid/issues/351))
([c37a518](c37a518e36)),
closes [#&#8203;338](https://redirect.github.com/uuidjs/uuid/issues/338)
- rename repository to github:uuidjs/uuid
([#&#8203;351](https://redirect.github.com/uuidjs/uuid/issues/351))
([e2d7314](e2d731463b)),
closes [#&#8203;338](https://redirect.github.com/uuidjs/uuid/issues/338)
- support v6 uuids
([#&#8203;754](https://redirect.github.com/uuidjs/uuid/issues/754))
([c4ed13e](c4ed13e715))
- update node support matrix (only support node 16-20)
([#&#8203;750](https://redirect.github.com/uuidjs/uuid/issues/750))
([883b163](883b163b9a))
- use native crypto.randomUUID when available
([#&#8203;600](https://redirect.github.com/uuidjs/uuid/issues/600))
([c9e076c](c9e076c852))
- v8 support
([#&#8203;759](https://redirect.github.com/uuidjs/uuid/issues/759))
([35a5342](35a5342820))

##### Bug Fixes

- 248
([#&#8203;251](https://redirect.github.com/uuidjs/uuid/issues/251))
([67d697c](67d697cd83))
- 30, \_rb not defined for lesser node.js versions
([8a6c03f](8a6c03f969))
- add CommonJS syntax example to README quickstart section
([#&#8203;417](https://redirect.github.com/uuidjs/uuid/issues/417))
([e0ec840](e0ec8402c7))
- add deep-require proxies for local testing and adjust tests
([#&#8203;365](https://redirect.github.com/uuidjs/uuid/issues/365))
([7fedc79](7fedc79ac8))
- add Jest/jsdom compatibility
([#&#8203;642](https://redirect.github.com/uuidjs/uuid/issues/642))
([16f9c46](16f9c469ed))
- add missing exports and tests for new APIs
([#&#8203;495](https://redirect.github.com/uuidjs/uuid/issues/495))
([681e1da](681e1dabfb))
- assignment to readonly property to allow running in strict mode
([#&#8203;270](https://redirect.github.com/uuidjs/uuid/issues/270))
([d062fdc](d062fdc14a))
- change default export to named function
([#&#8203;545](https://redirect.github.com/uuidjs/uuid/issues/545))
([c57bc5a](c57bc5a9a0))
- clean up esm builds for node and browser
([#&#8203;383](https://redirect.github.com/uuidjs/uuid/issues/383))
([59e6a49](59e6a49e7c))
- export package.json required by react-native and bundlers
([#&#8203;449](https://redirect.github.com/uuidjs/uuid/issues/449))
([be1c8fe](be1c8fe9a3)),
closes [#&#8203;444](https://redirect.github.com/uuidjs/uuid/issues/444)
- fix [#&#8203;229](https://redirect.github.com/uuidjs/uuid/issues/229)
([d9033cf](d9033cf358))
- fix [#&#8203;284](https://redirect.github.com/uuidjs/uuid/issues/284)
by setting function name in try-catch
([f2a60f2](f2a60f2fcd))
- Get correct version of IE11 crypto
([#&#8203;274](https://redirect.github.com/uuidjs/uuid/issues/274))
([205e0ed](205e0ed1f7))
- handle error when parameter is not set in v3 and v5
([#&#8203;622](https://redirect.github.com/uuidjs/uuid/issues/622))
([fcd7388](fcd7388169))
- lazy load getRandomValues
([#&#8203;537](https://redirect.github.com/uuidjs/uuid/issues/537))
([16c8f6d](16c8f6df2f)),
closes [#&#8203;536](https://redirect.github.com/uuidjs/uuid/issues/536)
- make access to msCrypto consistent
([#&#8203;393](https://redirect.github.com/uuidjs/uuid/issues/393))
([8bf2a20](8bf2a20f35))
- make deep require deprecation warning work in browsers
([#&#8203;409](https://redirect.github.com/uuidjs/uuid/issues/409))
([4b71107](4b71107d8c))
- mem issue when generating uuid
([#&#8203;267](https://redirect.github.com/uuidjs/uuid/issues/267))
([c47702c](c47702c291))
- missing v7 expectations in browser spec
([#&#8203;751](https://redirect.github.com/uuidjs/uuid/issues/751))
([f54a866](f54a866ced))
- prepare package exports for webpack 5
([#&#8203;468](https://redirect.github.com/uuidjs/uuid/issues/468))
([8d6e6a5](8d6e6a5f89))
- provide browser versions independent from module system
([#&#8203;380](https://redirect.github.com/uuidjs/uuid/issues/380))
([4344a22](4344a22e7a))
- refactor v1 internal state and options logic
([#&#8203;780](https://redirect.github.com/uuidjs/uuid/issues/780))
([031b3d3](031b3d3d73))
- refactor v7 internal state and options logic, fixes
[#&#8203;764](https://redirect.github.com/uuidjs/uuid/issues/764)
([#&#8203;779](https://redirect.github.com/uuidjs/uuid/issues/779))
([9dbd1cd](9dbd1cd417))
- remove v4 options default assignment preventing native.randomUUID from
being used
([#&#8203;786](https://redirect.github.com/uuidjs/uuid/issues/786))
([afe6232](afe62323c4))
- revert "perf: remove superfluous call to toLowerCase
([#&#8203;677](https://redirect.github.com/uuidjs/uuid/issues/677))"
([#&#8203;738](https://redirect.github.com/uuidjs/uuid/issues/738))
([e267b90](e267b9073d))
- run npm audit fix
([#&#8203;644](https://redirect.github.com/uuidjs/uuid/issues/644))
([04686f5](04686f54c5))
- seq_hi shift for byte 6
([#&#8203;775](https://redirect.github.com/uuidjs/uuid/issues/775))
([1d532ca](1d532ca374))
- simplify link in deprecation warning
([#&#8203;391](https://redirect.github.com/uuidjs/uuid/issues/391))
([bb2c8e4](bb2c8e4e9f))
- support expo>=39.0.0
([#&#8203;515](https://redirect.github.com/uuidjs/uuid/issues/515))
([c65a0f3](c65a0f3fa7)),
closes [#&#8203;375](https://redirect.github.com/uuidjs/uuid/issues/375)
- tsconfig module type
([#&#8203;778](https://redirect.github.com/uuidjs/uuid/issues/778))
([7eff835](7eff835cba))
- typo
([305d877](305d877790))
- update links to match content in readme
([#&#8203;386](https://redirect.github.com/uuidjs/uuid/issues/386))
([44f2f86](44f2f86e9d))
- upgrading from uuid3 broken link
([#&#8203;568](https://redirect.github.com/uuidjs/uuid/issues/568))
([1c849da](1c849da6e1))
- use msCrypto if available. Fixes
[#&#8203;241](https://redirect.github.com/uuidjs/uuid/issues/241)
([#&#8203;247](https://redirect.github.com/uuidjs/uuid/issues/247))
([1fef18b](1fef18baf2))

##### Performance Improvements

- **nodejs:** introduce pool into default rng
([#&#8203;513](https://redirect.github.com/uuidjs/uuid/issues/513))
([7f1af04](7f1af044be))
- remove superfluous call to toLowerCase
([#&#8203;677](https://redirect.github.com/uuidjs/uuid/issues/677))
([e53793f](e53793f5be))

##### Documentation

- add note about removal of default export
([#&#8203;372](https://redirect.github.com/uuidjs/uuid/issues/372))
([12749b7](12749b700e)),
closes [#&#8203;370](https://redirect.github.com/uuidjs/uuid/issues/370)
- deprecated deep requiring of the different algorithm versions
([#&#8203;361](https://redirect.github.com/uuidjs/uuid/issues/361))
([c0bdf15](c0bdf15e41))

##### Miscellaneous Chores

- drop node 10.x to upgrade dev dependencies
([#&#8203;653](https://redirect.github.com/uuidjs/uuid/issues/653))
([28a5712](28a571283f))
- release 11.0.0
([#&#8203;805](https://redirect.github.com/uuidjs/uuid/issues/805))
([b003cde](b003cdeda4))

##### Build System

- drop Node.js 8.x from babel transpile target
([#&#8203;603](https://redirect.github.com/uuidjs/uuid/issues/603))
([aa11485](aa11485826))
- drop support for legacy browsers (IE11, Safari 10)
([#&#8203;604](https://redirect.github.com/uuidjs/uuid/issues/604))
([0f433e5](0f433e5ec4))

###
[`v10.0.0`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1000-2024-06-07)

[Compare
Source](https://redirect.github.com/uuidjs/uuid/compare/v9.0.1...v10.0.0)

##### ⚠ BREAKING CHANGES

- update node support (drop node@12, node@14, add node@20)
([#&#8203;750](https://redirect.github.com/uuidjs/uuid/issues/750))

##### Features

- support support rfc9562 MAX uuid (new in RFC9562)
([#&#8203;714](https://redirect.github.com/uuidjs/uuid/issues/714))
([0385cd3](0385cd3f18))
- support rfc9562 v6 uuids
([#&#8203;754](https://redirect.github.com/uuidjs/uuid/issues/754))
([c4ed13e](c4ed13e715))
- support rfc9562 v7 uuids
([#&#8203;681](https://redirect.github.com/uuidjs/uuid/issues/681))
([db76a12](db76a12847))
- update node support matrix (only support node 16-20)
([#&#8203;750](https://redirect.github.com/uuidjs/uuid/issues/750))
([883b163](883b163b9a))
- support rfc9562 v8 uuids
([#&#8203;759](https://redirect.github.com/uuidjs/uuid/issues/759))
([35a5342](35a5342820))

##### Bug Fixes

- revert "perf: remove superfluous call to toLowerCase
([#&#8203;677](https://redirect.github.com/uuidjs/uuid/issues/677))"
([#&#8203;738](https://redirect.github.com/uuidjs/uuid/issues/738))
([e267b90](e267b9073d))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/js-sdk).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMzUuMiIsInVwZGF0ZWRJblZlciI6IjM5LjE3Ni4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 10:59:42 -05:00
Michael Beemer 6a224830fa
feat: add support for abort controllers to event handlers (#1151)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-26 16:17:28 -05:00
OpenFeature Bot a703ee7a6f
chore(main): release core 1.7.2 (#1150)
🤖 I have created a release *beep* *boop*
---


##
[1.7.2](https://github.com/open-feature/js-sdk/compare/core-v1.7.1...core-v1.7.2)
(2025-02-18)


### 🐛 Bug Fixes

* rename evaluation event property from data to body
([4c2b01e](4c2b01e367))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-02-18 13:50:35 -05:00
Michael Beemer 4c2b01e367
fix: rename evaluation event property from data to body
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-18 18:46:02 +00:00
OpenFeature Bot 40deec0414
chore(main): release angular-sdk 0.0.10 (#1143)
🤖 I have created a release *beep* *boop*
---


##
[0.0.10](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.9-experimental...angular-sdk-v0.0.10)
(2025-02-13)


### 🧹 Chore

* **angular:** update angular package to a non-experimental version
([#1147](https://github.com/open-feature/js-sdk/issues/1147))
([5272f76](5272f76c40)),
closes [#1110](https://github.com/open-feature/js-sdk/issues/1110)
* update sdk peer
([#1142](https://github.com/open-feature/js-sdk/issues/1142))
([8bb6206](8bb620601e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-02-13 21:11:57 +00:00
Lukas Reining 5272f76c40
chore(angular): update angular package to a non-experimental version (#1147)
Release Angular SDK as non-experimental version as described in #1110 

Release-As: 0.0.10

Fixes #1110

Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-13 22:08:56 +01:00
OpenFeature Bot 30004ea88f
chore(main): release core 1.7.1 (#1149)
🤖 I have created a release *beep* *boop*
---


##
[1.7.1](https://github.com/open-feature/js-sdk/compare/core-v1.7.0...core-v1.7.1)
(2025-02-13)


### 🐛 Bug Fixes

* export missing telemetry functionality
([#1148](https://github.com/open-feature/js-sdk/issues/1148))
([dcbc300](dcbc30090e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-02-13 16:03:13 -05:00
Michael Beemer dcbc30090e
fix: export missing telemetry functionality (#1148)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-13 15:59:56 -05:00
OpenFeature Bot 23ba0b3d8c
chore(main): release react-sdk 0.4.11 (#1122)
🤖 I have created a release *beep* *boop*
---


##
[0.4.11](https://github.com/open-feature/js-sdk/compare/react-sdk-v0.4.10...react-sdk-v0.4.11)
(2025-02-07)


###  New Features

* export useOpenFeatureClientStatus hook
([#1082](https://github.com/open-feature/js-sdk/issues/1082))
([4a6b860](4a6b860544))


### 🧹 Chore

* update sdk peer
([#1142](https://github.com/open-feature/js-sdk/issues/1142))
([8bb6206](8bb620601e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-02-07 21:05:41 +00:00
Michael Beemer 8bb620601e
chore: update sdk peer (#1142)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-07 16:02:31 -05:00
OpenFeature Bot ffceec91bc
chore(main): release web-sdk 1.4.1 (#1135)
🤖 I have created a release *beep* *boop*
---


##
[1.4.1](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.4.0...web-sdk-v1.4.1)
(2025-02-07)


### 🐛 Bug Fixes

* msg missing when providers return err resolutions
([#1134](https://github.com/open-feature/js-sdk/issues/1134))
([bc9f6e4](bc9f6e44da))


### 🧹 Chore

* update core peer
([8bbd43e](8bbd43e579))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-02-07 20:35:51 +00:00
87 changed files with 9923 additions and 9393 deletions

View File

@ -29,6 +29,6 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: "https://registry.npmjs.org"
cache: 'npm'

View File

@ -16,7 +16,7 @@ jobs:
- uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version: 18
node-version: 20
cache: 'npm'
- name: Install

View File

@ -16,9 +16,9 @@ jobs:
strategy:
matrix:
node-version:
- 18.x
- 20.x
- 22.x
- 24.x
steps:
- uses: actions/checkout@v4
@ -37,8 +37,11 @@ jobs:
- name: Lint
run: npm run lint
- name: Test
run: npm run test
- name: Test Jest Projects
run: npm run test:jest
- name: Test Angular SDK
run: npm run test:angular
codecov-and-docs:
runs-on: ubuntu-latest
@ -47,7 +50,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
cache: 'npm'
- name: Install
@ -69,8 +72,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
# we need 'fetch' for this test, which is only in 18
node-version: 18
node-version: 20
cache: 'npm'
- name: Install

View File

@ -38,7 +38,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Generate SBOM
run: |
npm install -g npm@^10.2.0
@ -54,6 +54,7 @@ jobs:
needs: release-please
runs-on: ubuntu-latest
if: ${{ needs.release-please.outputs.release_created }}
environment: publish
permissions:
id-token: write
contents: write
@ -64,7 +65,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: "https://registry.npmjs.org"
cache: 'npm'
- name: Build Packages

View File

@ -1,9 +1,8 @@
{
"packages/nest": "0.2.2",
"packages/react": "0.4.10",
"packages/angular": "0.0.1-experimental",
"packages/web": "1.4.0",
"packages/server": "1.17.1",
"packages/shared": "1.7.0",
"packages/angular/projects/angular-sdk": "0.0.9-experimental"
"packages/nest": "0.2.5",
"packages/react": "1.0.1",
"packages/web": "1.6.1",
"packages/server": "1.19.0",
"packages/shared": "1.9.0",
"packages/angular/projects/angular-sdk": "0.0.16"
}

View File

@ -3,4 +3,4 @@
#
# Managed by Peribolos: https://github.com/open-feature/community/blob/main/config/open-feature/sdk-javascript/workgroup.yaml
#
* @open-feature/sdk-javascript-maintainers
* @open-feature/sdk-javascript-maintainers @open-feature/maintainers

View File

@ -161,6 +161,7 @@ export default {
testMatch: ['<rootDir>/packages/nest/test/**/*.spec.ts'],
moduleNameMapper: {
'@openfeature/core': '<rootDir>/packages/shared/src',
'@openfeature/server-sdk': '<rootDir>/packages/server/src',
},
transform: {
'^.+\\.ts$': [
@ -189,26 +190,6 @@ export default {
],
},
},
{
displayName: 'angular',
testEnvironment: 'jsdom',
preset: 'jest-preset-angular',
testMatch: ['<rootDir>/packages/angular/projects/angular-sdk/src/**/*.spec.{ts,tsx}'],
setupFilesAfterEnv: ['<rootDir>/packages/angular/setup-jest.ts'],
moduleNameMapper: {
'@openfeature/core': '<rootDir>/packages/shared/src',
'@openfeature/web-sdk': '<rootDir>/packages/web/src',
},
transform: {
'^.+\\.(ts|js|html|svg)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/packages/angular/tsconfig.json',
isolatedModules: true,
},
],
},
}
],
// Use this configuration option to add custom reporters to Jest

16068
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,15 @@
{
"name": "@openfeature/js",
"engines": {
"npm": "^10.0.0"
},
"version": "0.0.0",
"private": true,
"description": "OpenFeature SDK for JavaScript",
"scripts": {
"test": "jest --selectProjects=shared --selectProjects=server --selectProjects=web --selectProjects=react --selectProjects=angular --silent",
"test": "npm run test:jest && npm run test:angular",
"test:jest": "jest --selectProjects=shared --selectProjects=server --selectProjects=web --selectProjects=react --selectProjects=nest --silent",
"test:angular": "npm run test:coverage --workspace=packages/angular",
"e2e-server": "git submodule update --init --recursive && shx cp test-harness/features/evaluation.feature packages/server/e2e/features && jest --selectProjects=server-e2e --verbose",
"e2e-web": "git submodule update --init --recursive && shx cp test-harness/features/evaluation.feature packages/web/e2e/features && jest --selectProjects=web-e2e --verbose",
"e2e": "npm run e2e-server && npm run e2e-web",
@ -31,18 +36,15 @@
"url": "https://github.com/open-feature/js-sdk/issues"
},
"homepage": "https://github.com/open-feature/js-sdk#readme",
"engines": {
"node": ">=18"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-typescript": "^12.0.0",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.16",
"@types/node": "^22.0.0",
"@types/react": "^18.2.55",
"@types/uuid": "^10.0.0",
"esbuild": "^0.24.0",
"esbuild": "^0.25.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-alias": "^1.1.2",
@ -57,23 +59,18 @@
"jest-environment-jsdom": "^29.7.0",
"jest-environment-node": "^29.7.0",
"jest-junit": "^16.0.0",
"jest-preset-angular": "^14.2.4",
"ng-packagr": "^18.2.1",
"prettier": "^3.2.5",
"react": "^18.2.0",
"rollup": "^4.0.0",
"rollup-plugin-dts": "^6.1.1",
"rxjs": "~7.8.0",
"shx": "^0.3.4",
"shx": "^0.4.0",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"tslib": "^2.3.0",
"typedoc": "^0.26.0",
"typescript": "^4.7.4",
"uuid": "^9.0.1"
},
"overrides": {
"typescript": "^4.7.4"
"uuid": "^11.0.0"
},
"workspaces": [
"packages/shared",

View File

@ -10,7 +10,7 @@
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"builder": "@angular/build:ng-packagr",
"options": {
"project": "projects/angular-sdk/ng-package.json"
},
@ -32,6 +32,15 @@
"projects/angular-sdk/**/*.html"
]
}
},
"test": {
"builder": "@angular/build:unit-test",
"options": {
"tsConfig": "projects/angular-sdk/tsconfig.spec.json",
"providersFile": "projects/angular-sdk/src/test-provider.ts",
"runner": "vitest",
"buildTarget": "::development"
}
}
}
}

View File

@ -7,37 +7,42 @@
"lint": "ng lint",
"lint:fix": "ng lint --fix",
"watch": "ng build --watch --configuration development",
"test": "jest",
"test": "ng test",
"test:coverage": "ng test --no-watch --code-coverage",
"build": "ng build && npm run postbuild",
"postbuild": "shx cp ./../../LICENSE ./dist/angular/LICENSE",
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm --prefix dist/angular run current-published-version -s)\" = \"$(npm --prefix dist/angular run current-version -s)\" ]; then echo 'already published, skipping'; else cd dist/angular && npm publish --access public; fi"
},
"private": true,
"devDependencies": {
"@angular-devkit/build-angular": "^19.0.0",
"@angular-eslint/builder": "18.4.3",
"@angular-eslint/eslint-plugin": "18.4.3",
"@angular-eslint/eslint-plugin-template": "18.4.3",
"@angular-eslint/schematics": "18.4.3",
"@angular-eslint/template-parser": "18.4.3",
"@angular/animations": "^19.0.0",
"@angular/cli": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
"@angular/compiler-cli": "^19.0.0",
"@angular/core": "^19.0.0",
"@angular/forms": "^19.0.0",
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@angular-eslint/builder": "^20.1.1",
"@angular-eslint/eslint-plugin": "^20.1.1",
"@angular-eslint/eslint-plugin-template": "^20.1.1",
"@angular-eslint/schematics": "^20.1.1",
"@angular-eslint/template-parser": "^20.1.1",
"@angular/animations": "^20.1.1",
"@angular/build": "^20.1.1",
"@angular/cli": "^20.1.1",
"@angular/common": "^20.1.1",
"@angular/compiler": "^20.1.1",
"@angular/compiler-cli": "^20.1.1",
"@angular/core": "^20.1.1",
"@angular/forms": "^20.1.1",
"@angular/platform-browser": "^20.1.1",
"@angular/platform-browser-dynamic": "^20.1.1",
"@angular/router": "^20.1.1",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
"@vitest/browser": "^3.2.4",
"@vitest/coverage-v8": "^3.2.4",
"eslint": "^8.57.0",
"jest-preset-angular": "^14.2.4",
"ng-packagr": "^19.0.0",
"jsdom": "^26.1.0",
"ng-packagr": "^20.1.0",
"playwright": "^1.53.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"typescript": "^5.5.4",
"typescript": "^5.8.3",
"vitest": "^3.2.4",
"zone.js": "~0.15.0"
}
}

View File

@ -0,0 +1,18 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For Angular's browser support policy, please see:
# https://angular.dev/reference/versions#browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
Chrome >= 107
ChromeAndroid >= 107
Edge >= 107
Firefox >= 104
FirefoxAndroid >= 104
Safari >= 16
iOS >= 16

View File

@ -1,6 +1,61 @@
# Changelog
## [0.0.16](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.15...angular-sdk-v0.0.16) (2025-07-25)
### ✨ New Features
* support Angular 20 ([#1220](https://github.com/open-feature/js-sdk/issues/1220)) ([aa232a9](https://github.com/open-feature/js-sdk/commit/aa232a9d6a8dfa416380ccdecd71843d3e361048))
## [0.0.15](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.14...angular-sdk-v0.0.15) (2025-05-27)
### 🐛 Bug Fixes
* **angular:** update docs ([#1200](https://github.com/open-feature/js-sdk/issues/1200)) ([b6ea588](https://github.com/open-feature/js-sdk/commit/b6ea5884f2ab9f4f94c8b258c4cf7268ea6dbeb8))
## [0.0.14](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.13...angular-sdk-v0.0.14) (2025-05-25)
### 🐛 Bug Fixes
* **angular:** add license and url field to package.json ([b2784f5](https://github.com/open-feature/js-sdk/commit/b2784f53b85a11c58abb8e2a0f87a31890885c54))
## [0.0.13](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.12...angular-sdk-v0.0.13) (2025-04-20)
### 📚 Documentation
* fix readme typo ([#1174](https://github.com/open-feature/js-sdk/issues/1174)) ([21a32ec](https://github.com/open-feature/js-sdk/commit/21a32ec92ecde9ec43c9d72b5921035af13448d1))
## [0.0.12](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.11...angular-sdk-v0.0.12) (2025-04-11)
### ✨ New Features
* **angular:** add docs for setting evaluation context in angular ([#1170](https://github.com/open-feature/js-sdk/issues/1170)) ([24f1b23](https://github.com/open-feature/js-sdk/commit/24f1b230bf1d57971a336ac21b9ee46e8baf0cab))
## [0.0.11](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.10...angular-sdk-v0.0.11) (2025-04-11)
### ✨ New Features
* **angular:** add option for initial context injection ([aafdb43](https://github.com/open-feature/js-sdk/commit/aafdb4382f113f96a649f5fc0cecadb4178ada67))
## [0.0.10](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.9-experimental...angular-sdk-v0.0.10) (2025-02-13)
### 🧹 Chore
* **angular:** update angular package to a non-experimental version ([#1147](https://github.com/open-feature/js-sdk/issues/1147)) ([5272f76](https://github.com/open-feature/js-sdk/commit/5272f76c4075ebbd21f9b24dacac8f2d22e31ca9)), closes [#1110](https://github.com/open-feature/js-sdk/issues/1110)
* update sdk peer ([#1142](https://github.com/open-feature/js-sdk/issues/1142)) ([8bb6206](https://github.com/open-feature/js-sdk/commit/8bb620601e2b8dc7b62d717169b585bd1c886996))
## [0.0.9-experimental](https://github.com/open-feature/js-sdk/compare/angular-sdk-v0.0.8-experimental...angular-sdk-v0.0.9-experimental) (2024-11-21)

View File

@ -16,8 +16,8 @@
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.8.0&color=yellow&style=for-the-badge" />
</a>
<!-- x-release-please-start-version -->
<a href="https://github.com/open-feature/js-sdk/releases/tag/angular-sdk-v0.0.9-experimental">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.0.9-experimental&color=blue&style=for-the-badge" />
<a href="https://github.com/open-feature/js-sdk/releases/tag/angular-sdk-v0.0.16">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.0.16&color=blue&style=for-the-badge" />
</a>
<!-- x-release-please-end -->
<br/>
@ -44,21 +44,22 @@ In addition to the features provided by the [web sdk](https://openfeature.dev/do
- [Overview](#overview)
- [Quick start](#quick-start)
- [Requirements](#requirements)
- [Install](#install)
- [npm](#npm)
- [yarn](#yarn)
- [Required peer dependencies](#required-peer-dependencies)
- [Usage](#usage)
- [Module](#module)
- [Minimal Example](#minimal-example)
- [How to use](#how-to-use)
- [Boolean Feature Flag](#boolean-feature-flag)
- [Number Feature Flag](#number-feature-flag)
- [String Feature Flag](#string-feature-flag)
- [Object Feature Flag](#object-feature-flag)
- [Opting-out of automatic re-rendering](#opting-out-of-automatic-re-rendering)
- [Consuming the evaluation details](#consuming-the-evaluation-details)
- [Requirements](#requirements)
- [Install](#install)
- [npm](#npm)
- [yarn](#yarn)
- [Required peer dependencies](#required-peer-dependencies)
- [Usage](#usage)
- [Module](#module)
- [Minimal Example](#minimal-example)
- [How to use](#how-to-use)
- [Boolean Feature Flag](#boolean-feature-flag)
- [Number Feature Flag](#number-feature-flag)
- [String Feature Flag](#string-feature-flag)
- [Object Feature Flag](#object-feature-flag)
- [Opting-out of automatic re-rendering](#opting-out-of-automatic-re-rendering)
- [Consuming the evaluation details](#consuming-the-evaluation-details)
- [Setting Evaluation Context](#setting-evaluation-context)
- [FAQ and troubleshooting](#faq-and-troubleshooting)
- [Resources](#resources)
@ -113,7 +114,7 @@ import { OpenFeatureModule } from '@openfeature/angular-sdk';
CommonModule,
OpenFeatureModule.forRoot({
provider: yourFeatureProvider,
// domainBoundProviders are optional, mostly needed if more than one provider is needed
// domainBoundProviders are optional, mostly needed if more than one provider is used in the application.
domainBoundProviders: {
domain1: new YourOpenFeatureProvider(),
domain2: new YourOtherOpenFeatureProvider(),
@ -281,6 +282,63 @@ This can be used to just render the flag value or details without conditional re
</div>
```
##### Setting evaluation context
To set the initial evaluation context, you can add the `context` parameter to the `OpenFeatureModule` configuration.
This context can be either an object or a factory function that returns an `EvaluationContext`.
> [!TIP]
> Updating the context can be done directly via the global OpenFeature API using `OpenFeature.setContext()`
Heres how you can define and use the initial client evaluation context:
###### Using a static object
```typescript
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OpenFeatureModule } from '@openfeature/angular-sdk';
const initialContext = {
user: {
id: 'user123',
role: 'admin',
}
};
@NgModule({
imports: [
CommonModule,
OpenFeatureModule.forRoot({
provider: yourFeatureProvider,
context: initialContext
})
],
})
export class AppModule {}
```
###### Using a factory function
```typescript
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OpenFeatureModule, EvaluationContext } from '@openfeature/angular-sdk';
const contextFactory = (): EvaluationContext => loadContextFromLocalStorage();
@NgModule({
imports: [
CommonModule,
OpenFeatureModule.forRoot({
provider: yourFeatureProvider,
context: contextFactory
})
],
})
export class AppModule {}
```
## FAQ and troubleshooting
> I can import things form the `@openfeature/angular-sdk`, `@openfeature/web-sdk`, and `@openfeature/core`; which should I use?
@ -291,4 +349,4 @@ Avoid importing anything from `@openfeature/web-sdk` or `@openfeature/core`.
## Resources
- [Example repo](https://github.com/open-feature/angular-test-app)
- [Example repo](https://github.com/open-feature/angular-test-app)

View File

@ -1,29 +1,34 @@
{
"name": "@openfeature/angular-sdk",
"version": "0.0.9-experimental",
"version": "0.0.16",
"description": "OpenFeature Angular SDK",
"repository": {
"type": "git",
"url": "git+https://github.com/open-feature/js-sdk.git"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/open-feature/js-sdk/issues"
},
"homepage": "https://github.com/open-feature/js-sdk#readme",
"scripts": {
"current-published-version": "npm show $npm_package_name@$npm_package_version version",
"current-version": "echo $npm_package_version",
"prepack": "shx cp ./../../../../LICENSE ./LICENSE"
},
"peerDependencies": {
"@angular/common": "^16.2.12 || ^17.3.0 || ^18.0.0 || ^19.0.0",
"@angular/core": "^16.2.12 || ^17.3.0 || ^18.0.0 || ^19.0.0",
"@openfeature/web-sdk": "^1.2.3"
"@angular/common": "^16.2.12 || ^17.3.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
"@angular/core": "^16.2.12 || ^17.3.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
"@openfeature/web-sdk": "^1.4.1"
},
"dependencies": {
"tslib": "^2.3.0"
},
"devDependencies": {
"@openfeature/core": "*",
"@openfeature/web-sdk": "*",
"@angular/common": "^19.0.0",
"@angular/core": "^19.0.0"
"@openfeature/core": "^1.8.1",
"@openfeature/web-sdk": "^1.5.0",
"@angular/common": "^20.1.2",
"@angular/core": "^20.1.2"
},
"sideEffects": false,
"keywords": [

View File

@ -253,6 +253,7 @@ describe('FeatureFlagDirective', () => {
await expectRenderedText(fixture, 'case-2', 'Flag On');
await updateFlagValue(provider, false);
fixture.detectChanges(); // Ensure change detection after flag update
await expectRenderedText(fixture, 'case-2', 'Flag Off');
});
});
@ -393,7 +394,9 @@ describe('FeatureFlagDirective', () => {
await waitForClientReady(client);
await expectRenderedText(fixture, 'case-6', 'Flag On');
fixture.componentInstance.specialFlagKey = 'new-test-flag';
fixture.componentRef.setInput('specialFlagKey', 'new-test-flag');
await fixture.whenStable();
await expectRenderedText(fixture, 'case-6', 'Flag Off');
});
@ -445,7 +448,9 @@ describe('FeatureFlagDirective', () => {
);
await OpenFeature.setProviderAndWait(newDomain, newProvider);
fixture.componentInstance.domain = newDomain;
fixture.componentRef.setInput('domain', newDomain);
await fixture.whenStable();
await expectRenderedText(fixture, 'case-6', 'Flag Off');
});
});
@ -560,8 +565,7 @@ async function createTestingModule(config?: {
],
}).createComponent(TestComponent);
fixture.componentInstance.domain = domain;
fixture.detectChanges();
fixture.componentRef.setInput('domain', domain);
await fixture.whenStable();
const client = OpenFeature.getClient(domain);

View File

@ -8,6 +8,7 @@ import {
OnInit,
TemplateRef,
ViewContainerRef,
inject,
} from '@angular/core';
import {
Client,
@ -35,6 +36,9 @@ class FeatureFlagDirectiveContext<T extends FlagValue> {
selector: '[featureFlag]',
})
export abstract class FeatureFlagDirective<T extends FlagValue> implements OnInit, OnDestroy, OnChanges {
protected _changeDetectorRef: ChangeDetectorRef;
protected _viewContainerRef: ViewContainerRef;
protected _featureFlagDefault: T;
protected _featureFlagDomain: string | undefined;
@ -64,13 +68,7 @@ export abstract class FeatureFlagDirective<T extends FlagValue> implements OnIni
protected _reconcilingTemplateRef: TemplateRef<FeatureFlagDirectiveContext<T>> | null;
protected _reconcilingViewRef: EmbeddedViewRef<unknown> | null;
protected constructor(
protected _changeDetectorRef: ChangeDetectorRef,
protected _viewContainerRef: ViewContainerRef,
templateRef: TemplateRef<FeatureFlagDirectiveContext<T>>,
) {
this._thenTemplateRef = templateRef;
}
protected constructor() {}
set featureFlagDomain(domain: string | undefined) {
/**
@ -232,6 +230,10 @@ export abstract class FeatureFlagDirective<T extends FlagValue> implements OnIni
selector: '[booleanFeatureFlag]',
})
export class BooleanFeatureFlagDirective extends FeatureFlagDirective<boolean> implements OnChanges {
override _changeDetectorRef = inject(ChangeDetectorRef);
override _viewContainerRef = inject(ViewContainerRef);
override _thenTemplateRef = inject<TemplateRef<FeatureFlagDirectiveContext<boolean>>>(TemplateRef);
/**
* The key of the boolean feature flag.
*/
@ -242,12 +244,8 @@ export class BooleanFeatureFlagDirective extends FeatureFlagDirective<boolean> i
*/
@Input({ required: true }) booleanFeatureFlagDefault: boolean;
constructor(
_changeDetectorRef: ChangeDetectorRef,
_viewContainerRef: ViewContainerRef,
templateRef: TemplateRef<FeatureFlagDirectiveContext<boolean>>,
) {
super(_changeDetectorRef, _viewContainerRef, templateRef);
constructor() {
super();
}
override ngOnChanges() {
@ -347,6 +345,10 @@ export class BooleanFeatureFlagDirective extends FeatureFlagDirective<boolean> i
selector: '[numberFeatureFlag]',
})
export class NumberFeatureFlagDirective extends FeatureFlagDirective<number> implements OnChanges {
override _changeDetectorRef = inject(ChangeDetectorRef);
override _viewContainerRef = inject(ViewContainerRef);
override _thenTemplateRef = inject<TemplateRef<FeatureFlagDirectiveContext<number>>>(TemplateRef);
/**
* The key of the number feature flag.
*/
@ -362,12 +364,8 @@ export class NumberFeatureFlagDirective extends FeatureFlagDirective<number> imp
*/
@Input({ required: false }) numberFeatureFlagValue?: number;
constructor(
_changeDetectorRef: ChangeDetectorRef,
_viewContainerRef: ViewContainerRef,
templateRef: TemplateRef<FeatureFlagDirectiveContext<number>>,
) {
super(_changeDetectorRef, _viewContainerRef, templateRef);
constructor() {
super();
}
override ngOnChanges() {
@ -467,6 +465,10 @@ export class NumberFeatureFlagDirective extends FeatureFlagDirective<number> imp
selector: '[stringFeatureFlag]',
})
export class StringFeatureFlagDirective extends FeatureFlagDirective<string> implements OnChanges {
override _changeDetectorRef = inject(ChangeDetectorRef);
override _viewContainerRef = inject(ViewContainerRef);
override _thenTemplateRef = inject<TemplateRef<FeatureFlagDirectiveContext<string>>>(TemplateRef);
/**
* The key of the string feature flag.
*/
@ -482,12 +484,8 @@ export class StringFeatureFlagDirective extends FeatureFlagDirective<string> imp
*/
@Input({ required: false }) stringFeatureFlagValue?: string;
constructor(
_changeDetectorRef: ChangeDetectorRef,
_viewContainerRef: ViewContainerRef,
templateRef: TemplateRef<FeatureFlagDirectiveContext<string>>,
) {
super(_changeDetectorRef, _viewContainerRef, templateRef);
constructor() {
super();
}
override ngOnChanges() {
@ -587,6 +585,10 @@ export class StringFeatureFlagDirective extends FeatureFlagDirective<string> imp
selector: '[objectFeatureFlag]',
})
export class ObjectFeatureFlagDirective<T extends JsonValue> extends FeatureFlagDirective<T> implements OnChanges {
override _changeDetectorRef = inject(ChangeDetectorRef);
override _viewContainerRef = inject(ViewContainerRef);
override _thenTemplateRef = inject<TemplateRef<FeatureFlagDirectiveContext<T>>>(TemplateRef);
/**
* The key of the object feature flag.
*/
@ -602,12 +604,8 @@ export class ObjectFeatureFlagDirective<T extends JsonValue> extends FeatureFlag
*/
@Input({ required: false }) objectFeatureFlagValue?: T;
constructor(
_changeDetectorRef: ChangeDetectorRef,
_viewContainerRef: ViewContainerRef,
templateRef: TemplateRef<FeatureFlagDirectiveContext<T>>,
) {
super(_changeDetectorRef, _viewContainerRef, templateRef);
constructor() {
super();
}
override ngOnChanges() {

View File

@ -1,10 +1,13 @@
import { InjectionToken, ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OpenFeature, Provider } from '@openfeature/web-sdk';
import { EvaluationContext, OpenFeature, Provider } from '@openfeature/web-sdk';
export type EvaluationContextFactory = () => EvaluationContext;
export interface OpenFeatureConfig {
provider: Provider;
domainBoundProviders?: Record<string, Provider>;
context?: EvaluationContext | EvaluationContextFactory;
}
export const OPEN_FEATURE_CONFIG_TOKEN = new InjectionToken<OpenFeatureConfig>('OPEN_FEATURE_CONFIG_TOKEN');
@ -16,7 +19,9 @@ export const OPEN_FEATURE_CONFIG_TOKEN = new InjectionToken<OpenFeatureConfig>('
})
export class OpenFeatureModule {
static forRoot(config: OpenFeatureConfig): ModuleWithProviders<OpenFeatureModule> {
OpenFeature.setProvider(config.provider);
const context = typeof config.context === 'function' ? config.context() : config.context;
OpenFeature.setProvider(config.provider, context);
if (config.domainBoundProviders) {
Object.entries(config.domainBoundProviders).map(([domain, provider]) =>
OpenFeature.setProvider(domain, provider),

View File

@ -0,0 +1,3 @@
import { provideZonelessChangeDetection } from '@angular/core';
export default [provideZonelessChangeDetection()];

View File

@ -3,15 +3,20 @@
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jest",
"vitest/globals",
"node"
],
"paths": {
"angular": [
"./dist/angular"
]
},
"esModuleInterop": true,
"emitDecoratorMetadata": true
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts",
"setup-jest.ts"
"src/test-provider.ts"
]
}

View File

@ -1,5 +0,0 @@
import 'jest-preset-angular/setup-jest';
import { TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());

View File

@ -23,8 +23,8 @@
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2015",
"module": "ES2015",
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"strictNullChecks": false,
"lib": [

View File

@ -3,13 +3,14 @@
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jest"
"vitest/globals",
"node"
],
"esModuleInterop": true,
"emitDecoratorMetadata": true
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
"projects/angular-sdk/src/**/*.spec.ts",
"projects/angular-sdk/src/**/*.d.ts"
]
}

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
});

View File

@ -1,5 +1,34 @@
# Changelog
## [0.2.5](https://github.com/open-feature/js-sdk/compare/nestjs-sdk-v0.2.4...nestjs-sdk-v0.2.5) (2025-05-27)
### ✨ New Features
* adds RequireFlagsEnabled decorator ([#1159](https://github.com/open-feature/js-sdk/issues/1159)) ([59b8fe9](https://github.com/open-feature/js-sdk/commit/59b8fe904f053e4aa3d0c72631af34183ff54dc7))
## [0.2.4](https://github.com/open-feature/js-sdk/compare/nestjs-sdk-v0.2.3...nestjs-sdk-v0.2.4) (2025-04-20)
### 🧹 Chore
* **nest:** allow nestjs version 11 ([#1176](https://github.com/open-feature/js-sdk/issues/1176)) ([42a3b39](https://github.com/open-feature/js-sdk/commit/42a3b39c2488002f249b37ce86794ef2f77eb31c))
## [0.2.3](https://github.com/open-feature/js-sdk/compare/nestjs-sdk-v0.2.2...nestjs-sdk-v0.2.3) (2025-04-11)
### 🧹 Chore
* update sdk peer ([#1142](https://github.com/open-feature/js-sdk/issues/1142)) ([8bb6206](https://github.com/open-feature/js-sdk/commit/8bb620601e2b8dc7b62d717169b585bd1c886996))
### Dependencies
* The following workspace dependencies were updated
* devDependencies
* @openfeature/server-sdk bumped from * to 1.18.0
## [0.2.2](https://github.com/open-feature/js-sdk/compare/nestjs-sdk-v0.2.1-experimental...nestjs-sdk-v0.2.2) (2024-10-29)

View File

@ -16,8 +16,8 @@
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.8.0&color=yellow&style=for-the-badge" />
</a>
<!-- x-release-please-start-version -->
<a href="https://github.com/open-feature/js-sdk/releases/tag/nestjs-sdk-v0.2.2">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.2.2&color=blue&style=for-the-badge" />
<a href="https://github.com/open-feature/js-sdk/releases/tag/nestjs-sdk-v0.2.5">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.2.5&color=blue&style=for-the-badge" />
</a>
<!-- x-release-please-end -->
<br/>
@ -50,7 +50,7 @@ Capabilities include:
### Requirements
- Node.js version 18+
- Node.js version 20+
- NestJS version 8+
### Install
@ -72,10 +72,10 @@ yarn add @openfeature/nestjs-sdk @openfeature/server-sdk @openfeature/core
The following list contains the peer dependencies of `@openfeature/nestjs-sdk` with its expected and compatible versions:
* `@openfeature/server-sdk`: >=1.7.5
* `@nestjs/common`: ^8.0.0 || ^9.0.0 || ^10.0.0
* `@nestjs/core`: ^8.0.0 || ^9.0.0 || ^10.0.0
* `rxjs`: ^6.0.0 || ^7.0.0 || ^8.0.0
- `@openfeature/server-sdk`: >=1.7.5
- `@nestjs/common`: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
- `@nestjs/core`: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
- `rxjs`: ^6.0.0 || ^7.0.0 || ^8.0.0
The minimum required version of `@openfeature/server-sdk` currently is `1.7.5`.
@ -152,6 +152,24 @@ export class OpenFeatureTestService {
}
```
#### Managing Controller or Route Access via Feature Flags
The `RequireFlagsEnabled` decorator can be used to manage access to a controller or route based on the enabled state of a feature flag. The decorator will throw an exception if the required feature flag(s) are not enabled.
```ts
import { Controller, Get } from '@nestjs/common';
import { RequireFlagsEnabled } from '@openfeature/nestjs-sdk';
@Controller()
export class OpenFeatureController {
@RequireFlagsEnabled({ flags: [{ flagKey: 'testBooleanFlag' }] })
@Get('/welcome')
public async welcome() {
return 'Welcome to this OpenFeature-enabled NestJS app!';
}
}
```
## Module additional information
### Flag evaluation context injection

View File

@ -1,6 +1,6 @@
{
"name": "@openfeature/nestjs-sdk",
"version": "0.2.2",
"version": "0.2.5",
"description": "OpenFeature Nest.js SDK",
"main": "./dist/cjs/index.js",
"files": [
@ -46,18 +46,18 @@
},
"homepage": "https://github.com/open-feature/js-sdk#readme",
"peerDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
"@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0",
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
"@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
"rxjs": "^6.0.0 || ^7.0.0 || 8.0.0",
"@openfeature/server-sdk": ">=1.14.0"
"@openfeature/server-sdk": "^1.17.1"
},
"devDependencies": {
"@nestjs/common": "^10.3.6",
"@nestjs/core": "^10.3.6",
"@nestjs/platform-express": "^10.3.6",
"@nestjs/testing": "^10.3.6",
"@nestjs/common": "^11.0.20",
"@nestjs/core": "^11.0.20",
"@nestjs/platform-express": "^11.0.20",
"@nestjs/testing": "^11.0.20",
"@openfeature/core": "*",
"@openfeature/server-sdk": "*",
"@openfeature/server-sdk": "1.18.0",
"@types/supertest": "^6.0.0",
"supertest": "^7.0.0"
}

View File

@ -1,16 +1,10 @@
import { createParamDecorator, Inject } from '@nestjs/common';
import type {
EvaluationContext,
EvaluationDetails,
FlagValue,
JsonValue} from '@openfeature/server-sdk';
import {
OpenFeature,
Client,
} from '@openfeature/server-sdk';
import type { EvaluationContext, EvaluationDetails, FlagValue, JsonValue } from '@openfeature/server-sdk';
import { Client } from '@openfeature/server-sdk';
import { getOpenFeatureClientToken } from './open-feature.module';
import type { Observable } from 'rxjs';
import { from } from 'rxjs';
import { getClientForEvaluation } from './utils';
/**
* Options for injecting an OpenFeature client into a constructor.
@ -56,16 +50,6 @@ interface FeatureProps<T extends FlagValue> {
context?: EvaluationContext;
}
/**
* Returns a domain scoped or the default OpenFeature client with the given context.
* @param {string} domain The domain of the OpenFeature client.
* @param {EvaluationContext} context The evaluation context of the client.
* @returns {Client} The OpenFeature client.
*/
function getClientForEvaluation(domain?: string, context?: EvaluationContext) {
return domain ? OpenFeature.getClient(domain, context) : OpenFeature.getClient(context);
}
/**
* Route handler parameter decorator.
*

View File

@ -2,5 +2,6 @@ export * from './open-feature.module';
export * from './feature.decorator';
export * from './evaluation-context-interceptor';
export * from './context-factory';
export * from './require-flags-enabled.decorator';
// re-export the server-sdk so consumers can access that API from the nestjs-sdk
export * from '@openfeature/server-sdk';

View File

@ -0,0 +1,104 @@
import type { CallHandler, ExecutionContext, HttpException, NestInterceptor } from '@nestjs/common';
import { applyDecorators, mixin, NotFoundException, UseInterceptors } from '@nestjs/common';
import { getClientForEvaluation } from './utils';
import type { EvaluationContext } from '@openfeature/server-sdk';
import type { ContextFactory } from './context-factory';
type RequiredFlag = {
flagKey: string;
defaultValue?: boolean;
};
/**
* Options for using one or more Boolean feature flags to control access to a Controller or Route.
*/
interface RequireFlagsEnabledProps {
/**
* The key and default value of the feature flag.
* @see {@link Client#getBooleanValue}
*/
flags: RequiredFlag[];
/**
* The exception to throw if any of the required feature flags are not enabled.
* Defaults to a 404 Not Found exception.
* @see {@link HttpException}
* @default new NotFoundException(`Cannot ${req.method} ${req.url}`)
*/
exception?: HttpException;
/**
* The domain of the OpenFeature client, if a domain scoped client should be used.
* @see {@link OpenFeature#getClient}
*/
domain?: string;
/**
* The {@link EvaluationContext} for evaluating the feature flag.
* @see {@link OpenFeature#setContext}
*/
context?: EvaluationContext;
/**
* A factory function for creating an OpenFeature {@link EvaluationContext} from Nest {@link ExecutionContext}.
* For example, this can be used to get header info from an HTTP request or information from a gRPC call to be used in the {@link EvaluationContext}.
* @see {@link ContextFactory}
*/
contextFactory?: ContextFactory;
}
/**
* Controller or Route permissions handler decorator.
*
* Requires that the given feature flags are enabled for the request to be processed, else throws an exception.
*
* For example:
* ```typescript
* @RequireFlagsEnabled({
* flags: [ // Required, an array of Boolean flags to check, with optional default values (defaults to false)
* { flagKey: 'flagName' },
* { flagKey: 'flagName2', defaultValue: true },
* ],
* exception: new ForbiddenException(), // Optional, defaults to a 404 Not Found Exception
* domain: 'my-domain', // Optional, defaults to the default OpenFeature Client
* context: { // Optional, defaults to the global OpenFeature Context
* targetingKey: 'user-id',
* },
* contextFactory: (context: ExecutionContext) => { // Optional, defaults to the global OpenFeature Context. Takes precedence over the context option.
* return {
* targetingKey: context.switchToHttp().getRequest().headers['x-user-id'],
* };
* },
* })
* @Get('/')
* public async handleGetRequest()
* ```
* @param {RequireFlagsEnabledProps} props The options for injecting the feature flag.
* @returns {ClassDecorator & MethodDecorator} The decorator that can be used to require Boolean Feature Flags to be enabled for a controller or a specific route.
*/
export const RequireFlagsEnabled = (props: RequireFlagsEnabledProps): ClassDecorator & MethodDecorator =>
applyDecorators(UseInterceptors(FlagsEnabledInterceptor(props)));
const FlagsEnabledInterceptor = (props: RequireFlagsEnabledProps) => {
class FlagsEnabledInterceptor implements NestInterceptor {
constructor() {}
async intercept(context: ExecutionContext, next: CallHandler) {
const req = context.switchToHttp().getRequest();
const evaluationContext = props.contextFactory ? await props.contextFactory(context) : props.context;
const client = getClientForEvaluation(props.domain, evaluationContext);
for (const flag of props.flags) {
const endpointAccessible = await client.getBooleanValue(flag.flagKey, flag.defaultValue ?? false);
if (!endpointAccessible) {
throw props.exception || new NotFoundException(`Cannot ${req.method} ${req.url}`);
}
}
return next.handle();
}
}
return mixin(FlagsEnabledInterceptor);
};

View File

@ -0,0 +1,12 @@
import type { Client, EvaluationContext } from '@openfeature/server-sdk';
import { OpenFeature } from '@openfeature/server-sdk';
/**
* Returns a domain scoped or the default OpenFeature client with the given context.
* @param {string} domain The domain of the OpenFeature client.
* @param {EvaluationContext} context The evaluation context of the client.
* @returns {Client} The OpenFeature client.
*/
export function getClientForEvaluation(domain?: string, context?: EvaluationContext) {
return domain ? OpenFeature.getClient(domain, context) : OpenFeature.getClient(context);
}

View File

@ -1,4 +1,5 @@
import { InMemoryProvider } from '@openfeature/server-sdk';
import type { EvaluationContext } from '@openfeature/server-sdk';
import type { ExecutionContext } from '@nestjs/common';
import { OpenFeatureModule } from '../src';
@ -23,6 +24,17 @@ export const defaultProvider = new InMemoryProvider({
variants: { default: { client: 'default' } },
disabled: false,
},
testBooleanFlag2: {
defaultVariant: 'default',
variants: { default: false, enabled: true },
disabled: false,
contextEvaluator: (ctx: EvaluationContext) => {
if (ctx.targetingKey === '123') {
return 'enabled';
}
return 'default';
},
},
});
export const providers = {

View File

@ -2,7 +2,12 @@ import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import type { INestApplication } from '@nestjs/common';
import supertest from 'supertest';
import { OpenFeatureController, OpenFeatureControllerContextScopedController, OpenFeatureTestService } from './test-app';
import {
OpenFeatureController,
OpenFeatureContextScopedController,
OpenFeatureRequireFlagsEnabledController,
OpenFeatureTestService,
} from './test-app';
import { exampleContextFactory, getOpenFeatureDefaultTestModule } from './fixtures';
import { OpenFeatureModule } from '../src';
import { defaultProvider, providers } from './fixtures';
@ -14,11 +19,9 @@ describe('OpenFeature SDK', () => {
beforeAll(async () => {
moduleRef = await Test.createTestingModule({
imports: [
getOpenFeatureDefaultTestModule()
],
imports: [getOpenFeatureDefaultTestModule()],
providers: [OpenFeatureTestService],
controllers: [OpenFeatureController],
controllers: [OpenFeatureController, OpenFeatureRequireFlagsEnabledController],
}).compile();
app = moduleRef.createNestApplication();
app = await app.init();
@ -112,7 +115,7 @@ describe('OpenFeature SDK', () => {
});
describe('evaluation context service should', () => {
it('inject the evaluation context from contex factory', async function() {
it('inject the evaluation context from contex factory', async function () {
const evaluationSpy = jest.spyOn(defaultProvider, 'resolveBooleanEvaluation');
await supertest(app.getHttpServer())
.get('/dynamic-context-in-service')
@ -122,26 +125,77 @@ describe('OpenFeature SDK', () => {
expect(evaluationSpy).toHaveBeenCalledWith('testBooleanFlag', false, { targetingKey: 'dynamic-user' }, {});
});
});
describe('require flags enabled decorator', () => {
describe('OpenFeatureController', () => {
it('should sucessfully return the response if the flag is enabled', async () => {
await supertest(app.getHttpServer()).get('/flags-enabled').expect(200).expect('Get Boolean Flag Success!');
});
it('should throw an exception if the flag is disabled', async () => {
jest.spyOn(defaultProvider, 'resolveBooleanEvaluation').mockResolvedValueOnce({
value: false,
reason: 'DISABLED',
});
await supertest(app.getHttpServer()).get('/flags-enabled').expect(404);
});
it('should throw a custom exception if the flag is disabled', async () => {
jest.spyOn(defaultProvider, 'resolveBooleanEvaluation').mockResolvedValueOnce({
value: false,
reason: 'DISABLED',
});
await supertest(app.getHttpServer()).get('/flags-enabled-custom-exception').expect(403);
});
it('should throw a custom exception if the flag is disabled with context', async () => {
await supertest(app.getHttpServer())
.get('/flags-enabled-custom-exception-with-context')
.set('x-user-id', '123')
.expect(403);
});
});
describe('OpenFeatureControllerRequireFlagsEnabled', () => {
it('should allow access to the RequireFlagsEnabled controller with global context interceptor', async () => {
await supertest(app.getHttpServer())
.get('/require-flags-enabled')
.set('x-user-id', '123')
.expect(200)
.expect('Hello, world!');
});
it('should throw a 403 - Forbidden exception if user does not match targeting requirements', async () => {
await supertest(app.getHttpServer()).get('/require-flags-enabled').set('x-user-id', 'not-123').expect(403);
});
it('should throw a 403 - Forbidden exception if one of the flags is disabled', async () => {
jest.spyOn(defaultProvider, 'resolveBooleanEvaluation').mockResolvedValueOnce({
value: false,
reason: 'DISABLED',
});
await supertest(app.getHttpServer()).get('/require-flags-enabled').set('x-user-id', '123').expect(403);
});
});
});
});
describe('Without global context interceptor', () => {
let moduleRef: TestingModule;
let app: INestApplication;
beforeAll(async () => {
moduleRef = await Test.createTestingModule({
imports: [
OpenFeatureModule.forRoot({
contextFactory: exampleContextFactory,
defaultProvider,
providers,
useGlobalInterceptor: false
useGlobalInterceptor: false,
}),
],
providers: [OpenFeatureTestService],
controllers: [OpenFeatureController, OpenFeatureControllerContextScopedController],
controllers: [OpenFeatureController, OpenFeatureContextScopedController],
}).compile();
app = moduleRef.createNestApplication();
app = await app.init();
@ -158,7 +212,7 @@ describe('OpenFeature SDK', () => {
});
describe('evaluation context service should', () => {
it('inject empty context if no context interceptor is configured', async function() {
it('inject empty context if no context interceptor is configured', async function () {
const evaluationSpy = jest.spyOn(defaultProvider, 'resolveBooleanEvaluation');
await supertest(app.getHttpServer())
.get('/dynamic-context-in-service')
@ -172,9 +226,26 @@ describe('OpenFeature SDK', () => {
describe('With Controller bound Context interceptor', () => {
it('should not use context if global context interceptor is not configured', async () => {
const evaluationSpy = jest.spyOn(defaultProvider, 'resolveBooleanEvaluation');
await supertest(app.getHttpServer()).get('/controller-context').set('x-user-id', '123').expect(200).expect('true');
await supertest(app.getHttpServer())
.get('/controller-context')
.set('x-user-id', '123')
.expect(200)
.expect('true');
expect(evaluationSpy).toHaveBeenCalledWith('testBooleanFlag', false, { targetingKey: '123' }, {});
});
});
describe('require flags enabled decorator', () => {
it('should return a 404 - Not Found exception if the flag is disabled', async () => {
jest.spyOn(providers.domainScopedClient, 'resolveBooleanEvaluation').mockResolvedValueOnce({
value: false,
reason: 'DISABLED',
});
await supertest(app.getHttpServer())
.get('/controller-context/flags-enabled')
.set('x-user-id', '123')
.expect(404);
});
});
});
});

View File

@ -1,7 +1,14 @@
import { Controller, Get, Injectable, UseInterceptors } from '@nestjs/common';
import type { Observable} from 'rxjs';
import { Controller, ForbiddenException, Get, Injectable, UseInterceptors } from '@nestjs/common';
import type { Observable } from 'rxjs';
import { map } from 'rxjs';
import { BooleanFeatureFlag, ObjectFeatureFlag, NumberFeatureFlag, OpenFeatureClient, StringFeatureFlag } from '../src';
import {
BooleanFeatureFlag,
ObjectFeatureFlag,
NumberFeatureFlag,
OpenFeatureClient,
StringFeatureFlag,
RequireFlagsEnabled,
} from '../src';
import type { Client, EvaluationDetails, FlagValue } from '@openfeature/server-sdk';
import { EvaluationContextInterceptor } from '../src';
@ -84,11 +91,40 @@ export class OpenFeatureController {
public async handleDynamicContextInServiceRequest() {
return this.testService.serviceMethodWithDynamicContext('testBooleanFlag');
}
@RequireFlagsEnabled({
flags: [{ flagKey: 'testBooleanFlag' }],
})
@Get('/flags-enabled')
public async handleGuardedBooleanRequest() {
return 'Get Boolean Flag Success!';
}
@RequireFlagsEnabled({
flags: [{ flagKey: 'testBooleanFlag' }],
exception: new ForbiddenException(),
})
@Get('/flags-enabled-custom-exception')
public async handleBooleanRequestWithCustomException() {
return 'Get Boolean Flag Success!';
}
@RequireFlagsEnabled({
flags: [{ flagKey: 'testBooleanFlag2' }],
exception: new ForbiddenException(),
context: {
targetingKey: 'user-id',
},
})
@Get('/flags-enabled-custom-exception-with-context')
public async handleBooleanRequestWithCustomExceptionAndContext() {
return 'Get Boolean Flag Success!';
}
}
@Controller()
@UseInterceptors(EvaluationContextInterceptor)
export class OpenFeatureControllerContextScopedController {
export class OpenFeatureContextScopedController {
constructor(private testService: OpenFeatureTestService) {}
@Get('/controller-context')
@ -101,4 +137,27 @@ export class OpenFeatureControllerContextScopedController {
) {
return feature.pipe(map((details) => this.testService.serviceMethod(details)));
}
@RequireFlagsEnabled({
flags: [{ flagKey: 'testBooleanFlag' }],
domain: 'domainScopedClient',
})
@Get('/controller-context/flags-enabled')
public async handleBooleanRequest() {
return 'Get Boolean Flag Success!';
}
}
@Controller('require-flags-enabled')
@RequireFlagsEnabled({
flags: [{ flagKey: 'testBooleanFlag', defaultValue: false }, { flagKey: 'testBooleanFlag2' }],
exception: new ForbiddenException(),
})
export class OpenFeatureRequireFlagsEnabledController {
constructor() {}
@Get('/')
public async handleGetRequest() {
return 'Hello, world!';
}
}

View File

@ -1,5 +1,32 @@
# Changelog
## [1.0.1](https://github.com/open-feature/js-sdk/compare/react-sdk-v1.0.0...react-sdk-v1.0.1) (2025-08-18)
### 🐛 Bug Fixes
* **react:** re-evaluate flags on re-render to detect silent provider … ([#1226](https://github.com/open-feature/js-sdk/issues/1226)) ([3105595](https://github.com/open-feature/js-sdk/commit/31055959265a53f52102590f54fa3168811ec678))
## [1.0.0](https://github.com/open-feature/js-sdk/compare/react-sdk-v0.4.11...react-sdk-v1.0.0) (2025-04-14)
### ✨ New Features
* add polyfill for react use hook ([#1157](https://github.com/open-feature/js-sdk/issues/1157)) ([5afe61f](https://github.com/open-feature/js-sdk/commit/5afe61f9e351b037b04c93a1d81aee8016756748))
* add support for abort controllers to event handlers ([#1151](https://github.com/open-feature/js-sdk/issues/1151)) ([6a22483](https://github.com/open-feature/js-sdk/commit/6a224830fa4e62fc30a7802536f6f6fc3f772038))
## [0.4.11](https://github.com/open-feature/js-sdk/compare/react-sdk-v0.4.10...react-sdk-v0.4.11) (2025-02-07)
### ✨ New Features
* export useOpenFeatureClientStatus hook ([#1082](https://github.com/open-feature/js-sdk/issues/1082)) ([4a6b860](https://github.com/open-feature/js-sdk/commit/4a6b8605444edeaf43355713357fecb97dd850b6))
### 🧹 Chore
* update sdk peer ([#1142](https://github.com/open-feature/js-sdk/issues/1142)) ([8bb6206](https://github.com/open-feature/js-sdk/commit/8bb620601e2b8dc7b62d717169b585bd1c886996))
## [0.4.10](https://github.com/open-feature/js-sdk/compare/react-sdk-v0.4.9...react-sdk-v0.4.10) (2024-12-18)

View File

@ -16,8 +16,8 @@
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.8.0&color=yellow&style=for-the-badge" />
</a>
<!-- x-release-please-start-version -->
<a href="https://github.com/open-feature/js-sdk/releases/tag/react-sdk-v0.4.10">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.4.10&color=blue&style=for-the-badge" />
<a href="https://github.com/open-feature/js-sdk/releases/tag/react-sdk-v1.0.1">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.0.1&color=blue&style=for-the-badge" />
</a>
<!-- x-release-please-end -->
<br/>

View File

@ -1,6 +1,6 @@
{
"name": "@openfeature/react-sdk",
"version": "0.4.10",
"version": "1.0.1",
"description": "OpenFeature React SDK",
"main": "./dist/cjs/index.js",
"files": [
@ -47,7 +47,7 @@
},
"homepage": "https://github.com/open-feature/js-sdk#readme",
"peerDependencies": {
"@openfeature/web-sdk": "^1.3.0",
"@openfeature/web-sdk": "^1.5.0",
"react": ">=16.8.0"
},
"devDependencies": {

View File

@ -5,18 +5,24 @@ import type {
EventHandler,
FlagEvaluationOptions,
FlagValue,
JsonValue} from '@openfeature/web-sdk';
import {
ProviderEvents,
ProviderStatus,
JsonValue,
} from '@openfeature/web-sdk';
import { useEffect, useRef, useState } from 'react';
import { ProviderEvents, ProviderStatus } from '@openfeature/web-sdk';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
DEFAULT_OPTIONS,
isEqual,
normalizeOptions,
suspendUntilInitialized,
suspendUntilReconciled,
useProviderOptions,
} from '../internal';
import type { ReactFlagEvaluationNoSuspenseOptions, ReactFlagEvaluationOptions } from '../options';
import { DEFAULT_OPTIONS, isEqual, normalizeOptions, suspendUntilReady, useProviderOptions } from '../internal';
import { useOpenFeatureClient } from '../provider/use-open-feature-client';
import { useOpenFeatureClientStatus } from '../provider/use-open-feature-client-status';
import { useOpenFeatureProvider } from '../provider/use-open-feature-provider';
import type { FlagQuery } from '../query';
import { HookFlagQuery } from './hook-flag-query';
import { HookFlagQuery } from '../internal/hook-flag-query';
// This type is a bit wild-looking, but I think we need it.
// We have to use the conditional, because otherwise useFlag('key', false) would return false, not boolean (too constrained).
@ -280,19 +286,33 @@ function attachHandlersAndResolve<T extends FlagValue>(
const defaultedOptions = { ...DEFAULT_OPTIONS, ...useProviderOptions(), ...normalizeOptions(options) };
const client = useOpenFeatureClient();
const status = useOpenFeatureClientStatus();
const provider = useOpenFeatureProvider();
const isFirstRender = useRef(true);
// suspense
if (defaultedOptions.suspendUntilReady && status === ProviderStatus.NOT_READY) {
suspendUntilReady(client);
suspendUntilInitialized(provider, client);
}
if (defaultedOptions.suspendWhileReconciling && status === ProviderStatus.RECONCILING) {
suspendUntilReady(client);
suspendUntilReconciled(client);
}
const [evaluationDetails, setEvaluationDetails] = useState<EvaluationDetails<T>>(
const [evaluationDetails, setEvaluationDetails] = useState<EvaluationDetails<T>>(() =>
resolver(client).call(client, flagKey, defaultValue, options),
);
// Re-evaluate when dependencies change (handles prop changes like flagKey), or if during a re-render, we have detected a change in the evaluated value
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
const newDetails = resolver(client).call(client, flagKey, defaultValue, options);
if (!isEqual(newDetails.value, evaluationDetails.value)) {
setEvaluationDetails(newDetails);
}
}, [client, flagKey, defaultValue, options, resolver, evaluationDetails]);
// Maintain a mutable reference to the evaluation details to have a up-to-date reference in the handlers.
const evaluationDetailsRef = useRef<EvaluationDetails<T>>(evaluationDetails);
@ -300,7 +320,7 @@ function attachHandlersAndResolve<T extends FlagValue>(
evaluationDetailsRef.current = evaluationDetails;
}, [evaluationDetails]);
const updateEvaluationDetailsCallback = () => {
const updateEvaluationDetailsCallback = useCallback(() => {
const updatedEvaluationDetails = resolver(client).call(client, flagKey, defaultValue, options);
/**
@ -311,41 +331,47 @@ function attachHandlersAndResolve<T extends FlagValue>(
if (!isEqual(updatedEvaluationDetails.value, evaluationDetailsRef.current.value)) {
setEvaluationDetails(updatedEvaluationDetails);
}
};
}, [client, flagKey, defaultValue, options, resolver]);
const configurationChangeCallback: EventHandler<ClientProviderEvents.ConfigurationChanged> = (eventDetails) => {
if (shouldEvaluateFlag(flagKey, eventDetails?.flagsChanged)) {
updateEvaluationDetailsCallback();
}
};
const configurationChangeCallback = useCallback<EventHandler<ClientProviderEvents.ConfigurationChanged>>(
(eventDetails) => {
if (shouldEvaluateFlag(flagKey, eventDetails?.flagsChanged)) {
updateEvaluationDetailsCallback();
}
},
[flagKey, updateEvaluationDetailsCallback],
);
useEffect(() => {
const controller = new AbortController();
if (status === ProviderStatus.NOT_READY) {
// update when the provider is ready
client.addHandler(ProviderEvents.Ready, updateEvaluationDetailsCallback);
client.addHandler(ProviderEvents.Ready, updateEvaluationDetailsCallback, { signal: controller.signal });
}
if (defaultedOptions.updateOnContextChanged) {
// update when the context changes
client.addHandler(ProviderEvents.ContextChanged, updateEvaluationDetailsCallback);
client.addHandler(ProviderEvents.ContextChanged, updateEvaluationDetailsCallback, { signal: controller.signal });
}
return () => {
// cleanup the handlers
client.removeHandler(ProviderEvents.Ready, updateEvaluationDetailsCallback);
client.removeHandler(ProviderEvents.ContextChanged, updateEvaluationDetailsCallback);
};
}, []);
useEffect(() => {
if (defaultedOptions.updateOnConfigurationChanged) {
// update when the provider configuration changes
client.addHandler(ProviderEvents.ConfigurationChanged, configurationChangeCallback);
client.addHandler(ProviderEvents.ConfigurationChanged, configurationChangeCallback, {
signal: controller.signal,
});
}
return () => {
// cleanup the handlers
client.removeHandler(ProviderEvents.ConfigurationChanged, configurationChangeCallback);
controller.abort();
};
}, []);
}, [
client,
status,
defaultedOptions.updateOnContextChanged,
defaultedOptions.updateOnConfigurationChanged,
updateEvaluationDetailsCallback,
configurationChangeCallback,
]);
return evaluationDetails;
}

View File

@ -5,7 +5,8 @@ import { normalizeOptions } from '.';
/**
* The underlying React context.
* DO NOT EXPORT PUBLICLY
*
* **DO NOT EXPORT PUBLICLY**
* @internal
*/
export const Context = React.createContext<
@ -14,7 +15,8 @@ export const Context = React.createContext<
/**
* Get a normalized copy of the options used for this OpenFeatureProvider, see {@link normalizeOptions}.
* DO NOT EXPORT PUBLICLY
*
* **DO NOT EXPORT PUBLICLY**
* @internal
* @returns {NormalizedOptions} normalized options the defaulted options, not defaulted or normalized.
*/

View File

@ -0,0 +1,9 @@
const context = 'Components using OpenFeature must be wrapped with an <OpenFeatureProvider>.';
const tip = 'If you are seeing this in a test, see: https://openfeature.dev/docs/reference/technologies/client/web/react#testing';
export class MissingContextError extends Error {
constructor(reason: string) {
super(`${reason}: ${context} ${tip}`);
this.name = 'MissingContextError';
}
}

View File

@ -1,21 +1,56 @@
import type { Client} from '@openfeature/web-sdk';
import { ProviderEvents } from '@openfeature/web-sdk';
import type { Client, Provider } from '@openfeature/web-sdk';
import { NOOP_PROVIDER, ProviderEvents } from '@openfeature/web-sdk';
import { use } from './use';
/**
* A weak map is used to store the global suspense status for each provider. It's
* important for this to be global to avoid rerender loops. Using useRef won't
* work because the value isn't preserved when a promise is thrown in a component,
* which is how suspense operates.
*/
const globalProviderSuspenseStatus = new WeakMap<Provider, Promise<unknown>>();
/**
* Suspends until the client is ready to evaluate feature flags.
* DO NOT EXPORT PUBLICLY
* @param {Client} client OpenFeature client
*
* **DO NOT EXPORT PUBLICLY**
* @internal
* @param {Provider} provider the provider to suspend for
* @param {Client} client the client to check for readiness
*/
export function suspendUntilReady(client: Client): Promise<void> {
let resolve: (value: unknown) => void;
let reject: () => void;
throw new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
client.addHandler(ProviderEvents.Ready, resolve);
client.addHandler(ProviderEvents.Error, reject);
}).finally(() => {
client.removeHandler(ProviderEvents.Ready, resolve);
client.removeHandler(ProviderEvents.Ready, reject);
});
export function suspendUntilInitialized(provider: Provider, client: Client) {
const statusPromiseRef = globalProviderSuspenseStatus.get(provider);
if (!statusPromiseRef) {
// Noop provider is never ready, so we resolve immediately
const statusPromise = provider !== NOOP_PROVIDER ? isProviderReady(client) : Promise.resolve();
globalProviderSuspenseStatus.set(provider, statusPromise);
// Use will throw the promise and React will trigger a rerender when it's resolved
use(statusPromise);
} else {
// Reuse the existing promise, use won't rethrow if the promise has settled.
use(statusPromiseRef);
}
}
/**
* Suspends until the provider has finished reconciling.
*
* **DO NOT EXPORT PUBLICLY**
* @internal
* @param {Client} client the client to check for readiness
*/
export function suspendUntilReconciled(client: Client) {
use(isProviderReady(client));
}
async function isProviderReady(client: Client) {
const controller = new AbortController();
try {
return await new Promise((resolve, reject) => {
client.addHandler(ProviderEvents.Ready, resolve, { signal: controller.signal });
client.addHandler(ProviderEvents.Error, reject, { signal: controller.signal });
});
} finally {
controller.abort();
}
}

View File

@ -0,0 +1,53 @@
/// <reference types="react/experimental" />
// This function is adopted from https://github.com/vercel/swr
import React from 'react';
/**
* Extends a Promise-like value to include status tracking.
* The extra properties are used to manage the lifecycle of the Promise, indicating its current state.
* More information can be found in the React RFE for the use hook.
* @see https://github.com/reactjs/rfcs/pull/229
*/
export type UsePromise<T> =
Promise<T> & {
status?: 'pending' | 'fulfilled' | 'rejected';
value?: T;
reason?: unknown;
};
/**
* React.use is a React API that lets you read the value of a resource like a Promise or context.
* It was officially added in React 19, so needs to be polyfilled to support older React versions.
* @param {UsePromise} thenable A thenable object that represents a Promise-like value.
* @returns {unknown} The resolved value of the thenable or throws if it's still pending or rejected.
*/
export const use =
React.use ||
// This extra generic is to avoid TypeScript mixing up the generic and JSX syntax
// and emitting an error.
// We assume that this is only for the `use(thenable)` case, not `use(context)`.
// https://github.com/facebook/react/blob/aed00dacfb79d17c53218404c52b1c7aa59c4a89/packages/react-server/src/ReactFizzThenable.js#L45
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(<T, _>(thenable: UsePromise<T>): T => {
switch (thenable.status) {
case 'pending':
throw thenable;
case 'fulfilled':
return thenable.value as T;
case 'rejected':
throw thenable.reason;
default:
thenable.status = 'pending';
thenable.then(
(v) => {
thenable.status = 'fulfilled';
thenable.value = v;
},
(e) => {
thenable.status = 'rejected';
thenable.reason = e;
},
);
throw thenable;
}
});

View File

@ -31,7 +31,7 @@ type ProviderProps = {
* @param {ProviderProps} properties props for the context provider
* @returns {OpenFeatureProvider} context provider
*/
export function OpenFeatureProvider({ client, domain, children, ...options }: ProviderProps) {
export function OpenFeatureProvider({ client, domain, children, ...options }: ProviderProps): JSX.Element {
if (!client) {
client = OpenFeature.getClient(domain);
}

View File

@ -10,22 +10,18 @@ import { ProviderEvents } from '@openfeature/web-sdk';
export function useOpenFeatureClientStatus(): ProviderStatus {
const client = useOpenFeatureClient();
const [status, setStatus] = useState<ProviderStatus>(client.providerStatus);
const controller = new AbortController();
useEffect(() => {
const updateStatus = () => setStatus(client.providerStatus);
client.addHandler(ProviderEvents.ConfigurationChanged, updateStatus);
client.addHandler(ProviderEvents.ContextChanged, updateStatus);
client.addHandler(ProviderEvents.Error, updateStatus);
client.addHandler(ProviderEvents.Ready, updateStatus);
client.addHandler(ProviderEvents.Stale, updateStatus);
client.addHandler(ProviderEvents.Reconciling, updateStatus);
client.addHandler(ProviderEvents.ConfigurationChanged, updateStatus, { signal: controller.signal });
client.addHandler(ProviderEvents.ContextChanged, updateStatus, { signal: controller.signal });
client.addHandler(ProviderEvents.Error, updateStatus, { signal: controller.signal });
client.addHandler(ProviderEvents.Ready, updateStatus, { signal: controller.signal });
client.addHandler(ProviderEvents.Stale, updateStatus, { signal: controller.signal });
client.addHandler(ProviderEvents.Reconciling, updateStatus, { signal: controller.signal });
return () => {
client.removeHandler(ProviderEvents.ConfigurationChanged, updateStatus);
client.removeHandler(ProviderEvents.ContextChanged, updateStatus);
client.removeHandler(ProviderEvents.Error, updateStatus);
client.removeHandler(ProviderEvents.Ready, updateStatus);
client.removeHandler(ProviderEvents.Stale, updateStatus);
client.removeHandler(ProviderEvents.Reconciling, updateStatus);
controller.abort();
};
}, [client]);

View File

@ -1,6 +1,7 @@
import React from 'react';
import { Context } from '../internal';
import type { Client } from '@openfeature/web-sdk';
import { type Client } from '@openfeature/web-sdk';
import { MissingContextError } from '../internal/errors';
/**
* Get the {@link Client} instance for this OpenFeatureProvider context.
@ -11,9 +12,7 @@ export function useOpenFeatureClient(): Client {
const { client } = React.useContext(Context) || {};
if (!client) {
throw new Error(
'No OpenFeature client available - components using OpenFeature must be wrapped with an <OpenFeatureProvider>. If you are seeing this in a test, see: https://openfeature.dev/docs/reference/technologies/client/web/react#testing',
);
throw new MissingContextError('No OpenFeature client available');
}
return client;

View File

@ -0,0 +1,21 @@
import React from 'react';
import { Context } from '../internal';
import { OpenFeature } from '@openfeature/web-sdk';
import type { Provider } from '@openfeature/web-sdk';
import { MissingContextError } from '../internal/errors';
/**
* Get the {@link Provider} bound to the domain specified in the OpenFeatureProvider context.
* Note that it isn't recommended to interact with the provider directly, but rather through
* an OpenFeature client.
* @returns {Provider} provider for this scope
*/
export function useOpenFeatureProvider(): Provider {
const openFeatureContext = React.useContext(Context);
if (!openFeatureContext) {
throw new MissingContextError('No OpenFeature context available');
}
return OpenFeature.getProvider(openFeatureContext.domain);
}

View File

@ -2,7 +2,8 @@ import { ProviderStatus } from '@openfeature/web-sdk';
import { useOpenFeatureClient } from './use-open-feature-client';
import { useOpenFeatureClientStatus } from './use-open-feature-client-status';
import type { ReactFlagEvaluationOptions } from '../options';
import { DEFAULT_OPTIONS, useProviderOptions, normalizeOptions, suspendUntilReady } from '../internal';
import { DEFAULT_OPTIONS, useProviderOptions, normalizeOptions, suspendUntilInitialized } from '../internal';
import { useOpenFeatureProvider } from './use-open-feature-provider';
type Options = Pick<ReactFlagEvaluationOptions, 'suspendUntilReady'>;
@ -14,14 +15,14 @@ type Options = Pick<ReactFlagEvaluationOptions, 'suspendUntilReady'>;
* @returns {boolean} boolean indicating if provider is {@link ProviderStatus.READY}, useful if suspense is disabled and you want to handle loaders on your own
*/
export function useWhenProviderReady(options?: Options): boolean {
const client = useOpenFeatureClient();
const status = useOpenFeatureClientStatus();
// highest priority > evaluation hook options > provider options > default options > lowest priority
const defaultedOptions = { ...DEFAULT_OPTIONS, ...useProviderOptions(), ...normalizeOptions(options) };
const client = useOpenFeatureClient();
const status = useOpenFeatureClientStatus();
const provider = useOpenFeatureProvider();
// suspense
if (defaultedOptions.suspendUntilReady && status === ProviderStatus.NOT_READY) {
suspendUntilReady(client);
suspendUntilInitialized(provider, client);
}
return status === ProviderStatus.READY;

View File

@ -6,12 +6,7 @@ import '@testing-library/jest-dom'; // see: https://testing-library.com/docs/rea
import { act, render, renderHook, screen, waitFor } from '@testing-library/react';
import * as React from 'react';
import { startTransition, useState } from 'react';
import type {
EvaluationContext,
EvaluationDetails,
EventContext,
Hook
} from '../src/';
import type { EvaluationContext, EvaluationDetails, EventContext, Hook } from '../src/';
import {
ErrorCode,
InMemoryProvider,
@ -27,15 +22,18 @@ import {
useObjectFlagValue,
useStringFlagDetails,
useStringFlagValue,
useSuspenseFlag
useSuspenseFlag,
} from '../src/';
import { HookFlagQuery } from '../src/evaluation/hook-flag-query';
import { HookFlagQuery } from '../src/internal/hook-flag-query';
import { TestingProvider } from './test.utils';
// custom provider to have better control over the emitted events
class CustomEventInMemoryProvider extends InMemoryProvider {
putConfigurationWithCustomEvent(flagConfiguration: FlagConfiguration, event: ProviderEmittableEvents, eventContext: EventContext) {
putConfigurationWithCustomEvent(
flagConfiguration: FlagConfiguration,
event: ProviderEmittableEvents,
eventContext: EventContext,
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this['_flagConfiguration'] = { ...flagConfiguration }; // private access hack
this.events.emit(event, eventContext);
@ -395,16 +393,19 @@ describe('evaluation', () => {
expect(screen.queryByTestId('render-count')).toHaveTextContent('1');
await act(async () => {
await rerenderProvider.putConfigurationWithCustomEvent({
...FLAG_CONFIG,
[BOOL_FLAG_KEY]: {
...FLAG_CONFIG[BOOL_FLAG_KEY],
// Change the default; this should be ignored and not cause a re-render because flagsChanged is empty
defaultVariant: 'off',
await rerenderProvider.putConfigurationWithCustomEvent(
{
...FLAG_CONFIG,
[BOOL_FLAG_KEY]: {
...FLAG_CONFIG[BOOL_FLAG_KEY],
// Change the default; this should be ignored and not cause a re-render because flagsChanged is empty
defaultVariant: 'off',
},
// if the flagsChanged is empty, we know nothing has changed, so we don't bother diffing
},
// if the flagsChanged is empty, we know nothing has changed, so we don't bother diffing
}, ClientProviderEvents.ConfigurationChanged, { flagsChanged: [] });
ClientProviderEvents.ConfigurationChanged,
{ flagsChanged: [] },
);
});
expect(screen.queryByTestId('render-count')).toHaveTextContent('1');
@ -420,16 +421,19 @@ describe('evaluation', () => {
expect(screen.queryByTestId('render-count')).toHaveTextContent('1');
await act(async () => {
await rerenderProvider.putConfigurationWithCustomEvent({
...FLAG_CONFIG,
[BOOL_FLAG_KEY]: {
...FLAG_CONFIG[BOOL_FLAG_KEY],
// Change the default variant to trigger a rerender since not only do we check flagsChanged, but we also diff the value
defaultVariant: 'off',
await rerenderProvider.putConfigurationWithCustomEvent(
{
...FLAG_CONFIG,
[BOOL_FLAG_KEY]: {
...FLAG_CONFIG[BOOL_FLAG_KEY],
// Change the default variant to trigger a rerender since not only do we check flagsChanged, but we also diff the value
defaultVariant: 'off',
},
// if the flagsChanged is falsy, we don't know what flags changed - so we attempt to diff everything
},
// if the flagsChanged is falsy, we don't know what flags changed - so we attempt to diff everything
}, ClientProviderEvents.ConfigurationChanged, { flagsChanged: undefined });
ClientProviderEvents.ConfigurationChanged,
{ flagsChanged: undefined },
);
});
expect(screen.queryByTestId('render-count')).toHaveTextContent('2');
@ -573,10 +577,41 @@ describe('evaluation', () => {
},
};
afterEach(() => {
OpenFeature.clearProviders();
});
const suspendingProvider = () => {
return new TestingProvider(CONFIG, DELAY); // delay init by 100ms
};
describe('when using the noop provider', () => {
function TestComponent() {
const { value } = useSuspenseFlag(SUSPENSE_FLAG_KEY, DEFAULT);
return (
<>
<div>{value}</div>
</>
);
}
it('should fallback to the default value on the next rerender', async () => {
render(
<OpenFeatureProvider>
<React.Suspense fallback={<div>{FALLBACK}</div>}>
<TestComponent></TestComponent>
</React.Suspense>
</OpenFeatureProvider>,
);
// The loading indicator should be shown on the first render
expect(screen.queryByText(FALLBACK)).toBeInTheDocument();
// The default value should be shown on the next render
await waitFor(() => expect(screen.queryByText(DEFAULT)).toBeInTheDocument(), {
timeout: DELAY,
});
});
});
describe('updateOnConfigurationChanged=true (default)', () => {
function TestComponent() {
const { value } = useFlag(SUSPENSE_FLAG_KEY, DEFAULT);
@ -889,17 +924,124 @@ describe('evaluation', () => {
OpenFeature.setContext(SUSPEND_OFF, { user: TARGETED_USER });
});
// expect to see static value until we reconcile
await waitFor(() => expect(screen.queryByText(STATIC_FLAG_VALUE_A)).toBeInTheDocument(), {
timeout: DELAY / 2,
});
// make sure we updated after reconciling
// With the fix for useState initialization, the hook now immediately
// reflects provider state changes. This is intentional to handle cases
// where providers don't emit proper events.
// The value updates immediately to the targeted value.
await waitFor(() => expect(screen.queryByText(TARGETED_FLAG_VALUE)).toBeInTheDocument(), {
timeout: DELAY * 2,
});
});
});
describe('re-render behavior when flag values change without provider events', ()=> {
it('should reflect provider state changes on re-render even without provider events', async () => {
let providerValue = 'initial-value';
class SilentUpdateProvider extends InMemoryProvider {
resolveBooleanEvaluation() {
return {
value: true,
variant: 'on',
reason: StandardResolutionReasons.STATIC,
};
}
resolveStringEvaluation() {
return {
value: providerValue,
variant: providerValue,
reason: StandardResolutionReasons.STATIC,
};
}
}
const provider = new SilentUpdateProvider({});
await OpenFeature.setProviderAndWait('test', provider);
// The triggerRender prop forces a re-render
const TestComponent = ({ triggerRender }: { triggerRender: number }) => {
const { value } = useFlag('test-flag', 'default');
return <div data-testid="flag-value" data-render-count={triggerRender}>{value}</div>;
};
const WrapperComponent = () => {
const [renderCount, setRenderCount] = useState(0);
return (
<>
<button onClick={() => setRenderCount(c => c + 1)}>Force Re-render</button>
<TestComponent triggerRender={renderCount} />
</>
);
};
const { getByText } = render(
<OpenFeatureProvider client={OpenFeature.getClient('test')}>
<WrapperComponent />
</OpenFeatureProvider>
);
// Initial value should be rendered
await waitFor(() => {
expect(screen.getByTestId('flag-value')).toHaveTextContent('initial-value');
});
// Change the provider's internal state (without emitting events)
providerValue = 'updated-value';
// Force a re-render of the component
act(() => {
getByText('Force Re-render').click();
});
await waitFor(() => {
expect(screen.getByTestId('flag-value')).toHaveTextContent('updated-value');
});
});
it('should update flag value when flag key prop changes without provider events', async () => {
const provider = new InMemoryProvider({
'flag-a': {
disabled: false,
variants: { on: 'value-a' },
defaultVariant: 'on',
},
'flag-b': {
disabled: false,
variants: { on: 'value-b' },
defaultVariant: 'on',
},
});
await OpenFeature.setProviderAndWait(EVALUATION, provider);
const TestComponent = ({ flagKey }: { flagKey: string }) => {
const { value } = useFlag(flagKey, 'default');
return <div data-testid="flag-value">{value}</div>;
};
const { rerender } = render(
<OpenFeatureProvider client={OpenFeature.getClient(EVALUATION)}>
<TestComponent flagKey="flag-a" />
</OpenFeatureProvider>
);
await waitFor(() => {
expect(screen.getByTestId('flag-value')).toHaveTextContent('value-a');
});
// Change to flag-b (without any provider events)
rerender(
<OpenFeatureProvider client={OpenFeature.getClient(EVALUATION)}>
<TestComponent flagKey="flag-b" />
</OpenFeatureProvider>
);
await waitFor(() => {
expect(screen.getByTestId('flag-value')).toHaveTextContent('value-b');
});
});
});
});
describe('context, hooks and options', () => {

View File

@ -288,7 +288,7 @@ describe('OpenFeatureProvider', () => {
{ timeout: DELAY * 4 },
);
expect(screen.getByText('Will says hi')).toBeInTheDocument();
expect(screen.getByText('Will says aloha')).toBeInTheDocument();
});
});
});

View File

@ -1,5 +1,35 @@
# Changelog
## [1.19.0](https://github.com/open-feature/js-sdk/compare/server-sdk-v1.18.0...server-sdk-v1.19.0) (2025-08-14)
### ✨ New Features
* add evaluation-scoped hook data ([#1216](https://github.com/open-feature/js-sdk/issues/1216)) ([07af3a9](https://github.com/open-feature/js-sdk/commit/07af3a9eda895e9edb24c7ee1e3c1c4f16e17431))
### 🐛 Bug Fixes
* update core dep ([#1228](https://github.com/open-feature/js-sdk/issues/1228)) ([845d24c](https://github.com/open-feature/js-sdk/commit/845d24c5fecc80de3080e49fde839f08ecac6b33))
### 🧹 Chore
* update node to v20+ ([#1203](https://github.com/open-feature/js-sdk/issues/1203)) ([1f33453](https://github.com/open-feature/js-sdk/commit/1f33453c23df0763cbf0d0b44db8d91216377009))
### 📚 Documentation
* Clarify the behavior of setProviderAndWait ([#1180](https://github.com/open-feature/js-sdk/issues/1180)) ([4fe8d87](https://github.com/open-feature/js-sdk/commit/4fe8d87a2e5df2cbd4086cc4f4a380e8857ed8ba))
## [1.18.0](https://github.com/open-feature/js-sdk/compare/server-sdk-v1.17.1...server-sdk-v1.18.0) (2025-04-11)
### ✨ New Features
* add a top-level method for accessing providers ([#1152](https://github.com/open-feature/js-sdk/issues/1152)) ([ae8fce8](https://github.com/open-feature/js-sdk/commit/ae8fce87530005ed20f7e68dc696ce67053fca31))
* add support for abort controllers to event handlers ([#1151](https://github.com/open-feature/js-sdk/issues/1151)) ([6a22483](https://github.com/open-feature/js-sdk/commit/6a224830fa4e62fc30a7802536f6f6fc3f772038))
## [1.17.1](https://github.com/open-feature/js-sdk/compare/server-sdk-v1.17.0...server-sdk-v1.17.1) (2025-02-07)

View File

@ -16,8 +16,8 @@
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.8.0&color=yellow&style=for-the-badge" />
</a>
<!-- x-release-please-start-version -->
<a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.17.1">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.17.1&color=blue&style=for-the-badge" />
<a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.19.0">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.19.0&color=blue&style=for-the-badge" />
</a>
<!-- x-release-please-end -->
<br/>
@ -75,7 +75,11 @@ yarn add @openfeature/server-sdk @openfeature/core
import { OpenFeature } from '@openfeature/server-sdk';
// Register your feature flag provider
await OpenFeature.setProviderAndWait(new YourProviderOfChoice());
try {
await OpenFeature.setProviderAndWait(new YourProviderOfChoice());
} catch (error) {
console.error('Failed to initialize provider:', error);
}
// create a new client
const client = OpenFeature.getClient();

View File

@ -1,6 +1,6 @@
{
"name": "@openfeature/server-sdk",
"version": "1.17.1",
"version": "1.19.0",
"description": "OpenFeature SDK for JavaScript",
"main": "./dist/cjs/index.js",
"files": [
@ -45,12 +45,12 @@
},
"homepage": "https://github.com/open-feature/js-sdk#readme",
"engines": {
"node": ">=18"
"node": ">=20"
},
"peerDependencies": {
"@openfeature/core": "^1.7.0"
"@openfeature/core": "^1.9.0"
},
"devDependencies": {
"@openfeature/core": "^1.7.0"
"@openfeature/core": "^1.9.0"
}
}

View File

@ -12,6 +12,7 @@ import type {
OpenFeatureError,
FlagMetadata,
ResolutionDetails,
EventOptions,
} from '@openfeature/core';
import {
ErrorCode,
@ -21,6 +22,7 @@ import {
StandardResolutionReasons,
instantiateErrorByErrorCode,
statusMatchesEvent,
MapHookData,
} from '@openfeature/core';
import type { FlagEvaluationOptions } from '../../evaluation';
import type { ProviderEvents } from '../../events';
@ -79,7 +81,7 @@ export class OpenFeatureClient implements Client {
return this.providerStatusAccessor();
}
addHandler(eventType: ProviderEvents, handler: EventHandler): void {
addHandler(eventType: ProviderEvents, handler: EventHandler, options?: EventOptions): void {
this.emitterAccessor().addHandler(eventType, handler);
const shouldRunNow = statusMatchesEvent(eventType, this._providerStatus);
@ -95,6 +97,12 @@ export class OpenFeatureClient implements Client {
this._logger?.error('Error running event handler:', err);
}
}
if (options?.signal && typeof options.signal.addEventListener === 'function') {
options.signal.addEventListener('abort', () => {
this.removeHandler(eventType, handler);
});
}
}
removeHandler(eventType: ProviderEvents, handler: EventHandler) {
@ -269,22 +277,26 @@ export class OpenFeatureClient implements Client {
const mergedContext = this.mergeContexts(invocationContext);
// this reference cannot change during the course of evaluation
// it may be used as a key in WeakMaps
const hookContext: Readonly<HookContext> = {
flagKey,
defaultValue,
flagValueType: flagType,
clientMetadata: this.metadata,
providerMetadata: this._provider.metadata,
context: mergedContext,
logger: this._logger,
};
// Create hook context instances for each hook (stable object references for the entire evaluation)
// This ensures hooks can use WeakMaps with hookContext as keys across lifecycle methods
// NOTE: Uses the reversed order to reduce the number of times we have to calculate the index.
const hookContexts = allHooksReversed.map<HookContext>(() =>
Object.freeze({
flagKey,
defaultValue,
flagValueType: flagType,
clientMetadata: this.metadata,
providerMetadata: this._provider.metadata,
context: mergedContext,
logger: this._logger,
hookData: new MapHookData(),
}),
);
let evaluationDetails: EvaluationDetails<T>;
try {
const frozenContext = await this.beforeHooks(allHooks, hookContext, options);
const frozenContext = await this.beforeHooks(allHooks, hookContexts, mergedContext, options);
this.shortCircuitIfNotReady();
@ -299,53 +311,71 @@ export class OpenFeatureClient implements Client {
if (resolutionDetails.errorCode) {
const err = instantiateErrorByErrorCode(resolutionDetails.errorCode, resolutionDetails.errorMessage);
await this.errorHooks(allHooksReversed, hookContext, err, options);
await this.errorHooks(allHooksReversed, hookContexts, err, options);
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, err, resolutionDetails.flagMetadata);
} else {
await this.afterHooks(allHooksReversed, hookContext, resolutionDetails, options);
await this.afterHooks(allHooksReversed, hookContexts, resolutionDetails, options);
evaluationDetails = resolutionDetails;
}
} catch (err: unknown) {
await this.errorHooks(allHooksReversed, hookContext, err, options);
await this.errorHooks(allHooksReversed, hookContexts, err, options);
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, err);
}
await this.finallyHooks(allHooksReversed, hookContext, evaluationDetails, options);
await this.finallyHooks(allHooksReversed, hookContexts, evaluationDetails, options);
return evaluationDetails;
}
private async beforeHooks(hooks: Hook[], hookContext: HookContext, options: FlagEvaluationOptions) {
for (const hook of hooks) {
// freeze the hookContext
Object.freeze(hookContext);
private async beforeHooks(
hooks: Hook[],
hookContexts: HookContext[],
mergedContext: EvaluationContext,
options: FlagEvaluationOptions,
) {
let accumulatedContext = mergedContext;
// use Object.assign to avoid modification of frozen hookContext
Object.assign(hookContext.context, {
...hookContext.context,
...(await hook?.before?.(hookContext, Object.freeze(options.hookHints))),
});
for (const [index, hook] of hooks.entries()) {
const hookContextIndex = hooks.length - 1 - index; // reverse index for before hooks
const hookContext = hookContexts[hookContextIndex];
// Update the context on the stable hook context object
Object.assign(hookContext.context, accumulatedContext);
const hookResult = await hook?.before?.(hookContext, Object.freeze(options.hookHints));
if (hookResult) {
accumulatedContext = {
...accumulatedContext,
...hookResult,
};
for (let i = 0; i < hooks.length; i++) {
Object.assign(hookContexts[hookContextIndex].context, accumulatedContext);
}
}
}
// after before hooks, freeze the EvaluationContext.
return Object.freeze(hookContext.context);
return Object.freeze(accumulatedContext);
}
private async afterHooks(
hooks: Hook[],
hookContext: HookContext,
hookContexts: HookContext[],
evaluationDetails: EvaluationDetails<FlagValue>,
options: FlagEvaluationOptions,
) {
// run "after" hooks sequentially
for (const hook of hooks) {
for (const [index, hook] of hooks.entries()) {
const hookContext = hookContexts[index];
await hook?.after?.(hookContext, evaluationDetails, options.hookHints);
}
}
private async errorHooks(hooks: Hook[], hookContext: HookContext, err: unknown, options: FlagEvaluationOptions) {
private async errorHooks(hooks: Hook[], hookContexts: HookContext[], err: unknown, options: FlagEvaluationOptions) {
// run "error" hooks sequentially
for (const hook of hooks) {
for (const [index, hook] of hooks.entries()) {
try {
const hookContext = hookContexts[index];
await hook?.error?.(hookContext, err, options.hookHints);
} catch (err) {
this._logger.error(`Unhandled error during 'error' hook: ${err}`);
@ -359,13 +389,14 @@ export class OpenFeatureClient implements Client {
private async finallyHooks(
hooks: Hook[],
hookContext: HookContext,
hookContexts: HookContext[],
evaluationDetails: EvaluationDetails<FlagValue>,
options: FlagEvaluationOptions,
) {
// run "finally" hooks sequentially
for (const hook of hooks) {
for (const [index, hook] of hooks.entries()) {
try {
const hookContext = hookContexts[index];
await hook?.finally?.(hookContext, evaluationDetails, options.hookHints);
} catch (err) {
this._logger.error(`Unhandled error during 'finally' hook: ${err}`);

View File

@ -1,7 +1,8 @@
import type { BaseHook, EvaluationContext, FlagValue } from '@openfeature/core';
export type Hook = BaseHook<
export type Hook<TData = Record<string, unknown>> = BaseHook<
FlagValue,
TData,
Promise<EvaluationContext | void> | EvaluationContext | void,
Promise<void> | void
>;

View File

@ -82,7 +82,7 @@ export class OpenFeatureAPI
* Setting a provider supersedes the current provider used in new and existing unbound clients.
* @param {Provider} provider The provider responsible for flag evaluations.
* @returns {Promise<void>}
* @throws Uncaught exceptions thrown by the provider during initialization.
* @throws {Error} If the provider throws an exception during initialization.
*/
setProviderAndWait(provider: Provider): Promise<void>;
/**
@ -92,7 +92,7 @@ export class OpenFeatureAPI
* @param {string} domain The name to identify the client
* @param {Provider} provider The provider responsible for flag evaluations.
* @returns {Promise<void>}
* @throws Uncaught exceptions thrown by the provider during initialization.
* @throws {Error} If the provider throws an exception during initialization.
*/
setProviderAndWait(domain: string, provider: Provider): Promise<void>;
async setProviderAndWait(domainOrProvider?: string | Provider, providerOrUndefined?: Provider): Promise<void> {
@ -138,6 +138,27 @@ export class OpenFeatureAPI
return this;
}
/**
* Get the default provider.
*
* Note that it isn't recommended to interact with the provider directly, but rather through
* an OpenFeature client.
* @returns {Provider} Default Provider
*/
getProvider(): Provider;
/**
* Get the provider bound to the specified domain.
*
* Note that it isn't recommended to interact with the provider directly, but rather through
* an OpenFeature client.
* @param {string} domain An identifier which logically binds clients with providers
* @returns {Provider} Domain-scoped provider
*/
getProvider(domain?: string): Provider;
getProvider(domain?: string): Provider {
return this.getProviderForClient(domain);
}
setContext(context: EvaluationContext): this {
this._context = context;
return this;

View File

@ -449,7 +449,21 @@ describe('Events', () => {
expect(OpenFeature.getHandlers(eventType)).toHaveLength(0);
});
it('The API provides a function allowing the removal of event handlers', () => {
it('The event handler can be removed using an abort signal', () => {
const abortController = new AbortController();
const handler1 = jest.fn();
const handler2 = jest.fn();
const eventType = ProviderEvents.Stale;
OpenFeature.addHandler(eventType, handler1, { signal: abortController.signal });
OpenFeature.addHandler(eventType, handler2);
expect(OpenFeature.getHandlers(eventType)).toHaveLength(2);
abortController.abort();
expect(OpenFeature.getHandlers(eventType)).toHaveLength(1);
});
it('The API provides a function allowing the removal of event handlers from client', () => {
const client = OpenFeature.getClient(domain);
const handler = jest.fn();
const eventType = ProviderEvents.Stale;
@ -459,6 +473,21 @@ describe('Events', () => {
client.removeHandler(eventType, handler);
expect(client.getHandlers(eventType)).toHaveLength(0);
});
it('The event handler on the client can be removed using an abort signal', () => {
const abortController = new AbortController();
const client = OpenFeature.getClient(domain);
const handler1 = jest.fn();
const handler2 = jest.fn();
const eventType = ProviderEvents.Stale;
client.addHandler(eventType, handler1, { signal: abortController.signal });
client.addHandler(eventType, handler2);
expect(client.getHandlers(eventType)).toHaveLength(2);
abortController.abort();
expect(client.getHandlers(eventType)).toHaveLength(1);
});
});
describe('Requirement 5.3.1', () => {

View File

@ -0,0 +1,508 @@
import { OpenFeature } from '../src';
import type { Client } from '../src/client';
import type {
JsonValue,
ResolutionDetails,
HookContext,
BeforeHookContext,
HookData} from '@openfeature/core';
import {
StandardResolutionReasons
} from '@openfeature/core';
import type { Provider } from '../src/provider';
import type { Hook } from '../src/hooks';
const BOOLEAN_VALUE = true;
const STRING_VALUE = 'val';
const NUMBER_VALUE = 1;
const OBJECT_VALUE = { key: 'value' };
// A test hook that stores data in the before stage and retrieves it in after/error/finally
class TestHookWithData implements Hook {
beforeData: unknown;
afterData: unknown;
errorData: unknown;
finallyData: unknown;
async before(hookContext: BeforeHookContext) {
// Store some data
hookContext.hookData.set('testKey', 'testValue');
hookContext.hookData.set('timestamp', Date.now());
hookContext.hookData.set('object', { nested: 'value' });
this.beforeData = hookContext.hookData.get('testKey');
}
async after(hookContext: HookContext) {
// Retrieve data stored in before
this.afterData = hookContext.hookData.get('testKey');
}
async error(hookContext: HookContext) {
// Retrieve data stored in before
this.errorData = hookContext.hookData.get('testKey');
}
async finally(hookContext: HookContext) {
// Retrieve data stored in before
this.finallyData = hookContext.hookData.get('testKey');
}
}
// Typed hook example demonstrating improved type safety
interface OpenTelemetryData {
spanId: string;
traceId: string;
startTime: number;
attributes: Record<string, string | number | boolean>;
}
class TypedOpenTelemetryHook implements Hook {
spanId?: string;
duration?: number;
async before(hookContext: BeforeHookContext) {
const spanId = `span-${Math.random().toString(36).substring(2, 11)}`;
const traceId = `trace-${Math.random().toString(36).substring(2, 11)}`;
// Demonstrate that we can cast for type safety while maintaining compatibility
const typedHookData = hookContext.hookData as unknown as HookData<OpenTelemetryData>;
// Type-safe setting with proper intellisense
typedHookData.set('spanId', spanId);
typedHookData.set('traceId', traceId);
typedHookData.set('startTime', Date.now());
typedHookData.set('attributes', {
flagKey: hookContext.flagKey,
clientName: hookContext.clientMetadata.name || 'unknown',
providerName: hookContext.providerMetadata.name,
});
this.spanId = spanId;
}
async after(hookContext: HookContext) {
// Type-safe getting with proper return types
const typedHookData = hookContext.hookData as unknown as HookData<OpenTelemetryData>;
const startTime: number | undefined = typedHookData.get('startTime');
const spanId: string | undefined = typedHookData.get('spanId');
if (startTime && spanId) {
this.duration = Date.now() - startTime;
// Simulate span completion
}
}
async error(hookContext: HookContext) {
const typedHookData = hookContext.hookData as unknown as HookData<OpenTelemetryData>;
const spanId: string | undefined = typedHookData.get('spanId');
if (spanId) {
// Mark span as error
}
}
}
// A timing hook that measures evaluation duration
class TimingHook implements Hook {
duration?: number;
async before(hookContext: BeforeHookContext) {
hookContext.hookData.set('startTime', Date.now());
}
async after(hookContext: HookContext) {
const startTime = hookContext.hookData.get('startTime') as number;
if (startTime) {
this.duration = Date.now() - startTime;
}
}
async error(hookContext: HookContext) {
const startTime = hookContext.hookData.get('startTime') as number;
if (startTime) {
this.duration = Date.now() - startTime;
}
}
}
// Hook that tests hook data isolation
class IsolationTestHook implements Hook {
hookId: string;
constructor(id: string) {
this.hookId = id;
}
before(hookContext: BeforeHookContext) {
const storedId = hookContext.hookData.get('hookId');
if (storedId) {
throw new Error('Hook data isolation violated! Data is set in before hook.');
}
// Each hook instance should have its own data
hookContext.hookData.set('hookId', this.hookId);
hookContext.hookData.set(`data_${this.hookId}`, `value_${this.hookId}`);
}
after(hookContext: HookContext) {
// Verify we can only see our own data
const storedId = hookContext.hookData.get('hookId');
if (storedId !== this.hookId) {
throw new Error(`Hook data isolation violated! Expected ${this.hookId}, got ${storedId}`);
}
}
}
// Mock provider for testing
const MOCK_PROVIDER: Provider = {
metadata: { name: 'mock-provider' },
async resolveBooleanEvaluation(): Promise<ResolutionDetails<boolean>> {
return {
value: BOOLEAN_VALUE,
variant: 'default',
reason: StandardResolutionReasons.DEFAULT,
};
},
async resolveStringEvaluation(): Promise<ResolutionDetails<string>> {
return {
value: STRING_VALUE,
variant: 'default',
reason: StandardResolutionReasons.DEFAULT,
};
},
async resolveNumberEvaluation(): Promise<ResolutionDetails<number>> {
return {
value: NUMBER_VALUE,
variant: 'default',
reason: StandardResolutionReasons.DEFAULT,
};
},
async resolveObjectEvaluation<T extends JsonValue>(): Promise<ResolutionDetails<T>> {
return {
value: OBJECT_VALUE as unknown as T,
variant: 'default',
reason: StandardResolutionReasons.DEFAULT,
};
},
};
// Mock provider that throws an error
const ERROR_PROVIDER: Provider = {
metadata: { name: 'error-provider' },
async resolveBooleanEvaluation(): Promise<ResolutionDetails<boolean>> {
throw new Error('Provider error');
},
async resolveStringEvaluation(): Promise<ResolutionDetails<string>> {
throw new Error('Provider error');
},
async resolveNumberEvaluation(): Promise<ResolutionDetails<number>> {
throw new Error('Provider error');
},
async resolveObjectEvaluation<T extends JsonValue>(): Promise<ResolutionDetails<T>> {
throw new Error('Provider error');
},
};
describe('Hook Data', () => {
let client: Client;
beforeEach(async () => {
OpenFeature.clearHooks();
await OpenFeature.setProviderAndWait(MOCK_PROVIDER);
client = OpenFeature.getClient();
});
afterEach(async () => {
await OpenFeature.clearProviders();
});
describe('Basic Hook Data Functionality', () => {
it('should allow hooks to store and retrieve data across stages', async () => {
const hook = new TestHookWithData();
client.addHooks(hook);
await client.getBooleanValue('test-flag', false);
// Verify data was stored in before and retrieved in all other stages
expect(hook.beforeData).toBe('testValue');
expect(hook.afterData).toBe('testValue');
expect(hook.finallyData).toBe('testValue');
});
it('should support storing different data types', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const storedValues: any = {};
const hook: Hook = {
async before(hookContext: BeforeHookContext) {
// Store various types
hookContext.hookData.set('string', 'test');
hookContext.hookData.set('number', 42);
hookContext.hookData.set('boolean', true);
hookContext.hookData.set('object', { key: 'value' });
hookContext.hookData.set('array', [1, 2, 3]);
hookContext.hookData.set('null', null);
hookContext.hookData.set('undefined', undefined);
},
async after(hookContext: HookContext) {
storedValues.string = hookContext.hookData.get('string');
storedValues.number = hookContext.hookData.get('number');
storedValues.boolean = hookContext.hookData.get('boolean');
storedValues.object = hookContext.hookData.get('object');
storedValues.array = hookContext.hookData.get('array');
storedValues.null = hookContext.hookData.get('null');
storedValues.undefined = hookContext.hookData.get('undefined');
},
};
client.addHooks(hook);
await client.getBooleanValue('test-flag', false);
expect(storedValues.string).toBe('test');
expect(storedValues.number).toBe(42);
expect(storedValues.boolean).toBe(true);
expect(storedValues.object).toEqual({ key: 'value' });
expect(storedValues.array).toEqual([1, 2, 3]);
expect(storedValues.null).toBeNull();
expect(storedValues.undefined).toBeUndefined();
});
it('should handle hook data in error scenarios', async () => {
await OpenFeature.setProviderAndWait(ERROR_PROVIDER);
const hook = new TestHookWithData();
client.addHooks(hook);
await client.getBooleanValue('test-flag', false);
// Verify data was accessible in error and finally stages
expect(hook.beforeData).toBe('testValue');
expect(hook.errorData).toBe('testValue');
expect(hook.finallyData).toBe('testValue');
expect(hook.afterData).toBeUndefined(); // after should not run on error
});
});
describe('Hook Data API', () => {
it('should support has() method', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hasResults: any = {};
const hook: Hook = {
async before(hookContext: BeforeHookContext) {
hookContext.hookData.set('exists', 'value');
hasResults.beforeExists = hookContext.hookData.has('exists');
hasResults.beforeNotExists = hookContext.hookData.has('notExists');
},
async after(hookContext: HookContext) {
hasResults.afterExists = hookContext.hookData.has('exists');
hasResults.afterNotExists = hookContext.hookData.has('notExists');
},
};
client.addHooks(hook);
await client.getBooleanValue('test-flag', false);
expect(hasResults.beforeExists).toBe(true);
expect(hasResults.beforeNotExists).toBe(false);
expect(hasResults.afterExists).toBe(true);
expect(hasResults.afterNotExists).toBe(false);
});
it('should support delete() method', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deleteResults: any = {};
const hook: Hook = {
async before(hookContext: BeforeHookContext) {
hookContext.hookData.set('toDelete', 'value');
deleteResults.hasBeforeDelete = hookContext.hookData.has('toDelete');
deleteResults.deleteResult = hookContext.hookData.delete('toDelete');
deleteResults.hasAfterDelete = hookContext.hookData.has('toDelete');
deleteResults.deleteAgainResult = hookContext.hookData.delete('toDelete');
},
};
client.addHooks(hook);
await client.getBooleanValue('test-flag', false);
expect(deleteResults.hasBeforeDelete).toBe(true);
expect(deleteResults.deleteResult).toBe(true);
expect(deleteResults.hasAfterDelete).toBe(false);
expect(deleteResults.deleteAgainResult).toBe(false);
});
it('should support clear() method', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const clearResults: any = {};
const hook: Hook = {
async before(hookContext: BeforeHookContext) {
hookContext.hookData.set('key1', 'value1');
hookContext.hookData.set('key2', 'value2');
hookContext.hookData.set('key3', 'value3');
clearResults.hasBeforeClear = hookContext.hookData.has('key1');
hookContext.hookData.clear();
clearResults.hasAfterClear = hookContext.hookData.has('key1');
},
async after(hookContext: HookContext) {
// Verify all data was cleared
clearResults.afterHasKey1 = hookContext.hookData.has('key1');
clearResults.afterHasKey2 = hookContext.hookData.has('key2');
clearResults.afterHasKey3 = hookContext.hookData.has('key3');
},
};
client.addHooks(hook);
await client.getBooleanValue('test-flag', false);
expect(clearResults.hasBeforeClear).toBe(true);
expect(clearResults.hasAfterClear).toBe(false);
expect(clearResults.afterHasKey1).toBe(false);
expect(clearResults.afterHasKey2).toBe(false);
expect(clearResults.afterHasKey3).toBe(false);
});
});
describe('Hook Data Isolation', () => {
it('should isolate data between different hook instances', async () => {
const hook1 = new IsolationTestHook('hook1');
const hook2 = new IsolationTestHook('hook2');
const hook3 = new IsolationTestHook('hook3');
client.addHooks(hook1, hook2, hook3);
expect(await client.getBooleanValue('test-flag', false)).toBe(true);
});
it('should isolate data between the same hook instance', async () => {
const hook = new IsolationTestHook('hook');
client.addHooks(hook, hook);
expect(await client.getBooleanValue('test-flag', false)).toBe(true);
});
it('should not share data between different evaluations', async () => {
let firstEvalData: unknown;
let secondEvalData: unknown;
const hook: Hook = {
async before(hookContext: BeforeHookContext) {
// Check if data exists from previous evaluation
const existingData = hookContext.hookData.get('evalData');
if (existingData) {
throw new Error('Hook data leaked between evaluations!');
}
hookContext.hookData.set('evalData', 'evaluation-specific');
},
async after(hookContext: HookContext) {
if (!firstEvalData) {
firstEvalData = hookContext.hookData.get('evalData');
} else {
secondEvalData = hookContext.hookData.get('evalData');
}
},
};
client.addHooks(hook);
// First evaluation
await client.getBooleanValue('test-flag', false);
// Second evaluation
await client.getBooleanValue('test-flag', false);
expect(firstEvalData).toBe('evaluation-specific');
expect(secondEvalData).toBe('evaluation-specific');
});
it('should isolate data between global, client, and invocation hooks', async () => {
const globalHook = new IsolationTestHook('global');
const clientHook = new IsolationTestHook('client');
const invocationHook = new IsolationTestHook('invocation');
OpenFeature.addHooks(globalHook);
client.addHooks(clientHook);
expect(await client.getBooleanValue('test-flag', false, {}, { hooks: [invocationHook] })).toBe(true);
});
});
describe('Use Cases', () => {
it('should support timing measurements', async () => {
const timingHook = new TimingHook();
client.addHooks(timingHook);
await client.getBooleanValue('test-flag', false);
expect(timingHook.duration).toBeDefined();
expect(timingHook.duration).toBeGreaterThanOrEqual(0);
});
it('should support multi-stage validation accumulation', async () => {
let finalErrors: string[] = [];
const validationHook: Hook = {
async before(hookContext: BeforeHookContext) {
hookContext.hookData.set('errors', []);
// Simulate validation
const errors = hookContext.hookData.get('errors') as string[];
if (!hookContext.context.userId) {
errors.push('Missing userId');
}
if (!hookContext.context.region) {
errors.push('Missing region');
}
},
async finally(hookContext: HookContext) {
finalErrors = (hookContext.hookData.get('errors') as string[]) || [];
},
};
client.addHooks(validationHook);
await client.getBooleanValue('test-flag', false, {});
expect(finalErrors).toContain('Missing userId');
expect(finalErrors).toContain('Missing region');
});
it('should support request correlation', async () => {
let correlationId: string | undefined;
const correlationHook: Hook = {
async before(hookContext: BeforeHookContext) {
const id = `req-${Date.now()}-${Math.random()}`;
hookContext.hookData.set('correlationId', id);
},
async after(hookContext: HookContext) {
correlationId = hookContext.hookData.get('correlationId') as string;
},
};
client.addHooks(correlationHook);
await client.getBooleanValue('test-flag', false);
expect(correlationId).toBeDefined();
expect(correlationId).toMatch(/^req-\d+-[\d.]+$/);
});
it('should support typed hook data for better type safety', async () => {
const typedHook = new TypedOpenTelemetryHook();
client.addHooks(typedHook);
await client.getBooleanValue('test-flag', false);
// Verify the typed hook worked correctly
expect(typedHook.spanId).toBeDefined();
expect(typedHook.spanId).toMatch(/^span-[a-z0-9]+$/);
expect(typedHook.duration).toBeDefined();
expect(typeof typedHook.duration).toBe('number');
expect(typedHook.duration).toBeGreaterThanOrEqual(0);
});
});
});

View File

@ -74,8 +74,8 @@ describe('OpenFeature', () => {
it('should set the default provider if no domain is provided', () => {
const provider = mockProvider();
OpenFeature.setProvider(provider);
const client = OpenFeature.getClient();
expect(client.metadata.providerMetadata.name).toEqual(provider.metadata.name);
const registeredProvider = OpenFeature.getProvider();
expect(registeredProvider).toEqual(provider);
});
it('should not change providers associated with a domain when setting a new default provider', () => {
@ -85,11 +85,11 @@ describe('OpenFeature', () => {
OpenFeature.setProvider(provider);
OpenFeature.setProvider(domain, fakeProvider);
const defaultClient = OpenFeature.getClient();
const domainSpecificClient = OpenFeature.getClient(domain);
const defaultProvider = OpenFeature.getProvider();
const domainSpecificProvider = OpenFeature.getProvider(domain);
expect(defaultClient.metadata.providerMetadata.name).toEqual(provider.metadata.name);
expect(domainSpecificClient.metadata.providerMetadata.name).toEqual(fakeProvider.metadata.name);
expect(defaultProvider).toEqual(provider);
expect(domainSpecificProvider).toEqual(fakeProvider);
});
it('should bind a new provider to existing clients in a matching domain', () => {

View File

@ -1,5 +1,41 @@
# Changelog
## [1.9.0](https://github.com/open-feature/js-sdk/compare/core-v1.8.1...core-v1.9.0) (2025-08-10)
### ✨ New Features
* add evaluation-scoped hook data ([#1216](https://github.com/open-feature/js-sdk/issues/1216)) ([07af3a9](https://github.com/open-feature/js-sdk/commit/07af3a9eda895e9edb24c7ee1e3c1c4f16e17431))
* support Angular 20 ([#1220](https://github.com/open-feature/js-sdk/issues/1220)) ([aa232a9](https://github.com/open-feature/js-sdk/commit/aa232a9d6a8dfa416380ccdecd71843d3e361048))
## [1.8.1](https://github.com/open-feature/js-sdk/compare/core-v1.8.0...core-v1.8.1) (2025-06-04)
### 🔄 Refactoring
* **telemetry:** update telemetry attributes and remove unused evaluation data ([#1189](https://github.com/open-feature/js-sdk/issues/1189)) ([3e6bcae](https://github.com/open-feature/js-sdk/commit/3e6bcaef0bb5c05e914a0078f0cb884b7f74f068))
## [1.8.0](https://github.com/open-feature/js-sdk/compare/core-v1.7.2...core-v1.8.0) (2025-04-10)
### ✨ New Features
* add support for abort controllers to event handlers ([#1151](https://github.com/open-feature/js-sdk/issues/1151)) ([6a22483](https://github.com/open-feature/js-sdk/commit/6a224830fa4e62fc30a7802536f6f6fc3f772038))
## [1.7.2](https://github.com/open-feature/js-sdk/compare/core-v1.7.1...core-v1.7.2) (2025-02-18)
### 🐛 Bug Fixes
* rename evaluation event property from data to body ([4c2b01e](https://github.com/open-feature/js-sdk/commit/4c2b01e36773091038d758ac10bba06056ff4c45))
## [1.7.1](https://github.com/open-feature/js-sdk/compare/core-v1.7.0...core-v1.7.1) (2025-02-13)
### 🐛 Bug Fixes
* export missing telemetry functionality ([#1148](https://github.com/open-feature/js-sdk/issues/1148)) ([dcbc300](https://github.com/open-feature/js-sdk/commit/dcbc30090e7611c60e06d05826f6471f0c8c4009))
## [1.7.0](https://github.com/open-feature/js-sdk/compare/core-v1.6.0...core-v1.7.0) (2025-02-07)

View File

@ -1,6 +1,6 @@
{
"name": "@openfeature/core",
"version": "1.7.0",
"version": "1.9.0",
"description": "Shared OpenFeature JS components (server and web)",
"main": "./dist/cjs/index.js",
"files": [

View File

@ -18,3 +18,4 @@ export abstract class OpenFeatureError extends Error {
this.cause = options?.cause;
}
}

View File

@ -3,7 +3,7 @@ import type { JsonValue } from '../types/structure';
export type FlagValueType = 'boolean' | 'string' | 'number' | 'object';
/**
* Represents a JSON node value, or Date.
* Represents a JSON node value.
*/
export type FlagValue = boolean | string | number | JsonValue;

View File

@ -66,6 +66,9 @@ export type EventDetails<
export type EventHandler<
T extends ServerProviderEvents | ClientProviderEvents = ServerProviderEvents | ClientProviderEvents,
> = (eventDetails?: EventDetails<T>) => Promise<unknown> | unknown;
export type EventOptions = {
signal?: AbortSignal;
};
export interface Eventing<T extends ServerProviderEvents | ClientProviderEvents> {
/**
@ -73,6 +76,7 @@ export interface Eventing<T extends ServerProviderEvents | ClientProviderEvents>
* The handlers are called in the order they have been added.
* @param eventType The provider event type to listen to
* @param {EventHandler} handler The handler to run on occurrence of the event type
* @param {EventOptions} options Optional options such as signal for aborting
*/
addHandler(
eventType: T extends ClientProviderEvents
@ -83,14 +87,17 @@ export interface Eventing<T extends ServerProviderEvents | ClientProviderEvents>
? ClientProviderEvents.ConfigurationChanged
: ServerProviderEvents.ConfigurationChanged
>,
options?: EventOptions,
): void;
addHandler(
eventType: T extends ClientProviderEvents ? ClientNotChangeEvents : ServerNotChangeEvents,
handler: EventHandler<T extends ClientProviderEvents ? ClientNotChangeEvents : ServerNotChangeEvents>,
options?: EventOptions,
): void;
addHandler(
eventType: T extends ClientProviderEvents ? ClientProviderEvents : ServerProviderEvents,
handler: EventHandler<T extends ClientProviderEvents ? ClientProviderEvents : ServerProviderEvents>,
options?: EventOptions,
): void;
/**

View File

@ -0,0 +1,72 @@
/**
* A mutable data structure for hooks to maintain state across their lifecycle.
* Each hook instance gets its own isolated data store that persists for the
* duration of a single flag evaluation.
* @template TData - A record type that defines the shape of the stored data
*/
export interface HookData<TData = Record<string, unknown>> {
/**
* Sets a value in the hook data store.
* @param key The key to store the value under
* @param value The value to store
*/
set<K extends keyof TData>(key: K, value: TData[K]): void;
set(key: string, value: unknown): void;
/**
* Gets a value from the hook data store.
* @param key The key to retrieve the value for
* @returns The stored value, or undefined if not found
*/
get<K extends keyof TData>(key: K): TData[K] | undefined;
get(key: string): unknown;
/**
* Checks if a key exists in the hook data store.
* @param key The key to check
* @returns True if the key exists, false otherwise
*/
has<K extends keyof TData>(key: K): boolean;
has(key: string): boolean;
/**
* Deletes a value from the hook data store.
* @param key The key to delete
* @returns True if the key was deleted, false if it didn't exist
*/
delete<K extends keyof TData>(key: K): boolean;
delete(key: string): boolean;
/**
* Clears all values from the hook data store.
*/
clear(): void;
}
/**
* Default implementation of HookData using a Map.
* @template TData - A record type that defines the shape of the stored data
*/
export class MapHookData<TData = Record<string, unknown>> implements HookData<TData> {
private readonly data = new Map<keyof TData, TData[keyof TData]>();
set<K extends keyof TData>(key: K, value: TData[K]): void {
this.data.set(key, value);
}
get<K extends keyof TData>(key: K): TData[K] | undefined {
return this.data.get(key) as TData[K] | undefined;
}
has<K extends keyof TData>(key: K): boolean {
return this.data.has(key);
}
delete<K extends keyof TData>(key: K): boolean {
return this.data.delete(key);
}
clear(): void {
this.data.clear();
}
}

View File

@ -1,14 +1,19 @@
import type { BeforeHookContext, HookContext, HookHints } from './hooks';
import type { EvaluationDetails, FlagValue } from '../evaluation';
export interface BaseHook<T extends FlagValue = FlagValue, BeforeHookReturn = unknown, HooksReturn = unknown> {
export interface BaseHook<
T extends FlagValue = FlagValue,
TData = Record<string, unknown>,
BeforeHookReturn = unknown,
HooksReturn = unknown
> {
/**
* Runs before flag values are resolved from the provider.
* If an EvaluationContext is returned, it will be merged with the pre-existing EvaluationContext.
* @param hookContext
* @param hookHints
*/
before?(hookContext: BeforeHookContext, hookHints?: HookHints): BeforeHookReturn;
before?(hookContext: BeforeHookContext<T, TData>, hookHints?: HookHints): BeforeHookReturn;
/**
* Runs after flag values are successfully resolved from the provider.
@ -17,7 +22,7 @@ export interface BaseHook<T extends FlagValue = FlagValue, BeforeHookReturn = un
* @param hookHints
*/
after?(
hookContext: Readonly<HookContext<T>>,
hookContext: Readonly<HookContext<T, TData>>,
evaluationDetails: EvaluationDetails<T>,
hookHints?: HookHints,
): HooksReturn;
@ -28,7 +33,7 @@ export interface BaseHook<T extends FlagValue = FlagValue, BeforeHookReturn = un
* @param error
* @param hookHints
*/
error?(hookContext: Readonly<HookContext<T>>, error: unknown, hookHints?: HookHints): HooksReturn;
error?(hookContext: Readonly<HookContext<T, TData>>, error: unknown, hookHints?: HookHints): HooksReturn;
/**
* Runs after all other hook stages, regardless of success or error.
@ -37,8 +42,9 @@ export interface BaseHook<T extends FlagValue = FlagValue, BeforeHookReturn = un
* @param hookHints
*/
finally?(
hookContext: Readonly<HookContext<T>>,
hookContext: Readonly<HookContext<T, TData>>,
evaluationDetails: EvaluationDetails<T>,
hookHints?: HookHints,
): HooksReturn;
}

View File

@ -2,10 +2,11 @@ import type { ProviderMetadata } from '../provider';
import type { ClientMetadata } from '../client';
import type { EvaluationContext, FlagValue, FlagValueType } from '../evaluation';
import type { Logger } from '../logger';
import type { HookData } from './hook-data';
export type HookHints = Readonly<Record<string, unknown>>;
export interface HookContext<T extends FlagValue = FlagValue> {
export interface HookContext<T extends FlagValue = FlagValue, TData = Record<string, unknown>> {
readonly flagKey: string;
readonly defaultValue: T;
readonly flagValueType: FlagValueType;
@ -13,8 +14,9 @@ export interface HookContext<T extends FlagValue = FlagValue> {
readonly clientMetadata: ClientMetadata;
readonly providerMetadata: ProviderMetadata;
readonly logger: Logger;
readonly hookData: HookData<TData>;
}
export interface BeforeHookContext extends HookContext {
export interface BeforeHookContext<T extends FlagValue = FlagValue, TData = Record<string, unknown>> extends HookContext<T, TData> {
context: EvaluationContext;
}

View File

@ -1,3 +1,4 @@
export * from './hook';
export * from './hooks';
export * from './evaluation-lifecycle';
export * from './hook-data';

View File

@ -4,6 +4,7 @@ export * from './client';
export * from './errors';
export * from './events';
export * from './logger';
export * from './telemetry';
export * from './provider';
export * from './evaluation';
export * from './type-guards';

View File

@ -7,14 +7,13 @@ import type {
EventDetails,
EventHandler,
Eventing,
GenericEventEmitter} from './events';
import {
AllProviderEvents,
statusMatchesEvent,
EventOptions,
GenericEventEmitter,
} from './events';
import { AllProviderEvents, statusMatchesEvent } from './events';
import { isDefined } from './filter';
import type { BaseHook, EvaluationLifeCycle } from './hooks';
import type { Logger, ManageLogger} from './logger';
import type { Logger, ManageLogger } from './logger';
import { DefaultLogger, SafeLogger } from './logger';
import type { ClientProviderStatus, CommonProvider, ProviderMetadata, ServerProviderStatus } from './provider';
import { objectOrUndefined, stringOrUndefined } from './type-guards';
@ -154,8 +153,9 @@ export abstract class OpenFeatureCommonAPI<
* API (global) events run for all providers.
* @param {AnyProviderEvent} eventType The provider event type to listen to
* @param {EventHandler} handler The handler to run on occurrence of the event type
* @param {EventOptions} options Optional options such as signal for aborting
*/
addHandler<T extends AnyProviderEvent>(eventType: T, handler: EventHandler): void {
addHandler<T extends AnyProviderEvent>(eventType: T, handler: EventHandler, options?: EventOptions): void {
[...new Map([[undefined, this._defaultProvider]]), ...this._domainScopedProviders].forEach((keyProviderTuple) => {
const domain = keyProviderTuple[0];
const provider = keyProviderTuple[1].provider;
@ -173,6 +173,11 @@ export abstract class OpenFeatureCommonAPI<
});
this._apiEmitter.addHandler(eventType, handler);
if (options?.signal && typeof options.signal.addEventListener === 'function') {
options.signal.addEventListener('abort', () => {
this.removeHandler(eventType, handler);
});
}
}
/**
@ -248,7 +253,7 @@ export abstract class OpenFeatureCommonAPI<
// initialize the provider if it implements "initialize" and it's not already registered
if (typeof provider.initialize === 'function' && !this.allProviders.includes(provider)) {
initializationPromise = provider
.initialize?.(domain ? this._domainScopedContext.get(domain) ?? this._context : this._context)
.initialize?.(domain ? (this._domainScopedContext.get(domain) ?? this._context) : this._context)
?.then(() => {
wrappedProvider.status = this._statusEnumType.READY;
// fetch the most recent event emitters, some may have been added during init

View File

@ -20,6 +20,14 @@ export const TelemetryAttribute = {
* - example: `flag_not_found`
*/
ERROR_CODE: 'error.type',
/**
* A message explaining the nature of an error occurring during flag evaluation.
*
* - type: `string`
* - requirement level: `recommended`
* - example: `Flag not found`
*/
ERROR_MESSAGE: 'error.message',
/**
* A semantic identifier for an evaluated flag value.
*
@ -28,7 +36,16 @@ export const TelemetryAttribute = {
* - condition: variant is defined on the evaluation details
* - example: `blue`; `on`; `true`
*/
VARIANT: 'feature_flag.variant',
VARIANT: 'feature_flag.result.variant',
/**
* The evaluated value of the feature flag.
*
* - type: `undefined`
* - requirement level: `conditionally required`
* - condition: variant is not defined on the evaluation details
* - example: `#ff0000`; `1`; `true`
*/
VALUE: 'feature_flag.result.value',
/**
* The unique identifier for the flag evaluation context. For example, the targeting key.
*
@ -37,14 +54,6 @@ export const TelemetryAttribute = {
* - example: `5157782b-2203-4c80-a857-dbbd5e7761db`
*/
CONTEXT_ID: 'feature_flag.context.id',
/**
* A message explaining the nature of an error occurring during flag evaluation.
*
* - type: `string`
* - requirement level: `recommended`
* - example: `Flag not found`
*/
ERROR_MESSAGE: 'feature_flag.evaluation.error.message',
/**
* The reason code which shows how a feature flag value was determined.
*
@ -52,7 +61,7 @@ export const TelemetryAttribute = {
* - requirement level: `recommended`
* - example: `targeting_match`
*/
REASON: 'feature_flag.evaluation.reason',
REASON: 'feature_flag.result.reason',
/**
* Describes a class of error the operation ended with.
*
@ -60,7 +69,7 @@ export const TelemetryAttribute = {
* - requirement level: `recommended`
* - example: `flag_not_found`
*/
PROVIDER: 'feature_flag.provider_name',
PROVIDER: 'feature_flag.provider.name',
/**
* The identifier of the flag set to which the feature flag belongs.
*

View File

@ -1,17 +0,0 @@
/**
* Event data, sometimes referred as "body", is specific to a specific event.
* In this case, the event is `feature_flag.evaluation`. That's why the prefix
* is omitted from the values.
* @see https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
*/
export const TelemetryEvaluationData = {
/**
* The evaluated value of the feature flag.
*
* - type: `undefined`
* - requirement level: `conditionally required`
* - condition: variant is not defined on the evaluation details
* - example: `#ff0000`; `1`; `true`
*/
VALUE: 'value',
} as const;

View File

@ -1,14 +1,19 @@
import { ErrorCode, StandardResolutionReasons, type EvaluationDetails, type FlagValue } from '../evaluation/evaluation';
import type { HookContext } from '../hooks/hooks';
import type { JsonValue } from '../types';
import { TelemetryAttribute } from './attributes';
import { TelemetryEvaluationData } from './evaluation-data';
import { TelemetryFlagMetadata } from './flag-metadata';
type EvaluationEvent = {
/**
* The name of the feature flag evaluation event.
*/
name: string;
attributes: Record<string, string | number | boolean>;
data: Record<string, JsonValue>;
/**
* The attributes of an OpenTelemetry compliant event for flag evaluation.
* @experimental The attributes are subject to change.
* @see https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
*/
attributes: Record<string, string | number | boolean | FlagValue>;
};
const FLAG_EVALUATION_EVENT_NAME = 'feature_flag.evaluation';
@ -28,12 +33,11 @@ export function createEvaluationEvent(
[TelemetryAttribute.PROVIDER]: hookContext.providerMetadata.name,
[TelemetryAttribute.REASON]: (evaluationDetails.reason ?? StandardResolutionReasons.UNKNOWN).toLowerCase(),
};
const data: EvaluationEvent['data'] = {};
if (evaluationDetails.variant) {
attributes[TelemetryAttribute.VARIANT] = evaluationDetails.variant;
} else {
data[TelemetryEvaluationData.VALUE] = evaluationDetails.value;
attributes[TelemetryAttribute.VALUE] = evaluationDetails.value;
}
const contextId =
@ -62,6 +66,5 @@ export function createEvaluationEvent(
return {
name: FLAG_EVALUATION_EVENT_NAME,
attributes,
data,
};
}

View File

@ -1,4 +1,3 @@
export * from './attributes';
export * from './evaluation-data';
export * from './flag-metadata';
export * from './evaluation-event';

View File

@ -14,17 +14,23 @@ class TestEventEmitter extends GenericEventEmitter<AnyProviderEvent> {
}
}
// a little function to make sure we're at least waiting for the event loop
// a little function to make sure we're at least waiting for the event loop
// to clear before we start making assertions
const wait = (millis = 0) => {
return new Promise(resolve => {setTimeout(resolve, millis);});
return new Promise((resolve) => {
setTimeout(resolve, millis);
});
};
describe('GenericEventEmitter', () => {
const emitter = new TestEventEmitter();
afterEach(() => {
emitter.removeAllHandlers();
});
describe('addHandler should', function () {
it('attach handler for event type', async function () {
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
emitter.addHandler(AllProviderEvents.Ready, handler1);
emitter.emit(AllProviderEvents.Ready);
@ -35,8 +41,6 @@ describe('GenericEventEmitter', () => {
});
it('attach several handlers for event type', async function () {
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
const handler2 = jest.fn();
const handler3 = jest.fn();
@ -64,7 +68,6 @@ describe('GenericEventEmitter', () => {
debug: () => done(),
};
const emitter = new TestEventEmitter();
emitter.setLogger(logger);
emitter.addHandler(AllProviderEvents.Ready, async () => {
@ -74,8 +77,6 @@ describe('GenericEventEmitter', () => {
});
it('trigger handler for event type', async function () {
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
emitter.addHandler(AllProviderEvents.Ready, handler1);
emitter.emit(AllProviderEvents.Ready);
@ -87,7 +88,6 @@ describe('GenericEventEmitter', () => {
it('trigger handler for event type with event data', async function () {
const event: ReadyEvent = { message: 'message' };
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
emitter.addHandler(AllProviderEvents.Ready, handler1);
@ -99,8 +99,6 @@ describe('GenericEventEmitter', () => {
});
it('trigger several handlers for event type', async function () {
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
const handler2 = jest.fn();
const handler3 = jest.fn();
@ -121,8 +119,6 @@ describe('GenericEventEmitter', () => {
describe('removeHandler should', () => {
it('remove single handler', async function () {
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
emitter.addHandler(AllProviderEvents.Ready, handler1);
@ -138,8 +134,6 @@ describe('GenericEventEmitter', () => {
describe('removeAllHandlers should', () => {
it('remove all handlers for event type', async function () {
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
const handler2 = jest.fn();
emitter.addHandler(AllProviderEvents.Ready, handler1);
@ -156,8 +150,6 @@ describe('GenericEventEmitter', () => {
});
it('remove same handler when assigned to multiple events', async function () {
const emitter = new TestEventEmitter();
const handler = jest.fn();
emitter.addHandler(AllProviderEvents.Stale, handler);
emitter.addHandler(AllProviderEvents.ContextChanged, handler);
@ -174,8 +166,6 @@ describe('GenericEventEmitter', () => {
});
it('allow addition/removal of duplicate handlers', async function () {
const emitter = new TestEventEmitter();
const handler = jest.fn();
emitter.addHandler(AllProviderEvents.Stale, handler);
emitter.addHandler(AllProviderEvents.Stale, handler);
@ -191,8 +181,6 @@ describe('GenericEventEmitter', () => {
});
it('allow duplicate event handlers and call them', async function () {
const emitter = new TestEventEmitter();
const handler = jest.fn();
emitter.addHandler(AllProviderEvents.Stale, handler);
emitter.addHandler(AllProviderEvents.Stale, handler);
@ -205,8 +193,6 @@ describe('GenericEventEmitter', () => {
});
it('remove all handlers only for event type', async function () {
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
const handler2 = jest.fn();
emitter.addHandler(AllProviderEvents.Ready, handler1);
@ -223,8 +209,6 @@ describe('GenericEventEmitter', () => {
});
it('remove all handlers if no event type is given', async function () {
const emitter = new TestEventEmitter();
const handler1 = jest.fn();
const handler2 = jest.fn();
emitter.addHandler(AllProviderEvents.Ready, handler1);

View File

@ -0,0 +1,214 @@
import type { HookData, BaseHook, BeforeHookContext, HookContext } from '../src/hooks';
import { MapHookData } from '../src/hooks';
import type { FlagValue } from '../src/evaluation';
describe('Hook Data Type Safety', () => {
it('should provide type safety with typed hook data', () => {
// Define a strict type for hook data
interface MyHookData {
startTime: number;
userId: string;
metadata: { version: string; feature: boolean };
tags: string[];
}
const hookData = new MapHookData<MyHookData>();
// Type-safe setting and getting
hookData.set('startTime', 123456);
hookData.set('userId', 'user-123');
hookData.set('metadata', { version: '1.0.0', feature: true });
hookData.set('tags', ['tag1', 'tag2']);
// TypeScript should infer the correct return types
const startTime: number | undefined = hookData.get('startTime');
const userId: string | undefined = hookData.get('userId');
const metadata: { version: string; feature: boolean } | undefined = hookData.get('metadata');
const tags: string[] | undefined = hookData.get('tags');
// Verify the values
expect(startTime).toBe(123456);
expect(userId).toBe('user-123');
expect(metadata).toEqual({ version: '1.0.0', feature: true });
expect(tags).toEqual(['tag1', 'tag2']);
// Type-safe existence checks
expect(hookData.has('startTime')).toBe(true);
expect(hookData.has('userId')).toBe(true);
expect(hookData.has('metadata')).toBe(true);
expect(hookData.has('tags')).toBe(true);
// Type-safe deletion
expect(hookData.delete('tags')).toBe(true);
expect(hookData.has('tags')).toBe(false);
});
it('should support untyped usage for backward compatibility', () => {
const hookData: HookData = new MapHookData();
// Untyped usage still works
hookData.set('anyKey', 'anyValue');
hookData.set('numberKey', 42);
hookData.set('objectKey', { nested: true });
const value: unknown = hookData.get('anyKey');
const numberValue: unknown = hookData.get('numberKey');
const objectValue: unknown = hookData.get('objectKey');
expect(value).toBe('anyValue');
expect(numberValue).toBe(42);
expect(objectValue).toEqual({ nested: true });
});
it('should support mixed usage with typed and untyped keys', () => {
interface PartiallyTypedData {
correlationId: string;
timestamp: number;
}
const hookData: HookData<PartiallyTypedData> = new MapHookData<PartiallyTypedData>();
// Typed usage
hookData.set('correlationId', 'abc-123');
hookData.set('timestamp', Date.now());
// Untyped usage for additional keys
hookData.set('dynamicKey', 'dynamicValue');
// Type-safe retrieval for typed keys
const correlationId: string | undefined = hookData.get('correlationId');
const timestamp: number | undefined = hookData.get('timestamp');
// Untyped retrieval for dynamic keys
const dynamicValue: unknown = hookData.get('dynamicKey');
expect(correlationId).toBe('abc-123');
expect(typeof timestamp).toBe('number');
expect(dynamicValue).toBe('dynamicValue');
});
it('should work with complex nested types', () => {
interface ComplexHookData {
request: {
id: string;
headers: Record<string, string>;
body?: { [key: string]: unknown };
};
response: {
status: number;
data: unknown;
headers: Record<string, string>;
};
metrics: {
startTime: number;
endTime?: number;
duration?: number;
};
}
const hookData: HookData<ComplexHookData> = new MapHookData<ComplexHookData>();
const requestData = {
id: 'req-123',
headers: { 'Content-Type': 'application/json' },
body: { flag: 'test-flag' },
};
hookData.set('request', requestData);
hookData.set('metrics', { startTime: Date.now() });
const retrievedRequest = hookData.get('request');
const retrievedMetrics = hookData.get('metrics');
expect(retrievedRequest).toEqual(requestData);
expect(retrievedMetrics?.startTime).toBeDefined();
expect(typeof retrievedMetrics?.startTime).toBe('number');
});
it('should support generic type inference', () => {
// This function demonstrates how the generic types work in practice
function createTypedHookData<T>(): HookData<T> {
return new MapHookData<T>();
}
interface TimingData {
start: number;
checkpoint: number;
}
const timingHookData = createTypedHookData<TimingData>();
timingHookData.set('start', performance.now());
timingHookData.set('checkpoint', performance.now());
const start: number | undefined = timingHookData.get('start');
const checkpoint: number | undefined = timingHookData.get('checkpoint');
expect(typeof start).toBe('number');
expect(typeof checkpoint).toBe('number');
});
it('should work with BaseHook interface without casting', () => {
interface TestHookData {
testId: string;
startTime: number;
metadata: { version: string };
}
class TestTypedHook implements BaseHook<FlagValue, TestHookData> {
capturedData: { testId?: string; duration?: number } = {};
before(hookContext: BeforeHookContext<FlagValue, TestHookData>) {
// No casting needed - TypeScript knows the types
hookContext.hookData.set('testId', 'test-123');
hookContext.hookData.set('startTime', Date.now());
hookContext.hookData.set('metadata', { version: '1.0.0' });
}
after(hookContext: HookContext<FlagValue, TestHookData>) {
// Type-safe getting with proper return types
const testId: string | undefined = hookContext.hookData.get('testId');
const startTime: number | undefined = hookContext.hookData.get('startTime');
if (testId && startTime) {
this.capturedData = {
testId,
duration: Date.now() - startTime,
};
}
}
}
const hook = new TestTypedHook();
// Create mock contexts that satisfy the BaseHook interface
const mockBeforeContext: BeforeHookContext<FlagValue, TestHookData> = {
flagKey: 'test-flag',
defaultValue: true,
flagValueType: 'boolean',
context: {},
clientMetadata: {
name: 'test-client',
domain: 'test-domain',
providerMetadata: { name: 'test-provider' },
},
providerMetadata: { name: 'test-provider' },
logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
hookData: new MapHookData<TestHookData>(),
};
const mockAfterContext: HookContext<FlagValue, TestHookData> = {
...mockBeforeContext,
context: Object.freeze({}),
};
// Execute the hook methods
hook.before!(mockBeforeContext);
hook.after!(mockAfterContext);
// Verify the typed hook worked correctly
expect(hook.capturedData.testId).toBe('test-123');
expect(hook.capturedData.duration).toBeDefined();
expect(typeof hook.capturedData.duration).toBe('number');
});
});

View File

@ -1,7 +1,8 @@
import { createEvaluationEvent } from '../src/telemetry/evaluation-event';
import { ErrorCode, StandardResolutionReasons, type EvaluationDetails } from '../src/evaluation/evaluation';
import type { HookContext } from '../src/hooks/hooks';
import { TelemetryAttribute, TelemetryFlagMetadata, TelemetryEvaluationData } from '../src/telemetry';
import { TelemetryAttribute, TelemetryFlagMetadata } from '../src/telemetry';
import { MapHookData } from '../src/hooks/hook-data';
describe('evaluationEvent', () => {
const flagKey = 'test-flag';
@ -25,6 +26,7 @@ describe('evaluationEvent', () => {
error: jest.fn(),
warn: jest.fn(),
},
hookData: new MapHookData(),
};
it('should return basic event body with mandatory fields', () => {
@ -43,9 +45,7 @@ describe('evaluationEvent', () => {
[TelemetryAttribute.PROVIDER]: 'test-provider',
[TelemetryAttribute.REASON]: StandardResolutionReasons.STATIC.toLowerCase(),
[TelemetryAttribute.CONTEXT_ID]: 'test-target',
});
expect(result.data).toEqual({
[TelemetryEvaluationData.VALUE]: true,
[TelemetryAttribute.VALUE]: true,
});
});
@ -61,7 +61,7 @@ describe('evaluationEvent', () => {
const result = createEvaluationEvent(mockHookContext, details);
expect(result.attributes[TelemetryAttribute.VARIANT]).toBe('test-variant');
expect(result.attributes[TelemetryEvaluationData.VALUE]).toBeUndefined();
expect(result.attributes[TelemetryAttribute.VALUE]).toBeUndefined();
});
it('should include flag metadata when provided', () => {

View File

@ -1,6 +1,51 @@
# Changelog
## [1.6.1](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.6.0...web-sdk-v1.6.1) (2025-08-14)
### 🐛 Bug Fixes
* update core dep ([#1228](https://github.com/open-feature/js-sdk/issues/1228)) ([845d24c](https://github.com/open-feature/js-sdk/commit/845d24c5fecc80de3080e49fde839f08ecac6b33))
## [1.6.0](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.5.0...web-sdk-v1.6.0) (2025-08-12)
### ✨ New Features
* add evaluation-scoped hook data ([#1216](https://github.com/open-feature/js-sdk/issues/1216)) ([07af3a9](https://github.com/open-feature/js-sdk/commit/07af3a9eda895e9edb24c7ee1e3c1c4f16e17431))
* **web-global-build:** impl ([#1225](https://github.com/open-feature/js-sdk/issues/1225)) ([40a512e](https://github.com/open-feature/js-sdk/commit/40a512e21204eb92dc3ef4161b383f9c1fd74da7))
### 📚 Documentation
* Clarify the behavior of setProviderAndWait ([#1180](https://github.com/open-feature/js-sdk/issues/1180)) ([4fe8d87](https://github.com/open-feature/js-sdk/commit/4fe8d87a2e5df2cbd4086cc4f4a380e8857ed8ba))
## [1.5.0](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.4.1...web-sdk-v1.5.0) (2025-04-11)
### ✨ New Features
* add a top-level method for accessing providers ([#1152](https://github.com/open-feature/js-sdk/issues/1152)) ([ae8fce8](https://github.com/open-feature/js-sdk/commit/ae8fce87530005ed20f7e68dc696ce67053fca31))
* add support for abort controllers to event handlers ([#1151](https://github.com/open-feature/js-sdk/issues/1151)) ([6a22483](https://github.com/open-feature/js-sdk/commit/6a224830fa4e62fc30a7802536f6f6fc3f772038))
### 🐛 Bug Fixes
* Typo in name of the function ([2c5b37c](https://github.com/open-feature/js-sdk/commit/2c5b37c79d72d60864c27b9e67d96e99ef4ae241))
## [1.4.1](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.4.0...web-sdk-v1.4.1) (2025-02-07)
### 🐛 Bug Fixes
* msg missing when providers return err resolutions ([#1134](https://github.com/open-feature/js-sdk/issues/1134)) ([bc9f6e4](https://github.com/open-feature/js-sdk/commit/bc9f6e44da3f1c0a66659aee2d0316629ac34fbf))
### 🧹 Chore
* update core peer ([8bbd43e](https://github.com/open-feature/js-sdk/commit/8bbd43e579a0c2e0c5b7eec00f94bbcffce04773))
## [1.4.0](https://github.com/open-feature/js-sdk/compare/web-sdk-v1.3.2...web-sdk-v1.4.0) (2024-12-18)
### ⚠ BREAKING CHANGES

View File

@ -16,8 +16,8 @@
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.8.0&color=yellow&style=for-the-badge" />
</a>
<!-- x-release-please-start-version -->
<a href="https://github.com/open-feature/js-sdk/releases/tag/web-sdk-v1.4.0">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.4.0&color=blue&style=for-the-badge" />
<a href="https://github.com/open-feature/js-sdk/releases/tag/web-sdk-v1.6.1">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.6.1&color=blue&style=for-the-badge" />
</a>
<!-- x-release-please-end -->
<br/>
@ -75,7 +75,11 @@ yarn add @openfeature/web-sdk @openfeature/core
import { OpenFeature } from '@openfeature/web-sdk';
// Register your feature flag provider
await OpenFeature.setProviderAndWait(new YourProviderOfChoice());
try {
await OpenFeature.setProviderAndWait(new YourProviderOfChoice());
} catch (error) {
console.error('Failed to initialize provider:', error);
}
// create a new client
const client = OpenFeature.getClient();
@ -121,7 +125,11 @@ Once you've added a provider as a dependency, it can be registered with OpenFeat
To register a provider and ensure it is ready before further actions are taken, you can use the `setProviderAndWait` method as shown below:
```ts
await OpenFeature.setProviderAndWait(new MyProvider());
try {
await OpenFeature.setProviderAndWait(new MyProvider());
} catch (error) {
console.error('Failed to initialize provider:', error);
}
```
#### Synchronous
@ -172,7 +180,7 @@ await OpenFeature.setContext({ targetingKey: localStorage.getItem("targetingKey"
```
Context is global and setting it is `async`.
Providers may implement an `onContextChanged` method that receives the old and newer contexts.
Providers may implement an `onContextChange` method that receives the old and newer contexts.
Given a context change, providers can use this method internally to detect if the flag values cached on the client are still valid.
If needed, a request will be made to the provider with the new context in order to get the correct flag values.

View File

@ -1,8 +1,10 @@
{
"name": "@openfeature/web-sdk",
"version": "1.4.0",
"version": "1.6.1",
"description": "OpenFeature SDK for Web",
"main": "./dist/cjs/index.js",
"unpkg": "dist/global/index.min.js",
"jsdelivr": "dist/global/index.min.js",
"files": [
"dist/"
],
@ -20,8 +22,10 @@
"clean": "shx rm -rf ./dist",
"build:web-esm": "esbuild src/index.ts --bundle --external:@openfeature/core --sourcemap --target=es2015 --platform=browser --format=esm --outfile=./dist/esm/index.js --analyze",
"build:web-cjs": "esbuild src/index.ts --bundle --external:@openfeature/core --sourcemap --target=es2015 --platform=browser --format=cjs --outfile=./dist/cjs/index.js --analyze",
"build:web-global": "esbuild src/index.ts --bundle --sourcemap --target=es2015 --platform=browser --format=iife --outfile=./dist/global/index.js --global-name=OpenFeature --analyze",
"build:web-global:min": "esbuild src/index.ts --bundle --sourcemap --target=es2015 --platform=browser --format=iife --outfile=./dist/global/index.min.js --global-name=OpenFeature --minify --analyze",
"build:rollup-types": "rollup -c ../../rollup.config.mjs",
"build": "npm run clean && npm run build:web-esm && npm run build:web-cjs && npm run build:rollup-types",
"build": "npm run clean && npm run build:web-esm && npm run build:web-cjs && npm run build:web-global && npm run build:web-global:min && npm run build:rollup-types",
"postbuild": "shx cp ./../../package.esm.json ./dist/esm/package.json",
"current-version": "echo $npm_package_version",
"prepack": "shx cp ./../../LICENSE ./LICENSE",
@ -46,9 +50,9 @@
},
"homepage": "https://github.com/open-feature/js-sdk#readme",
"peerDependencies": {
"@openfeature/core": "^1.7.0"
"@openfeature/core": "^1.9.0"
},
"devDependencies": {
"@openfeature/core": "^1.7.0"
"@openfeature/core": "^1.9.0"
}
}

View File

@ -12,6 +12,7 @@ import type {
OpenFeatureError,
FlagMetadata,
ResolutionDetails,
EventOptions,
} from '@openfeature/core';
import {
ErrorCode,
@ -21,6 +22,7 @@ import {
StandardResolutionReasons,
instantiateErrorByErrorCode,
statusMatchesEvent,
MapHookData,
} from '@openfeature/core';
import type { FlagEvaluationOptions } from '../../evaluation';
import type { ProviderEvents } from '../../events';
@ -74,7 +76,7 @@ export class OpenFeatureClient implements Client {
return this.providerStatusAccessor();
}
addHandler(eventType: ProviderEvents, handler: EventHandler): void {
addHandler(eventType: ProviderEvents, handler: EventHandler, options: EventOptions): void {
this.emitterAccessor().addHandler(eventType, handler);
const shouldRunNow = statusMatchesEvent(eventType, this.providerStatus);
@ -90,6 +92,12 @@ export class OpenFeatureClient implements Client {
this._logger?.error('Error running event handler:', err);
}
}
if (options?.signal && typeof options.signal.addEventListener === 'function') {
options.signal.addEventListener('abort', () => {
this.removeHandler(eventType, handler);
});
}
}
removeHandler(notificationType: ProviderEvents, handler: EventHandler): void {
@ -224,22 +232,26 @@ export class OpenFeatureClient implements Client {
...this.apiContextAccessor(this?.options?.domain),
};
// this reference cannot change during the course of evaluation
// it may be used as a key in WeakMaps
const hookContext: Readonly<HookContext> = {
flagKey,
defaultValue,
flagValueType: flagType,
clientMetadata: this.metadata,
providerMetadata: this._provider.metadata,
context,
logger: this._logger,
};
// Create hook context instances for each hook (stable object references for the entire evaluation)
// This ensures hooks can use WeakMaps with hookContext as keys across lifecycle methods
// NOTE: Uses the reversed order to reduce the number of times we have to calculate the index.
const hookContexts = allHooksReversed.map<HookContext>(() =>
Object.freeze({
flagKey,
defaultValue,
flagValueType: flagType,
clientMetadata: this.metadata,
providerMetadata: this._provider.metadata,
context,
logger: this._logger,
hookData: new MapHookData(),
}),
);
let evaluationDetails: EvaluationDetails<T>;
try {
this.beforeHooks(allHooks, hookContext, options);
this.beforeHooks(allHooks, hookContexts, options);
this.shortCircuitIfNotReady();
@ -254,45 +266,48 @@ export class OpenFeatureClient implements Client {
if (resolutionDetails.errorCode) {
const err = instantiateErrorByErrorCode(resolutionDetails.errorCode, resolutionDetails.errorMessage);
this.errorHooks(allHooksReversed, hookContext, err, options);
this.errorHooks(allHooksReversed, hookContexts, err, options);
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, err, resolutionDetails.flagMetadata);
} else {
this.afterHooks(allHooksReversed, hookContext, resolutionDetails, options);
this.afterHooks(allHooksReversed, hookContexts, resolutionDetails, options);
evaluationDetails = resolutionDetails;
}
} catch (err: unknown) {
this.errorHooks(allHooksReversed, hookContext, err, options);
this.errorHooks(allHooksReversed, hookContexts, err, options);
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, err);
}
this.finallyHooks(allHooksReversed, hookContext, evaluationDetails, options);
this.finallyHooks(allHooksReversed, hookContexts, evaluationDetails, options);
return evaluationDetails;
}
private beforeHooks(hooks: Hook[], hookContext: HookContext, options: FlagEvaluationOptions) {
Object.freeze(hookContext);
Object.freeze(hookContext.context);
for (const hook of hooks) {
private beforeHooks(hooks: Hook[], hookContexts: HookContext[], options: FlagEvaluationOptions) {
for (const [index, hook] of hooks.entries()) {
const hookContextIndex = hooks.length - 1 - index; // reverse index for before hooks
const hookContext = hookContexts[hookContextIndex];
Object.freeze(hookContext);
Object.freeze(hookContext.context);
hook?.before?.(hookContext, Object.freeze(options.hookHints));
}
}
private afterHooks(
hooks: Hook[],
hookContext: HookContext,
hookContexts: HookContext[],
evaluationDetails: EvaluationDetails<FlagValue>,
options: FlagEvaluationOptions,
) {
// run "after" hooks sequentially
for (const hook of hooks) {
for (const [index, hook] of hooks.entries()) {
const hookContext = hookContexts[index];
hook?.after?.(hookContext, evaluationDetails, options.hookHints);
}
}
private errorHooks(hooks: Hook[], hookContext: HookContext, err: unknown, options: FlagEvaluationOptions) {
private errorHooks(hooks: Hook[], hookContexts: HookContext[], err: unknown, options: FlagEvaluationOptions) {
// run "error" hooks sequentially
for (const hook of hooks) {
for (const [index, hook] of hooks.entries()) {
try {
const hookContext = hookContexts[index];
hook?.error?.(hookContext, err, options.hookHints);
} catch (err) {
this._logger.error(`Unhandled error during 'error' hook: ${err}`);
@ -306,13 +321,14 @@ export class OpenFeatureClient implements Client {
private finallyHooks(
hooks: Hook[],
hookContext: HookContext,
hookContexts: HookContext[],
evaluationDetails: EvaluationDetails<FlagValue>,
options: FlagEvaluationOptions,
) {
// run "finally" hooks sequentially
for (const hook of hooks) {
for (const [index, hook] of hooks.entries()) {
try {
const hookContext = hookContexts[index];
hook?.finally?.(hookContext, evaluationDetails, options.hookHints);
} catch (err) {
this._logger.error(`Unhandled error during 'finally' hook: ${err}`);

View File

@ -1,3 +1,3 @@
import type { BaseHook, FlagValue } from '@openfeature/core';
export type Hook = BaseHook<FlagValue, void, void>;
export type Hook<TData = Record<string, unknown>> = BaseHook<FlagValue, TData, void, void>;

View File

@ -77,7 +77,7 @@ export class OpenFeatureAPI
* Setting a provider supersedes the current provider used in new and existing unbound clients.
* @param {Provider} provider The provider responsible for flag evaluations.
* @returns {Promise<void>}
* @throws Uncaught exceptions thrown by the provider during initialization.
* @throws {Error} If the provider throws an exception during initialization.
*/
setProviderAndWait(provider: Provider): Promise<void>;
/**
@ -87,7 +87,7 @@ export class OpenFeatureAPI
* @param {Provider} provider The provider responsible for flag evaluations.
* @param {EvaluationContext} context The evaluation context to use for flag evaluations.
* @returns {Promise<void>}
* @throws Uncaught exceptions thrown by the provider during initialization.
* @throws {Error} If the provider throws an exception during initialization.
*/
setProviderAndWait(provider: Provider, context: EvaluationContext): Promise<void>;
/**
@ -97,7 +97,7 @@ export class OpenFeatureAPI
* @param {string} domain The name to identify the client
* @param {Provider} provider The provider responsible for flag evaluations.
* @returns {Promise<void>}
* @throws Uncaught exceptions thrown by the provider during initialization.
* @throws {Error} If the provider throws an exception during initialization.
*/
setProviderAndWait(domain: string, provider: Provider): Promise<void>;
/**
@ -108,7 +108,7 @@ export class OpenFeatureAPI
* @param {Provider} provider The provider responsible for flag evaluations.
* @param {EvaluationContext} context The evaluation context to use for flag evaluations.
* @returns {Promise<void>}
* @throws Uncaught exceptions thrown by the provider during initialization.
* @throws {Error} If the provider throws an exception during initialization.
*/
setProviderAndWait(domain: string, provider: Provider, context: EvaluationContext): Promise<void>;
async setProviderAndWait(
@ -205,6 +205,27 @@ export class OpenFeatureAPI
return this;
}
/**
* Get the default provider.
*
* Note that it isn't recommended to interact with the provider directly, but rather through
* an OpenFeature client.
* @returns {Provider} Default Provider
*/
getProvider(): Provider;
/**
* Get the provider bound to the specified domain.
*
* Note that it isn't recommended to interact with the provider directly, but rather through
* an OpenFeature client.
* @param {string} domain An identifier which logically binds clients with providers
* @returns {Provider} Domain-scoped provider
*/
getProvider(domain?: string): Provider;
getProvider(domain?: string): Provider {
return this.getProviderForClient(domain);
}
/**
* Sets the evaluation context globally.
* This will be used by all providers that have not bound to a domain.
@ -325,9 +346,9 @@ export class OpenFeatureAPI
}
/**
* A factory function for creating new named OpenFeature clients. Clients can contain
* their own state (e.g. logger, hook, context). Multiple clients can be used
* to segment feature flag configuration.
* A factory function for creating new domain-scoped OpenFeature clients. Clients
* can contain their own state (e.g. logger, hook, context). Multiple domains
* can be used to segment feature flag configuration.
*
* If there is already a provider bound to this name via {@link this.setProvider setProvider}, this provider will be used.
* Otherwise, the default provider is used until a provider is assigned to that name.

View File

@ -476,7 +476,21 @@ describe('Events', () => {
expect(OpenFeature.getHandlers(eventType)).toHaveLength(0);
});
it('The API provides a function allowing the removal of event handlers', () => {
it('The event handler can be removed using an abort signal', () => {
const abortController = new AbortController();
const handler1 = jest.fn();
const handler2 = jest.fn();
const eventType = ProviderEvents.Stale;
OpenFeature.addHandler(eventType, handler1, { signal: abortController.signal });
OpenFeature.addHandler(eventType, handler2);
expect(OpenFeature.getHandlers(eventType)).toHaveLength(2);
abortController.abort();
expect(OpenFeature.getHandlers(eventType)).toHaveLength(1);
});
it('The API provides a function allowing the removal of event handlers from client', () => {
const client = OpenFeature.getClient(domain);
const handler = jest.fn();
const eventType = ProviderEvents.Stale;
@ -486,6 +500,21 @@ describe('Events', () => {
client.removeHandler(eventType, handler);
expect(client.getHandlers(eventType)).toHaveLength(0);
});
it('The event handler on the client can be removed using an abort signal', () => {
const abortController = new AbortController();
const client = OpenFeature.getClient(domain);
const handler1 = jest.fn();
const handler2 = jest.fn();
const eventType = ProviderEvents.Stale;
client.addHandler(eventType, handler1, { signal: abortController.signal });
client.addHandler(eventType, handler2);
expect(client.getHandlers(eventType)).toHaveLength(2);
abortController.abort();
expect(client.getHandlers(eventType)).toHaveLength(1);
});
});
describe('Requirement 5.3.1', () => {

View File

@ -0,0 +1,436 @@
import { OpenFeatureAPI } from '../src/open-feature';
import type { Client } from '../src/client';
import type { JsonValue, ResolutionDetails, HookContext, BeforeHookContext } from '@openfeature/core';
import { StandardResolutionReasons } from '@openfeature/core';
import type { Provider } from '../src/provider';
import type { Hook } from '../src/hooks';
const BOOLEAN_VALUE = true;
const STRING_VALUE = 'val';
const NUMBER_VALUE = 1;
const OBJECT_VALUE = { key: 'value' };
// A test hook that stores data in the before stage and retrieves it in after/error/finally
class TestHookWithData implements Hook {
beforeData: unknown;
afterData: unknown;
errorData: unknown;
finallyData: unknown;
before(hookContext: BeforeHookContext) {
// Store some data
hookContext.hookData.set('testKey', 'testValue');
hookContext.hookData.set('timestamp', Date.now());
hookContext.hookData.set('object', { nested: 'value' });
this.beforeData = hookContext.hookData.get('testKey');
}
after(hookContext: HookContext) {
// Retrieve data stored in before
this.afterData = hookContext.hookData.get('testKey');
}
error(hookContext: HookContext) {
// Retrieve data stored in before
this.errorData = hookContext.hookData.get('testKey');
}
finally(hookContext: HookContext) {
// Retrieve data stored in before
this.finallyData = hookContext.hookData.get('testKey');
}
}
// A timing hook that measures evaluation duration
class TimingHook implements Hook {
duration?: number;
before(hookContext: BeforeHookContext) {
hookContext.hookData.set('startTime', performance.now());
}
after(hookContext: HookContext) {
const startTime = hookContext.hookData.get('startTime') as number;
if (startTime) {
this.duration = performance.now() - startTime;
}
}
error(hookContext: HookContext) {
const startTime = hookContext.hookData.get('startTime') as number;
if (startTime) {
this.duration = performance.now() - startTime;
}
}
}
// Hook that tests hook data isolation
class IsolationTestHook implements Hook {
hookId: string;
constructor(id: string) {
this.hookId = id;
}
before(hookContext: BeforeHookContext) {
const storedId = hookContext.hookData.get('hookId');
if (storedId) {
throw new Error('Hook data isolation violated! Data is set in before hook.');
}
// Each hook instance should have its own data
hookContext.hookData.set('hookId', this.hookId);
hookContext.hookData.set(`data_${this.hookId}`, `value_${this.hookId}`);
}
after(hookContext: HookContext) {
// Verify we can only see our own data
const storedId = hookContext.hookData.get('hookId');
if (storedId !== this.hookId) {
throw new Error(`Hook data isolation violated! Expected ${this.hookId}, got ${storedId}`);
}
}
}
// Mock provider for testing
const MOCK_PROVIDER: Provider = {
metadata: { name: 'mock-provider' },
resolveBooleanEvaluation(): ResolutionDetails<boolean> {
return {
value: BOOLEAN_VALUE,
variant: 'default',
reason: StandardResolutionReasons.DEFAULT,
};
},
resolveStringEvaluation(): ResolutionDetails<string> {
return {
value: STRING_VALUE,
variant: 'default',
reason: StandardResolutionReasons.DEFAULT,
};
},
resolveNumberEvaluation(): ResolutionDetails<number> {
return {
value: NUMBER_VALUE,
variant: 'default',
reason: StandardResolutionReasons.DEFAULT,
};
},
resolveObjectEvaluation<T extends JsonValue>(): ResolutionDetails<T> {
return {
value: OBJECT_VALUE as unknown as T,
variant: 'default',
reason: StandardResolutionReasons.DEFAULT,
};
},
} as Provider;
// Mock provider that throws an error
const ERROR_PROVIDER: Provider = {
metadata: { name: 'error-provider' },
resolveBooleanEvaluation(): ResolutionDetails<boolean> {
throw new Error('Provider error');
},
resolveStringEvaluation(): ResolutionDetails<string> {
throw new Error('Provider error');
},
resolveNumberEvaluation(): ResolutionDetails<number> {
throw new Error('Provider error');
},
resolveObjectEvaluation<T extends JsonValue>(): ResolutionDetails<T> {
throw new Error('Provider error');
},
};
describe('Hook Data (Web SDK)', () => {
let client: Client;
let api: OpenFeatureAPI;
beforeEach(() => {
api = OpenFeatureAPI.getInstance();
api.clearHooks();
api.setProvider(MOCK_PROVIDER);
client = api.getClient();
});
afterEach(() => {
api.clearProviders();
});
describe('Basic Hook Data Functionality', () => {
it('should allow hooks to store and retrieve data across stages', () => {
const hook = new TestHookWithData();
client.addHooks(hook);
client.getBooleanValue('test-flag', false);
// Verify data was stored in before and retrieved in all other stages
expect(hook.beforeData).toBe('testValue');
expect(hook.afterData).toBe('testValue');
expect(hook.finallyData).toBe('testValue');
});
it('should support storing different data types', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const storedValues: any = {};
const hook: Hook = {
before(hookContext: BeforeHookContext) {
// Store various types
hookContext.hookData.set('string', 'test');
hookContext.hookData.set('number', 42);
hookContext.hookData.set('boolean', true);
hookContext.hookData.set('object', { key: 'value' });
hookContext.hookData.set('array', [1, 2, 3]);
hookContext.hookData.set('null', null);
hookContext.hookData.set('undefined', undefined);
},
after(hookContext: HookContext) {
storedValues.string = hookContext.hookData.get('string');
storedValues.number = hookContext.hookData.get('number');
storedValues.boolean = hookContext.hookData.get('boolean');
storedValues.object = hookContext.hookData.get('object');
storedValues.array = hookContext.hookData.get('array');
storedValues.null = hookContext.hookData.get('null');
storedValues.undefined = hookContext.hookData.get('undefined');
},
};
client.addHooks(hook);
client.getBooleanValue('test-flag', false);
expect(storedValues.string).toBe('test');
expect(storedValues.number).toBe(42);
expect(storedValues.boolean).toBe(true);
expect(storedValues.object).toEqual({ key: 'value' });
expect(storedValues.array).toEqual([1, 2, 3]);
expect(storedValues.null).toBeNull();
expect(storedValues.undefined).toBeUndefined();
});
it('should handle hook data in error scenarios', () => {
api.setProvider(ERROR_PROVIDER);
const hook = new TestHookWithData();
client.addHooks(hook);
client.getBooleanValue('test-flag', false);
// Verify data was accessible in error and finally stages
expect(hook.beforeData).toBe('testValue');
expect(hook.errorData).toBe('testValue');
expect(hook.finallyData).toBe('testValue');
expect(hook.afterData).toBeUndefined(); // after should not run on error
});
});
describe('Hook Data API', () => {
it('should support has() method', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hasResults: any = {};
const hook: Hook = {
before(hookContext: BeforeHookContext) {
hookContext.hookData.set('exists', 'value');
hasResults.beforeExists = hookContext.hookData.has('exists');
hasResults.beforeNotExists = hookContext.hookData.has('notExists');
},
after(hookContext: HookContext) {
hasResults.afterExists = hookContext.hookData.has('exists');
hasResults.afterNotExists = hookContext.hookData.has('notExists');
},
};
client.addHooks(hook);
client.getBooleanValue('test-flag', false);
expect(hasResults.beforeExists).toBe(true);
expect(hasResults.beforeNotExists).toBe(false);
expect(hasResults.afterExists).toBe(true);
expect(hasResults.afterNotExists).toBe(false);
});
it('should support delete() method', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deleteResults: any = {};
const hook: Hook = {
before(hookContext: BeforeHookContext) {
hookContext.hookData.set('toDelete', 'value');
deleteResults.hasBeforeDelete = hookContext.hookData.has('toDelete');
deleteResults.deleteResult = hookContext.hookData.delete('toDelete');
deleteResults.hasAfterDelete = hookContext.hookData.has('toDelete');
deleteResults.deleteAgainResult = hookContext.hookData.delete('toDelete');
},
};
client.addHooks(hook);
client.getBooleanValue('test-flag', false);
expect(deleteResults.hasBeforeDelete).toBe(true);
expect(deleteResults.deleteResult).toBe(true);
expect(deleteResults.hasAfterDelete).toBe(false);
expect(deleteResults.deleteAgainResult).toBe(false);
});
it('should support clear() method', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const clearResults: any = {};
const hook: Hook = {
before(hookContext: BeforeHookContext) {
hookContext.hookData.set('key1', 'value1');
hookContext.hookData.set('key2', 'value2');
hookContext.hookData.set('key3', 'value3');
clearResults.hasBeforeClear = hookContext.hookData.has('key1');
hookContext.hookData.clear();
clearResults.hasAfterClear = hookContext.hookData.has('key1');
},
after(hookContext: HookContext) {
// Verify all data was cleared
clearResults.afterHasKey1 = hookContext.hookData.has('key1');
clearResults.afterHasKey2 = hookContext.hookData.has('key2');
clearResults.afterHasKey3 = hookContext.hookData.has('key3');
},
};
client.addHooks(hook);
client.getBooleanValue('test-flag', false);
expect(clearResults.hasBeforeClear).toBe(true);
expect(clearResults.hasAfterClear).toBe(false);
expect(clearResults.afterHasKey1).toBe(false);
expect(clearResults.afterHasKey2).toBe(false);
expect(clearResults.afterHasKey3).toBe(false);
});
});
describe('Hook Data Isolation', () => {
it('should isolate data between different hook instances', () => {
const hook1 = new IsolationTestHook('hook1');
const hook2 = new IsolationTestHook('hook2');
const hook3 = new IsolationTestHook('hook3');
client.addHooks(hook1, hook2, hook3);
expect(client.getBooleanValue('test-flag', false)).toBe(true);
});
it('should isolate data between the same hook instance', () => {
const hook = new IsolationTestHook('hook');
client.addHooks(hook, hook);
expect(client.getBooleanValue('test-flag', false)).toBe(true);
});
it('should not share data between different evaluations', () => {
let firstEvalData: unknown;
let secondEvalData: unknown;
const hook: Hook = {
before(hookContext: BeforeHookContext) {
// Check if data exists from previous evaluation
const existingData = hookContext.hookData.get('evalData');
if (existingData) {
throw new Error('Hook data leaked between evaluations!');
}
hookContext.hookData.set('evalData', 'evaluation-specific');
},
after(hookContext: HookContext) {
if (!firstEvalData) {
firstEvalData = hookContext.hookData.get('evalData');
} else {
secondEvalData = hookContext.hookData.get('evalData');
}
},
};
client.addHooks(hook);
// First evaluation
client.getBooleanValue('test-flag', false);
// Second evaluation
client.getBooleanValue('test-flag', false);
expect(firstEvalData).toBe('evaluation-specific');
expect(secondEvalData).toBe('evaluation-specific');
});
it('should isolate data between global, client, and invocation hooks', () => {
const globalHook = new IsolationTestHook('global');
const clientHook = new IsolationTestHook('client');
const invocationHook = new IsolationTestHook('invocation');
api.addHooks(globalHook);
client.addHooks(clientHook);
expect(client.getBooleanValue('test-flag', false, { hooks: [invocationHook] })).toBe(true);
});
});
describe('Use Cases', () => {
it('should support timing measurements', () => {
const timingHook = new TimingHook();
client.addHooks(timingHook);
client.getBooleanValue('test-flag', false);
expect(timingHook.duration).toBeDefined();
expect(timingHook.duration).toBeGreaterThanOrEqual(0);
});
it('should support multi-stage validation accumulation', () => {
let finalErrors: string[] = [];
const validationHook: Hook = {
before(hookContext: BeforeHookContext) {
hookContext.hookData.set('errors', []);
// Simulate validation
const errors = hookContext.hookData.get('errors') as string[];
if (!hookContext.context.userId) {
errors.push('Missing userId');
}
if (!hookContext.context.region) {
errors.push('Missing region');
}
},
finally(hookContext: HookContext) {
finalErrors = (hookContext.hookData.get('errors') as string[]) || [];
},
};
client.addHooks(validationHook);
client.getBooleanValue('test-flag', false, {});
expect(finalErrors).toContain('Missing userId');
expect(finalErrors).toContain('Missing region');
});
it('should support request correlation', () => {
let correlationId: string | undefined;
const correlationHook: Hook = {
before(hookContext: BeforeHookContext) {
const id = `req-${Date.now()}-${Math.random()}`;
hookContext.hookData.set('correlationId', id);
},
after(hookContext: HookContext) {
correlationId = hookContext.hookData.get('correlationId') as string;
},
};
client.addHooks(correlationHook);
client.getBooleanValue('test-flag', false);
expect(correlationId).toBeDefined();
expect(correlationId).toMatch(/^req-\d+-[\d.]+$/);
});
});
});

View File

@ -75,8 +75,8 @@ describe('OpenFeature', () => {
it('should set the default provider if no domain is provided', () => {
const provider = mockProvider();
OpenFeature.setProvider(provider);
const client = OpenFeature.getClient();
expect(client.metadata.providerMetadata.name).toEqual(provider.metadata.name);
const registeredProvider = OpenFeature.getProvider();
expect(registeredProvider).toEqual(provider);
});
it('should not change providers associated with a domain when setting a new default provider', () => {
@ -86,11 +86,11 @@ describe('OpenFeature', () => {
OpenFeature.setProvider(provider);
OpenFeature.setProvider(domain, fakeProvider);
const defaultClient = OpenFeature.getClient();
const domainSpecificClient = OpenFeature.getClient(domain);
const defaultProvider = OpenFeature.getProvider();
const domainSpecificProvider = OpenFeature.getProvider(domain);
expect(defaultClient.metadata.providerMetadata.name).toEqual(provider.metadata.name);
expect(domainSpecificClient.metadata.providerMetadata.name).toEqual(fakeProvider.metadata.name);
expect(defaultProvider).toEqual(provider);
expect(domainSpecificProvider).toEqual(fakeProvider);
});
it('should bind a new provider to existing clients in a matching domain', () => {

View File

@ -29,7 +29,7 @@
},
"packages/angular/projects/angular-sdk": {
"release-type": "node",
"prerelease": true,
"prerelease": false,
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"extra-files": ["README.md"],