diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 846396a..aa1460c 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -24,6 +24,10 @@ This log covers the [monorepo](https://en.wikipedia.org/wiki/Monorepo).
- added a `prepare-publish.mjs` script to prepare packages for npm publishing (moving `README.md` and `LICENSE` files into package roots), replacing the prior `shx` based solution, and adding [`transform-markdown-links`](https://github.com/gakimball/transform-markdown-links) to fix relative paths that otherwise incorrectly link on [npmjs.com](https://www.npmjs.com/)
+### Changed
+
+- updated [`danger-js`](https://github.com/danger/danger-js) to [version 13.0.3](https://github.com/danger/danger-js/blob/main/CHANGELOG.md#1303), to remove high vulnerabilities
+
## [0.11.0] - 2025-09-29
### Changed
diff --git a/examples/express/docs/CHANGELOG.md b/examples/express/docs/CHANGELOG.md
index be5cc5a..606436e 100644
--- a/examples/express/docs/CHANGELOG.md
+++ b/examples/express/docs/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.3.0] - 2025-10-20
+
+### Changed
+
+- updated to features [version 0.5.0](../../../packages/features/docs/CHANGELOG.md#050---2025-10-20)
+
## [0.2.7] - 2025-09-30
### Changed
@@ -25,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
-- updated to use `variantGlobs` array, with updated webpack plugin [0.8.0][version 0.8.0](../../../packages/webpack/docs/CHANGELOG.md#080---2025-05-27)
+- updated to use `variantGlobs` array, with updated webpack plugin [version 0.8.0](../../../packages/webpack/docs/CHANGELOG.md#080---2025-05-27)
- used some differing syntax from [`micromatch`](https://github.com/micromatch/micromatch) to define `variantGlobs`, for coverage and where may be preferred
## [0.2.4] - 2024-02-07
diff --git a/examples/express/package.json b/examples/express/package.json
index ecc5be4..8ae5dc2 100644
--- a/examples/express/package.json
+++ b/examples/express/package.json
@@ -1,6 +1,6 @@
{
"name": "web-toggle-point-express-example",
- "version": "0.2.7",
+ "version": "0.3.0",
"type": "module",
"engines": {
"node": ">=20.6.0"
diff --git a/examples/express/src/routes/animals/featuresStore.js b/examples/express/src/routes/animals/featuresStore.js
index a108fc6..c6003a8 100644
--- a/examples/express/src/routes/animals/featuresStore.js
+++ b/examples/express/src/routes/animals/featuresStore.js
@@ -1,6 +1,6 @@
// eslint-disable-next-line import/no-unresolved -- https://github.com/import-js/eslint-plugin-import/issues/1810
import featuresStoreFactory from "@asos/web-toggle-point-features/storeFactories/nodeRequestScopedFeaturesStoreFactory";
-const featuresStore = featuresStoreFactory();
+const featuresStore = featuresStoreFactory({ toggleType: "api version" });
export default featuresStore;
diff --git a/examples/express/src/routes/config/featuresStore.js b/examples/express/src/routes/config/featuresStore.js
index 0a36142..be2693d 100644
--- a/examples/express/src/routes/config/featuresStore.js
+++ b/examples/express/src/routes/config/featuresStore.js
@@ -2,7 +2,7 @@
import featuresStoreFactory from "@asos/web-toggle-point-features/storeFactories/ssrBackedReactContextFeaturesStoreFactory";
const featuresStore = featuresStoreFactory({
- name: "config",
+ toggleType: "config",
logWarning: console.log
});
diff --git a/examples/next/docs/CHANGELOG.md b/examples/next/docs/CHANGELOG.md
index d96eb5c..2c7f1d0 100644
--- a/examples/next/docs/CHANGELOG.md
+++ b/examples/next/docs/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.4.0] - 2025-10-20
+
+### Changed
+
+- updated to features [version 0.5.0](../../../packages/features/docs/CHANGELOG.md#050---2025-10-20)
+
## [0.3.1] - 2025-09-30
### Changed
diff --git a/examples/next/package.json b/examples/next/package.json
index 1e1b342..698a568 100644
--- a/examples/next/package.json
+++ b/examples/next/package.json
@@ -1,6 +1,6 @@
{
"name": "web-toggle-point-next-example",
- "version": "0.3.1",
+ "version": "0.4.0",
"private": true,
"type": "module",
"scripts": {
diff --git a/examples/next/src/app/fixtures/content-management/featuresStore.ts b/examples/next/src/app/fixtures/content-management/featuresStore.ts
index 6861ec6..2036a05 100644
--- a/examples/next/src/app/fixtures/content-management/featuresStore.ts
+++ b/examples/next/src/app/fixtures/content-management/featuresStore.ts
@@ -3,7 +3,7 @@
import featuresStoreFactory from "@asos/web-toggle-point-features/storeFactories/reactContextFeaturesStoreFactory";
const reactContextStore = featuresStoreFactory({
- name: "Content Management"
+ toggleType: "Content Management"
});
export default reactContextStore;
diff --git a/examples/next/src/app/fixtures/experiments/featuresStore.ts b/examples/next/src/app/fixtures/experiments/featuresStore.ts
index 9a58eb5..2327f75 100644
--- a/examples/next/src/app/fixtures/experiments/featuresStore.ts
+++ b/examples/next/src/app/fixtures/experiments/featuresStore.ts
@@ -3,7 +3,7 @@
import featuresStoreFactory from "@asos/web-toggle-point-features/storeFactories/reactContextFeaturesStoreFactory";
const reactContextStore = featuresStoreFactory({
- name: "Experiments"
+ toggleType: "experiments"
});
export default reactContextStore;
diff --git a/examples/serve/docs/CHANGELOG.md b/examples/serve/docs/CHANGELOG.md
index c64642e..6471442 100644
--- a/examples/serve/docs/CHANGELOG.md
+++ b/examples/serve/docs/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.3.0] - 2025-10-20
+
+### Changed
+
+- updated to features [version 0.5.0](../../../packages/features/docs/CHANGELOG.md#050---2025-10-20)
+
## [0.2.7] - 2025-07-15
### Changed
diff --git a/examples/serve/package.json b/examples/serve/package.json
index 4835f1d..c6a5b97 100644
--- a/examples/serve/package.json
+++ b/examples/serve/package.json
@@ -1,6 +1,6 @@
{
"name": "web-toggle-point-serve-example",
- "version": "0.2.7",
+ "version": "0.3.0",
"type": "module",
"private": true,
"scripts": {
diff --git a/examples/serve/src/fixtures/audience/__featuresStore.js b/examples/serve/src/fixtures/audience/__featuresStore.js
index 9c89305..64d702c 100644
--- a/examples/serve/src/fixtures/audience/__featuresStore.js
+++ b/examples/serve/src/fixtures/audience/__featuresStore.js
@@ -1,7 +1,7 @@
// eslint-disable-next-line import/no-unresolved -- https://github.com/import-js/eslint-plugin-import/issues/1810
import featuresStoreFactory from "@asos/web-toggle-point-features/storeFactories/globalFeaturesStoreFactory";
-const featuresStore = featuresStoreFactory();
+const featuresStore = featuresStoreFactory({ toggleType: "audience" });
const [, audience] = document.cookie.match(/audience=(.+?)(;|$)/) || [];
diff --git a/examples/serve/src/fixtures/config/__featuresStore.js b/examples/serve/src/fixtures/config/__featuresStore.js
index 2ec0c5b..3f7ca5b 100644
--- a/examples/serve/src/fixtures/config/__featuresStore.js
+++ b/examples/serve/src/fixtures/config/__featuresStore.js
@@ -1,7 +1,7 @@
// eslint-disable-next-line import/no-unresolved -- https://github.com/import-js/eslint-plugin-import/issues/1810
import featuresStoreFactory from "@asos/web-toggle-point-features/storeFactories/globalFeaturesStoreFactory";
-const featuresStore = featuresStoreFactory();
+const featuresStore = featuresStoreFactory({ toggleType: "config" });
featuresStore.setValue({ value: new URL(document.URL).pathname.slice(1) });
diff --git a/examples/serve/src/fixtures/event/__featuresStore.js b/examples/serve/src/fixtures/event/__featuresStore.js
index 2cf89f9..61a3783 100644
--- a/examples/serve/src/fixtures/event/__featuresStore.js
+++ b/examples/serve/src/fixtures/event/__featuresStore.js
@@ -1,7 +1,7 @@
// eslint-disable-next-line import/no-unresolved -- https://github.com/import-js/eslint-plugin-import/issues/1810
import featuresStoreFactory from "@asos/web-toggle-point-features/storeFactories/globalFeaturesStoreFactory";
-const featuresStore = featuresStoreFactory();
+const featuresStore = featuresStoreFactory({ toggleType: "events" });
const getEvent = () => {
const dateString = new Intl.DateTimeFormat("en-GB").format(new Date());
diff --git a/examples/serve/src/fixtures/translation/__featuresStore.js b/examples/serve/src/fixtures/translation/__featuresStore.js
index e58d224..612ef2c 100644
--- a/examples/serve/src/fixtures/translation/__featuresStore.js
+++ b/examples/serve/src/fixtures/translation/__featuresStore.js
@@ -1,7 +1,7 @@
// eslint-disable-next-line import/no-unresolved -- https://github.com/import-js/eslint-plugin-import/issues/1810
import featuresStoreFactory from "@asos/web-toggle-point-features/storeFactories/globalFeaturesStoreFactory";
-const featuresStore = featuresStoreFactory();
+const featuresStore = featuresStoreFactory({ toggleType: "language" });
featuresStore.setValue({
value: navigator.language || document.documentElement.lang
diff --git a/package-lock.json b/package-lock.json
index 678d3f0..c700e0b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,7 +35,7 @@
"browserslist-config-asos": "file:./peripheral/browserslist-config-asos",
"core-js": "^3.0.0",
"cross-env": "^7.0.3",
- "danger": "^12.3.4",
+ "danger": "^13.0.3",
"eslint": "^9.15.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.9.0",
@@ -57,7 +57,7 @@
},
"examples/express": {
"name": "web-toggle-point-express-example",
- "version": "0.2.7",
+ "version": "0.3.0",
"dependencies": {
"@asos/web-toggle-point-features": "file:../../packages/features",
"@asos/web-toggle-point-react-pointcuts": "file:../../packages/react-pointcuts",
@@ -89,7 +89,7 @@
},
"examples/next": {
"name": "web-toggle-point-next-example",
- "version": "0.3.1",
+ "version": "0.4.0",
"dependencies": {
"@asos/web-toggle-point-features": "file:../../packages/features",
"@asos/web-toggle-point-react-pointcuts": "file:../../packages/react-pointcuts",
@@ -117,7 +117,7 @@
},
"examples/serve": {
"name": "web-toggle-point-serve-example",
- "version": "0.2.7",
+ "version": "0.3.0",
"dependencies": {
"@asos/web-toggle-point-features": "file:../../packages/features",
"@asos/web-toggle-point-webpack": "file:../../packages/webpack",
@@ -217,7 +217,6 @@
"node_modules/@babel/core": {
"version": "7.26.0",
"license": "MIT",
- "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.0",
@@ -246,7 +245,6 @@
"node_modules/@babel/eslint-parser": {
"version": "7.25.9",
"license": "MIT",
- "peer": true,
"dependencies": {
"@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
"eslint-visitor-keys": "^2.1.0",
@@ -1871,7 +1869,6 @@
"node_modules/@eslint/js": {
"version": "9.17.0",
"license": "MIT",
- "peer": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
@@ -3400,7 +3397,6 @@
"node_modules/@mdx-js/react": {
"version": "3.1.0",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/mdx": "^2.0.0"
},
@@ -3960,124 +3956,170 @@
}
},
"node_modules/@octokit/auth-token": {
- "version": "2.5.0",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
+ "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@octokit/types": "^6.0.3"
+ "engines": {
+ "node": ">= 18"
}
},
"node_modules/@octokit/core": {
- "version": "3.6.0",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz",
+ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "@octokit/auth-token": "^2.4.4",
- "@octokit/graphql": "^4.5.8",
- "@octokit/request": "^5.6.3",
- "@octokit/request-error": "^2.0.5",
- "@octokit/types": "^6.0.3",
+ "@octokit/auth-token": "^4.0.0",
+ "@octokit/graphql": "^7.1.0",
+ "@octokit/request": "^8.4.1",
+ "@octokit/request-error": "^5.1.1",
+ "@octokit/types": "^13.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
}
},
"node_modules/@octokit/endpoint": {
- "version": "6.0.12",
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
+ "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@octokit/types": "^6.0.3",
- "is-plain-object": "^5.0.0",
+ "@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
}
},
"node_modules/@octokit/graphql": {
- "version": "4.8.0",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
+ "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@octokit/request": "^5.6.0",
- "@octokit/types": "^6.0.3",
+ "@octokit/request": "^8.4.1",
+ "@octokit/types": "^13.0.0",
"universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
}
},
"node_modules/@octokit/openapi-types": {
- "version": "12.11.0",
+ "version": "24.2.0",
+ "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
+ "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"dev": true,
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest": {
- "version": "2.21.3",
+ "version": "11.4.4-cjs.2",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz",
+ "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@octokit/types": "^6.40.0"
+ "@octokit/types": "^13.7.0"
+ },
+ "engines": {
+ "node": ">= 18"
},
"peerDependencies": {
- "@octokit/core": ">=2"
+ "@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-request-log": {
- "version": "1.0.4",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz",
+ "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==",
"dev": true,
"license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ },
"peerDependencies": {
- "@octokit/core": ">=3"
+ "@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
- "version": "5.16.2",
+ "version": "13.3.2-cjs.1",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz",
+ "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@octokit/types": "^6.39.0",
- "deprecation": "^2.3.1"
+ "@octokit/types": "^13.8.0"
+ },
+ "engines": {
+ "node": ">= 18"
},
"peerDependencies": {
- "@octokit/core": ">=3"
+ "@octokit/core": "^5"
}
},
"node_modules/@octokit/request": {
- "version": "5.6.3",
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
+ "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@octokit/endpoint": "^6.0.1",
- "@octokit/request-error": "^2.1.0",
- "@octokit/types": "^6.16.1",
- "is-plain-object": "^5.0.0",
- "node-fetch": "^2.6.7",
+ "@octokit/endpoint": "^9.0.6",
+ "@octokit/request-error": "^5.1.1",
+ "@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
}
},
"node_modules/@octokit/request-error": {
- "version": "2.1.0",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
+ "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@octokit/types": "^6.0.3",
+ "@octokit/types": "^13.1.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
+ },
+ "engines": {
+ "node": ">= 18"
}
},
"node_modules/@octokit/rest": {
- "version": "18.12.0",
+ "version": "20.1.2",
+ "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz",
+ "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@octokit/core": "^3.5.1",
- "@octokit/plugin-paginate-rest": "^2.16.8",
- "@octokit/plugin-request-log": "^1.0.4",
- "@octokit/plugin-rest-endpoint-methods": "^5.12.0"
+ "@octokit/core": "^5.0.2",
+ "@octokit/plugin-paginate-rest": "11.4.4-cjs.2",
+ "@octokit/plugin-request-log": "^4.0.0",
+ "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1"
+ },
+ "engines": {
+ "node": ">= 18"
}
},
"node_modules/@octokit/types": {
- "version": "6.41.0",
+ "version": "13.10.0",
+ "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
+ "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@octokit/openapi-types": "^12.11.0"
+ "@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@pkgjs/parseargs": {
@@ -4102,7 +4144,6 @@
"node_modules/@playwright/test": {
"version": "1.55.1",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"playwright": "1.55.1"
},
@@ -4336,6 +4377,7 @@
"node_modules/@stylistic/eslint-plugin-js": {
"version": "2.12.1",
"license": "MIT",
+ "peer": true,
"dependencies": {
"eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0"
@@ -4350,6 +4392,7 @@
"node_modules/@stylistic/eslint-plugin-js/node_modules/eslint-visitor-keys": {
"version": "4.2.0",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -4463,13 +4506,13 @@
"node_modules/@types/aria-query": {
"version": "5.0.4",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7",
@@ -4632,7 +4675,6 @@
"version": "14.1.2",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
@@ -5148,7 +5190,6 @@
"node_modules/acorn": {
"version": "8.14.0",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5197,7 +5238,6 @@
"node_modules/ajv": {
"version": "6.12.6",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -5775,6 +5815,8 @@
},
"node_modules/before-after-hook": {
"version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
+ "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
"dev": true,
"license": "Apache-2.0"
},
@@ -5970,7 +6012,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -6704,7 +6745,6 @@
"version": "3.39.0",
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
@@ -6904,19 +6944,22 @@
},
"node_modules/csstype": {
"version": "3.1.3",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"license": "BSD-2-Clause"
},
"node_modules/danger": {
- "version": "12.3.4",
+ "version": "13.0.4",
+ "resolved": "https://registry.npmjs.org/danger/-/danger-13.0.4.tgz",
+ "integrity": "sha512-IAdQ5nSJyIs4zKj6AN35ixt2B0Ce3WZUm3IFe/CMnL/Op7wV7IGg4D348U0EKNaNPP58QgXbdSk9pM+IXP1QXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@gitbeaker/rest": "^38.0.0",
- "@octokit/rest": "^18.12.0",
+ "@octokit/rest": "^20.1.2",
"async-retry": "1.2.3",
"chalk": "^2.3.0",
"commander": "^2.18.0",
@@ -6927,6 +6970,7 @@
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.1",
"hyperlinker": "^1.0.0",
+ "ini": "^5.0.0",
"json5": "^2.2.3",
"jsonpointer": "^5.0.0",
"jsonwebtoken": "^9.0.0",
@@ -6936,14 +6980,13 @@
"lodash.keys": "^4.0.8",
"lodash.mapvalues": "^4.6.0",
"lodash.memoize": "^4.1.2",
- "memfs-or-file-map-to-github-branch": "^1.2.1",
+ "memfs-or-file-map-to-github-branch": "^1.3.0",
"micromatch": "^4.0.4",
"node-cleanup": "^2.1.2",
"node-fetch": "^2.6.7",
"override-require": "^1.1.1",
"p-limit": "^2.1.0",
"parse-diff": "^0.7.0",
- "parse-git-config": "^2.0.3",
"parse-github-url": "^1.0.2",
"parse-link-header": "^2.0.0",
"pinpoint": "^1.1.0",
@@ -7026,6 +7069,16 @@
"node": ">=4"
}
},
+ "node_modules/danger/node_modules/ini": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz",
+ "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
"node_modules/danger/node_modules/supports-color": {
"version": "5.5.0",
"dev": true,
@@ -7278,6 +7331,8 @@
},
"node_modules/deprecation": {
"version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
+ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
"dev": true,
"license": "ISC"
},
@@ -7363,7 +7418,8 @@
"node_modules/dom-accessibility-api": {
"version": "0.5.16",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/dom-serializer": {
"version": "2.0.0",
@@ -7779,7 +7835,6 @@
"node_modules/eslint": {
"version": "9.17.0",
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -7867,7 +7922,6 @@
"node_modules/eslint-config-prettier": {
"version": "9.1.0",
"license": "MIT",
- "peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -8030,6 +8084,7 @@
"node_modules/eslint-plugin-cypress": {
"version": "3.6.0",
"license": "MIT",
+ "peer": true,
"dependencies": {
"globals": "^13.20.0"
},
@@ -8040,6 +8095,7 @@
"node_modules/eslint-plugin-cypress/node_modules/globals": {
"version": "13.24.0",
"license": "MIT",
+ "peer": true,
"dependencies": {
"type-fest": "^0.20.2"
},
@@ -8053,6 +8109,7 @@
"node_modules/eslint-plugin-cypress/node_modules/type-fest": {
"version": "0.20.2",
"license": "(MIT OR CC0-1.0)",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -8063,7 +8120,6 @@
"node_modules/eslint-plugin-import": {
"version": "2.31.0",
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.8",
@@ -8102,7 +8158,6 @@
"node_modules/eslint-plugin-jest": {
"version": "28.10.0",
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
@@ -8126,7 +8181,6 @@
"node_modules/eslint-plugin-jest-formatting": {
"version": "3.1.0",
"license": "MIT",
- "peer": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@@ -8402,6 +8456,7 @@
"node_modules/eslint-plugin-playwright": {
"version": "1.8.3",
"license": "MIT",
+ "peer": true,
"workspaces": [
"examples"
],
@@ -8424,6 +8479,7 @@
"node_modules/eslint-plugin-playwright/node_modules/globals": {
"version": "13.24.0",
"license": "MIT",
+ "peer": true,
"dependencies": {
"type-fest": "^0.20.2"
},
@@ -8437,6 +8493,7 @@
"node_modules/eslint-plugin-playwright/node_modules/type-fest": {
"version": "0.20.2",
"license": "(MIT OR CC0-1.0)",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -8447,7 +8504,6 @@
"node_modules/eslint-plugin-prettier": {
"version": "5.2.1",
"license": "MIT",
- "peer": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.9.1"
@@ -8506,7 +8562,6 @@
"node_modules/eslint-plugin-react-hooks": {
"version": "5.1.0",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10"
},
@@ -8539,7 +8594,6 @@
"node_modules/eslint-plugin-testing-library": {
"version": "7.1.1",
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "^8.15.0",
"@typescript-eslint/utils": "^8.15.0"
@@ -8914,17 +8968,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/expand-tilde": {
- "version": "2.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "homedir-polyfill": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/expect": {
"version": "29.7.0",
"devOptional": true,
@@ -9063,17 +9106,6 @@
"version": "3.0.2",
"license": "MIT"
},
- "node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"license": "MIT"
@@ -9314,14 +9346,6 @@
"node": ">= 0.6"
}
},
- "node_modules/fs-exists-sync": {
- "version": "0.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fs-extra": {
"version": "8.1.0",
"dev": true,
@@ -9502,19 +9526,6 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
- "node_modules/git-config-path": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "fs-exists-sync": "^0.1.0",
- "homedir-polyfill": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/github-slugger": {
"version": "2.0.0",
"dev": true,
@@ -9778,17 +9789,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/homedir-polyfill": {
- "version": "1.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parse-passwd": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/hosted-git-info": {
"version": "4.1.0",
"dev": true,
@@ -10282,14 +10282,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/is-extendable": {
- "version": "0.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-extglob": {
"version": "2.1.1",
"license": "MIT",
@@ -10414,14 +10406,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-plain-object": {
- "version": "5.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-port-reachable": {
"version": "4.0.0",
"license": "MIT",
@@ -10724,7 +10708,6 @@
"version": "29.7.0",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -13730,6 +13713,7 @@
"version": "1.5.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -13795,7 +13779,6 @@
"version": "14.1.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
@@ -15645,19 +15628,6 @@
"version": "2.0.11",
"license": "MIT"
},
- "node_modules/parse-git-config": {
- "version": "2.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "expand-tilde": "^2.0.2",
- "git-config-path": "^1.0.1",
- "ini": "^1.3.5"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/parse-github-url": {
"version": "1.0.3",
"dev": true,
@@ -15706,14 +15676,6 @@
"xtend": "~4.0.1"
}
},
- "node_modules/parse-passwd": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/parse5": {
"version": "7.2.1",
"dev": true,
@@ -16034,7 +15996,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
@@ -16126,7 +16087,6 @@
"node_modules/prettier": {
"version": "3.4.2",
"license": "MIT",
- "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -16151,6 +16111,7 @@
"version": "27.5.1",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -16164,6 +16125,7 @@
"version": "5.0.1",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -16172,6 +16134,7 @@
"version": "5.2.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -16409,7 +16372,6 @@
"node_modules/react": {
"version": "18.3.1",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -16420,7 +16382,6 @@
"node_modules/react-dom": {
"version": "18.3.1",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -16432,7 +16393,8 @@
"node_modules/react-is": {
"version": "17.0.2",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/react-property": {
"version": "2.0.2",
@@ -17132,7 +17094,6 @@
"version": "3.29.5",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -17376,7 +17337,6 @@
"version": "8.17.1",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -18725,8 +18685,7 @@
},
"node_modules/tslib": {
"version": "2.8.1",
- "license": "0BSD",
- "peer": true
+ "license": "0BSD"
},
"node_modules/turndown": {
"version": "7.2.1",
@@ -18859,6 +18818,7 @@
"node_modules/typescript": {
"version": "5.7.2",
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -19189,6 +19149,8 @@
},
"node_modules/universal-user-agent": {
"version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
+ "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
"dev": true,
"license": "ISC"
},
@@ -19499,7 +19461,6 @@
"version": "5.97.1",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
@@ -19545,7 +19506,6 @@
"version": "4.10.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^1.2.0",
@@ -20123,7 +20083,7 @@
},
"packages/features": {
"name": "@asos/web-toggle-point-features",
- "version": "0.4.2",
+ "version": "0.5.0",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0"
diff --git a/package.json b/package.json
index 6f77d0d..d142a93 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
"browserslist-config-asos": "file:./peripheral/browserslist-config-asos",
"core-js": "^3.0.0",
"cross-env": "^7.0.3",
- "danger": "^12.3.4",
+ "danger": "^13.0.3",
"eslint": "^9.15.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.9.0",
diff --git a/packages/features/docs/CHANGELOG.md b/packages/features/docs/CHANGELOG.md
index 5700eaf..f7baa9e 100644
--- a/packages/features/docs/CHANGELOG.md
+++ b/packages/features/docs/CHANGELOG.md
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.5.0] - 2025-10-20
+
+### Changed
+
+- added a `FeaturesStoreFactory` interface that insists on a `toggleType` input to all features store factories
+ - for the `nodeRequestScopedFeaturesStoreFactory`, this becomes a unique key against [a realm-wide Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry), to ensure the store is shared in multi-runtime realms (e.g. NextJS compiled applications)
+
+### Fixed
+
+- renamed `nodeRequestScopedFeaturesStoreFactory` from `nodeRequestScopedStoreFactory` for consistency
+- ensured interfaces in JSDoc output properly
+ - concede to a [`@callback`](https://jsdoc.app/tags-callback) to represent the `FeaturesStoreFactory` interface.
+- descriptions in test files properly matching factories
+- some line-breaks in this CHANGELOG
+- more accurate [`valtio`](https://github.com/pmndrs/valtio) examples in the README
+
## [0.4.2] - 2025-09-30
### Fixed
@@ -79,6 +95,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `nodeRequestScopedStoreFactory` export, a "feature store" factory function designed for use within a request-scoped server runtime, e.g. primed within Express middleware
- `reactContextFeaturesStoreFactory` export, a "feature store" factory function, designed to wrap a proportion of a react application with a contextual features state
- `ssrBackedReactContextFeaturesStoreFactory` export, a "feature store" factory function, designed to wrap a proportion of a react application with a contextual features state, backed by the [`ssr` package](../../ssr/docs/README.md) for use without an SSR-compatible React framework.
+
...after spending _far too long_ trying to get node `"exports"` with paths working, so that could better create specific node/browser import paths, before giving up (it works, but ESLint doesn't play ball...)
## [0.2.4] - 2024-11-27
diff --git a/packages/features/docs/README.md b/packages/features/docs/README.md
index 04de567..b53cb74 100644
--- a/packages/features/docs/README.md
+++ b/packages/features/docs/README.md
@@ -22,7 +22,11 @@ The package contains the following exports:
### `storeFactories/globalFeaturesStoreFactory`
-A "global" features store factory: a thin wrapper around a singleton, this is an extension point for future plugins etc.
+A "global" features store factory: a thin wrapper around a singleton, this is an extension point for future plugins etc. Each invocation will create a new store, even with a `toggleType` matching a prior invocation.
+
+It accepts the following parameters:
+- `toggleType`
+ - the type of the toggle, used only for debugging.
It exports a store with:
@@ -33,23 +37,30 @@ It exports a store with:
For protection against variation (or other) code modifying the toggle state unduly, the value passed could be [deep frozen](https://github.com/christophehurpeau/deep-freeze-es6), e.g.
```js
import { deepFreeze } from "deep-freeze-es6";
-const value = deepFreeze(input);
+const initialValue = {};
+featuresStore.setValue({ value: deepFreeze(initialValue) });
```
-For reactive values, without the need for a React or other contextual wrapper, consider [`valtio`](https://github.com/pmndrs/valtio):
+For reactive values, without the need for a React or other contextual wrapper, consider wrapping an object with [`valtio`](https://github.com/pmndrs/valtio):
```js
import { proxy } from "valtio/vanilla";
-const value = proxy(input);
+const initialValue = {};
+featuresStore.setValue({ value: proxy(initialValue) });
```
...which can then be subscribed to in an appropriate toggle point, to re-evaluate toggled functions:
```js
import { subscribe } from "valtio/vanilla";
-subscribe(value, () => { /* re-evaluate */ });
+subscribe(featuresStore.getFeatures(), () => { /* re-evaluate */ });
```
If using React (e.g. `react-pointcuts` package), can just use the native support in Valtio:
```js
import { proxy, useSnapshot } from "valtio";
-const value = proxy(input); // passed to `stores/global`
-const getActiveFeatures = useSnapshot.bind(undefined, value); // passed to `withTogglePointFactory`
+const initialValue = {};
+featuresStore.setValue({ value: proxy(initialValue) });
+export const setValue = (input) => // consumed in updating code-paths
+ featuresStore.setValue({
+ value: Object.assign(featuresStore.getFeatures(), input)
+ });
+export const getActiveFeatures = () => useSnapshot(featuresStore.getFeatures()); // passed to `withTogglePointFactory`
```
...which will then re-render consuming components based on the parts of the toggle state they are reliant on.
@@ -57,6 +68,10 @@ const getActiveFeatures = useSnapshot.bind(undefined, value); // passed to `with
A "request scoped" features store factory, for use in [Node](https://nodejs.org/).
+It accepts the following parameters:
+- `toggleType`
+ - the type of the toggle, this is keyed against a singleton referenced by [a runtime-wide global symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry), to ensure it works across multi-compilation runtimes (e.g. NextJs) / throughout a [realm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model#realms). N.B. Each invocation with the same toggleType will return the initial store.
+
It exports a store with:
- a `setValue` function that sets a current value, taking a `scopeCallBack` (along with a `value`), under which the value is scoped.
- This is using [`AsyncLocalStorage.run`](https://nodejs.org/api/async_context.html#asynclocalstoragerunstore-callback-args) under the hood, which can be plugged into Express middleware thus:
@@ -64,7 +79,7 @@ It exports a store with:
import express from "express";
const app = express();
- const featuresStore = requestScopedFeaturesStoreFactory();
+ const featuresStore = requestScopedFeaturesStoreFactory({ toggleType: "some type of toggle" });
app.use((request, response, next) => {
const value = ?? // some value holding toggle state, either based on `request`, or scoped from outside this middleware, etc.
@@ -81,6 +96,10 @@ It exports a store with:
### `storeFactories/reactContextFeaturesStoreFactory`
+It accepts the following parameters:
+- `toggleType`
+ - the type of the toggle, forming the displayName of the react context provider
+
It exports a store with:
- a `providerFactory` factory function, creating a [react context provider](https://reactjs.org/docs/context.html#contextprovider).
- appropriate parts of the react tree, that need to have toggled react components, should be wrapped by this provider. It should be passed a value representing active features state.
@@ -95,8 +114,8 @@ It exports a store with the same signature as that exported by `reactContextFeat
It accepts the following parameters:
- `namespace`
- this becomes a prefix for the `id` of the `application/json` script written to the page, useful for pages running multiple react applications.
-- `name`
- - the type of the toggle, the latter part of the `id` of the aforementioned script, and becoming the prop holding the features state.
+- `toggleType`
+ - the type of the toggle, the latter part of the `id` of the aforementioned script, and becoming the prop holding the features state, and forming the displayName of the underlying react context provider
- `logWarning`
- a method to log warnings, should the serialized json somehow become malformed when hydrating the client application
- this was designed to allow modifications of markup in systems upstream of the origin, but downstream of the browser, with a view to ensure adequate telemetry is in place.
diff --git a/packages/features/package.json b/packages/features/package.json
index a2c55b3..8496603 100644
--- a/packages/features/package.json
+++ b/packages/features/package.json
@@ -1,7 +1,7 @@
{
"name": "@asos/web-toggle-point-features",
"description": "toggle point features code, used to store toggle state",
- "version": "0.4.2",
+ "version": "0.5.0",
"license": "MIT",
"type": "module",
"main": "./lib/global.js",
diff --git a/packages/features/src/global.js b/packages/features/src/global.js
index 7e8f4e3..c75d110 100644
--- a/packages/features/src/global.js
+++ b/packages/features/src/global.js
@@ -4,7 +4,15 @@ import "./external";
* Application code for holding feature toggle state
* @module web-toggle-point-features
*/
-
+/**
+ * Factories for feature toggle stores
+ *
+ * @callback FeaturesStoreFactory
+ * @memberof module:web-toggle-point-features
+ * @param {object} params parameters
+ * @param {string} params.toggleType The type of toggle in the store
+ * @returns {module:web-toggle-point-features.FeaturesStore} A store for features
+ */
/**
* Interface for feature toggle stores
*
diff --git a/packages/features/src/storeFactories/globalFeaturesStoreFactory.js b/packages/features/src/storeFactories/globalFeaturesStoreFactory.js
index 9c9a4fc..c6876ca 100644
--- a/packages/features/src/storeFactories/globalFeaturesStoreFactory.js
+++ b/packages/features/src/storeFactories/globalFeaturesStoreFactory.js
@@ -9,10 +9,13 @@ const storeMap = new WeakMap();
* Consider {@link https://github.com/christophehurpeau/deep-freeze-es6|deep freezing} the value to prevent accidental mutation, if this is intended to be static.
* For reactive decisions, consider implementing something that allows for reactivity e.g. a {@link https://github.com/pmndrs/valtio|valtio/vanilla} proxy, and subscribe appropriately in a toggle point.
* @memberof module:web-toggle-point-features
+ * @implements module:web-toggle-point-features.FeaturesStoreFactory
+ * @param {object} params parameters
+ * @param {string} params.toggleType The type of toggle in the store, used for debugging
* @returns {module:web-toggle-point-features.globalFeaturesStore} A store for features, held globally in the application.
*/
-const globalFeaturesStoreFactory = () => {
- const identifier = {};
+const globalFeaturesStoreFactory = ({ toggleType }) => {
+ const identifier = Symbol(toggleType);
/**
* @name globalFeaturesStore
* @memberof module:web-toggle-point-features
diff --git a/packages/features/src/storeFactories/globalFeaturesStoreFactory.test.js b/packages/features/src/storeFactories/globalFeaturesStoreFactory.test.js
index 126183d..e0d04aa 100644
--- a/packages/features/src/storeFactories/globalFeaturesStoreFactory.test.js
+++ b/packages/features/src/storeFactories/globalFeaturesStoreFactory.test.js
@@ -1,10 +1,11 @@
import globalFeaturesStoreFactory from "./globalFeaturesStoreFactory";
describe("globalFeaturesStoreFactory", () => {
+ const toggleType = "test-toggle-type";
let featuresStoreFactory;
beforeEach(() => {
- featuresStoreFactory = globalFeaturesStoreFactory();
+ featuresStoreFactory = globalFeaturesStoreFactory({ toggleType });
});
describe("when setting a value", () => {
@@ -34,7 +35,7 @@ describe("globalFeaturesStoreFactory", () => {
let newFeaturesStoreFactory;
beforeEach(() => {
- newFeaturesStoreFactory = globalFeaturesStoreFactory();
+ newFeaturesStoreFactory = globalFeaturesStoreFactory({ toggleType });
});
it("should not share the value with the new store", () => {
diff --git a/packages/features/src/storeFactories/nodeRequestScopedFeaturesStoreFactory.js b/packages/features/src/storeFactories/nodeRequestScopedFeaturesStoreFactory.js
index 9e74106..5666b27 100644
--- a/packages/features/src/storeFactories/nodeRequestScopedFeaturesStoreFactory.js
+++ b/packages/features/src/storeFactories/nodeRequestScopedFeaturesStoreFactory.js
@@ -1,33 +1,44 @@
import { AsyncLocalStorage } from "async_hooks";
+const globalSingleton = Symbol.for(
+ "web-toggle-point-features.node-request-scoped-features-store"
+);
+
/**
* A factory function used to create a store for features, held in request-scoped global value.
* Should only be used server-side, for anything user or request specific.
* A thin wrapper around node {@link https://nodejs.org/api/async_context.html#class-asynclocalstorage|AsyncLocalStorage}, used as an extension point for future plugins.
* @memberof module:web-toggle-point-features
+ * @implements module:web-toggle-point-features.FeaturesStoreFactory
+ * @param {object} params parameters
+ * @param {string} params.toggleType The type of toggle in the store, which should be unique within a javascript realm.
* @returns {module:web-toggle-point-features.nodeRequestScopedFeaturesStore} A store for features, scoped for the current request.
*/
-const nodeRequestScopedFeaturesStoreFactory = () => {
- const store = new AsyncLocalStorage();
-
- /**
- * @name nodeRequestScopedFeaturesStore
- * @memberof module:web-toggle-point-features
- * @implements module:web-toggle-point-features.FeaturesStore
- * @implements module:web-toggle-point-features.SingletonFeaturesStore
- */
- return {
- setValue: ({ value, scopeCallBack }) => {
- store.run(value, scopeCallBack);
- },
- getFeatures: () => {
- const features = store.getStore();
- if (features === undefined) {
- throw Error("Called outside of request context");
+const nodeRequestScopedFeaturesStoreFactory = ({ toggleType }) => {
+ if (!Object.getOwnPropertySymbols(globalThis).includes(globalSingleton)) {
+ globalThis[globalSingleton] = {};
+ }
+ if (!globalThis[globalSingleton][toggleType]) {
+ const store = new AsyncLocalStorage();
+ globalThis[globalSingleton][toggleType] = /**
+ * @name nodeRequestScopedFeaturesStore
+ * @memberof module:web-toggle-point-features
+ * @implements module:web-toggle-point-features.FeaturesStore
+ * @implements module:web-toggle-point-features.SingletonFeaturesStore
+ */ {
+ setValue: ({ value, scopeCallBack }) => {
+ store.run(value, scopeCallBack);
+ },
+ getFeatures: () => {
+ const features = store.getStore();
+ if (features === undefined) {
+ throw Error("Called outside of request context");
+ }
+ return features;
}
- return features;
- }
- };
+ };
+ }
+ return globalThis[globalSingleton][toggleType];
};
export default nodeRequestScopedFeaturesStoreFactory;
diff --git a/packages/features/src/storeFactories/nodeRequestScopedFeaturesStoreFactory.test.js b/packages/features/src/storeFactories/nodeRequestScopedFeaturesStoreFactory.test.js
index 86ae84d..8e1216f 100644
--- a/packages/features/src/storeFactories/nodeRequestScopedFeaturesStoreFactory.test.js
+++ b/packages/features/src/storeFactories/nodeRequestScopedFeaturesStoreFactory.test.js
@@ -9,10 +9,11 @@ jest.mock("async_hooks", () => ({
}));
describe("nodeRequestScopedFeaturesStoreFactory", () => {
+ const toggleType = "test-toggle-type";
let requestScopedStore;
beforeEach(() => {
- requestScopedStore = nodeRequestScopedFeaturesStoreFactory();
+ requestScopedStore = nodeRequestScopedFeaturesStoreFactory({ toggleType });
});
it("should create an AsyncLocalStorage store", () => {
@@ -30,23 +31,23 @@ describe("nodeRequestScopedFeaturesStoreFactory", () => {
describe("when setting a value", () => {
const value = Symbol("test-value");
const scopeCallBack = Symbol("test-callback");
+ let storeMock;
beforeEach(() => {
requestScopedStore.setValue({ value, scopeCallBack });
+ storeMock = AsyncLocalStorage.mock.results[0].value;
});
it("should scope the value to the descendants of the callback, by running it in the local storage", () => {
- expect(
- AsyncLocalStorage.mock.results.pop().value.run
- ).toHaveBeenCalledWith(value, scopeCallBack);
+ expect(storeMock.run).toHaveBeenCalledWith(value, scopeCallBack);
});
describe("when getting the features", () => {
const returnedValue = Symbol("test-value");
beforeEach(() => {
- AsyncLocalStorage.mock.results
- .pop()
- .value.getStore.mockReturnValue(returnedValue);
+ AsyncLocalStorage.mock.results[0].value.getStore.mockReturnValue(
+ returnedValue
+ );
});
it("should return the value scoped to the current request", () => {
@@ -54,4 +55,31 @@ describe("nodeRequestScopedFeaturesStoreFactory", () => {
});
});
});
+
+ describe("when creating a new store with the same toggleType", () => {
+ let newRequestScopedStore;
+ beforeEach(() => {
+ newRequestScopedStore = nodeRequestScopedFeaturesStoreFactory({
+ toggleType
+ });
+ });
+
+ it("should return the same store instance", () => {
+ expect(newRequestScopedStore).toBe(requestScopedStore);
+ });
+ });
+
+ describe("when creating a new store with a different toggleType", () => {
+ let newRequestScopedStore;
+ const newToggleType = "new-test-toggle-type";
+ beforeEach(() => {
+ newRequestScopedStore = nodeRequestScopedFeaturesStoreFactory({
+ toggleType: newToggleType
+ });
+ });
+
+ it("should return a different store instance", () => {
+ expect(newRequestScopedStore).not.toBe(requestScopedStore);
+ });
+ });
});
diff --git a/packages/features/src/storeFactories/reactContextFeaturesStoreFactory.js b/packages/features/src/storeFactories/reactContextFeaturesStoreFactory.js
index 6ef45cb..f056ab3 100644
--- a/packages/features/src/storeFactories/reactContextFeaturesStoreFactory.js
+++ b/packages/features/src/storeFactories/reactContextFeaturesStoreFactory.js
@@ -5,9 +5,12 @@ import PropTypes from "prop-types";
* A factory function used to create a store for features, held in a {@link https://reactjs.org/docs/context.html|React context}.
* A thin wrapper around a context, used as an extension point for future plugins.
* @memberof module:web-toggle-point-features
+ * @implements module:web-toggle-point-features.FeaturesStoreFactory
+ * @param {object} params parameters.
+ * @param {string} params.name The name of type of toggle in the store, used for debugging and the display name of the react context provider.
* @returns {module:web-toggle-point-features.reactContextFeaturesStore} A store for features, held within a {@link https://reactjs.org/docs/context.html|React context}.
*/
-const reactContextFeaturesStoreFactory = ({ name }) => {
+const reactContextFeaturesStoreFactory = ({ toggleType }) => {
const context = createContext();
/**
@@ -25,7 +28,7 @@ const reactContextFeaturesStoreFactory = ({ name }) => {
children: PropTypes.node.isRequired,
value: PropTypes.object.isRequired
};
- Provider.displayName = `${name[0].toUpperCase() + name.slice(1)}ToggleProvider`;
+ Provider.displayName = `${toggleType[0].toUpperCase() + toggleType.slice(1)}ToggleProvider`;
return Provider;
},
getFeatures: useContext.bind(undefined, context)
diff --git a/packages/features/src/storeFactories/reactContextFeaturesStoreFactory.test.js b/packages/features/src/storeFactories/reactContextFeaturesStoreFactory.test.js
index e968a1e..0dfd4ce 100644
--- a/packages/features/src/storeFactories/reactContextFeaturesStoreFactory.test.js
+++ b/packages/features/src/storeFactories/reactContextFeaturesStoreFactory.test.js
@@ -2,11 +2,11 @@ import reactContextFeaturesStoreFactory from "./reactContextFeaturesStoreFactory
import { render, screen } from "@testing-library/react";
describe("reactContextFeaturesStoreFactory", () => {
- const name = "test-name";
+ const toggleType = "test-toggle-type";
let reactContextStore;
beforeEach(() => {
- reactContextStore = reactContextFeaturesStoreFactory({ name });
+ reactContextStore = reactContextFeaturesStoreFactory({ toggleType });
});
describe("when creating a provider", () => {
@@ -27,9 +27,9 @@ describe("reactContextFeaturesStoreFactory", () => {
);
});
- it("should set an appropriate display name for the provider", () => {
+ it("should set an appropriate display name for the provider, based on the provided toggleType", () => {
expect(Provider.displayName).toBe(
- `${name[0].toUpperCase() + name.slice(1)}ToggleProvider`
+ `${toggleType[0].toUpperCase() + toggleType.slice(1)}ToggleProvider`
);
});
diff --git a/packages/features/src/storeFactories/ssrBackedReactContextFeaturesStoreFactory.js b/packages/features/src/storeFactories/ssrBackedReactContextFeaturesStoreFactory.js
index f2078b9..f2c067c 100644
--- a/packages/features/src/storeFactories/ssrBackedReactContextFeaturesStoreFactory.js
+++ b/packages/features/src/storeFactories/ssrBackedReactContextFeaturesStoreFactory.js
@@ -5,15 +5,20 @@ import reactContextFeaturesStoreFactory from "./reactContextFeaturesStoreFactory
* A factory function used to create a store for features, held in a {@link https://reactjs.org/docs/context.html|React context}, backed by server-side rendering.
* A wrapper around a {@link module:web-toggle-point-features.reactContextFeaturesStore|reactContextFeaturesStore}, with server-side rendering supplied by the {@link module:web-toggle-point-ssr|toggle-point-ssr} package.
* @memberof module:web-toggle-point-features
+ * @implements module:web-toggle-point-features.FeaturesStoreFactory
+ * @param {object} params parameters
+ * @param {string} [params.namespace="toggles"] The namespace for the script tag used to inject the initial state into the client-side application.
+ * @param {string} params.toggleType The type of toggle in the store, used for debugging, display name of the react context provider, the script name of the json script and the prop name used to pass the value to the provider.
+ * @param {function} params.logWarning A function to log warnings, used to highlight any malformed JSON in-case this is processed outside of the SSR process.
* @returns {module:web-toggle-point-features.ssrBackedReactContextFeaturesStore} A store for features, held within a {@link https://reactjs.org/docs/context.html|React context}.
*/
const ssrBackedReactContextFeaturesStoreFactory = ({
namespace = "toggles",
- name,
+ toggleType,
logWarning
}) => {
const { providerFactory, ...rest } = reactContextFeaturesStoreFactory({
- name
+ toggleType
});
/**
@@ -26,17 +31,17 @@ const ssrBackedReactContextFeaturesStoreFactory = ({
providerFactory: () => {
const FeaturesProvider = providerFactory();
const SSRBackedFeaturesProvider = withJsonIsomorphism(
- ({ children, [name]: value }) => (
+ ({ children, [toggleType]: value }) => (
{children}
),
logWarning,
{
- scriptId: `${namespace}_${name}`,
- propName: name
+ scriptId: `${namespace}_${toggleType}`,
+ propName: toggleType
}
);
return ({ children, value }) => (
-
+
{children}
);
diff --git a/packages/features/src/storeFactories/ssrBackedReactContextFeaturesStoreFactory.test.js b/packages/features/src/storeFactories/ssrBackedReactContextFeaturesStoreFactory.test.js
index 0bfc09a..0ce5b44 100644
--- a/packages/features/src/storeFactories/ssrBackedReactContextFeaturesStoreFactory.test.js
+++ b/packages/features/src/storeFactories/ssrBackedReactContextFeaturesStoreFactory.test.js
@@ -25,15 +25,15 @@ describe("ssrBackedReactContextFeaturesStoreFactory", () => {
beforeEach(() => {
props = {
- name: "test-name",
+ toggleType: "test-toggle-type",
logWarning: Symbol("test-log-warning")
};
});
const makeCommonAssertions = () => {
- it("should call the reactContextStoreFactory with the name", () => {
+ it("should call the reactContextStoreFactory with the toggleType", () => {
expect(reactContextFeaturesStoreFactory).toHaveBeenCalledWith({
- name: props.name
+ toggleType: props.toggleType
});
});
@@ -57,13 +57,13 @@ describe("ssrBackedReactContextFeaturesStoreFactory", () => {
).toHaveBeenCalled();
});
- it("should create an SSR-backed react component that serializes the provided value in a script with a namespace & named id", () => {
+ it("should create an SSR-backed react component that serializes the provided value in a script with a namespace & named id, using the supplied toggleType", () => {
expect(withJsonIsomorphism).toHaveBeenCalledWith(
expect.any(Function),
props.logWarning,
{
scriptId: expect.any(String),
- propName: props.name
+ propName: props.toggleType
}
);
});
@@ -83,7 +83,7 @@ describe("ssrBackedReactContextFeaturesStoreFactory", () => {
it("should pass the value to the SSR-backed component", () => {
expect(MockSSRBackedFeaturesProvider).toHaveBeenCalledWith(
expect.objectContaining({
- [props.name]: value
+ [props.toggleType]: value
}),
expect.anything()
);
@@ -112,7 +112,7 @@ describe("ssrBackedReactContextFeaturesStoreFactory", () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
- scriptId: `${props.namespace}_${props.name}`
+ scriptId: `${props.namespace}_${props.toggleType}`
})
);
});
@@ -130,7 +130,7 @@ describe("ssrBackedReactContextFeaturesStoreFactory", () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
- scriptId: `toggles_${props.name}`
+ scriptId: `toggles_${props.toggleType}`
})
);
});