Skip to content

Commit 62999e1

Browse files
toddbaertbeeme1mrgemini-code-assist[bot]lukas-reining
authored andcommitted
feat: add debounce hook (#1389)
Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Michael Beemer <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Lukas Reining <[email protected]> Signed-off-by: Thomas Poignant <[email protected]>
1 parent e84375a commit 62999e1

17 files changed

+1005
-1
lines changed

.release-please-manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222
"libs/providers/unleash-web": "0.1.1",
2323
"libs/providers/growthbook": "0.1.2",
2424
"libs/providers/aws-ssm": "0.1.3",
25-
"libs/providers/flagsmith": "0.1.2"
25+
"libs/providers/flagsmith": "0.1.2",
26+
"libs/hooks/debounce": "0.1.0"
2627
}

libs/hooks/debounce/.eslintrc.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"extends": "../../../.eslintrc.json",
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
},
17+
{
18+
"files": ["*.json"],
19+
"parser": "jsonc-eslint-parser",
20+
"rules": {
21+
"@nx/dependency-checks": [
22+
"error",
23+
{
24+
"ignoredFiles": ["{projectRoot}/eslint.config.{js,cjs,mjs}"]
25+
}
26+
]
27+
}
28+
}
29+
]
30+
}

libs/hooks/debounce/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Debounce Hook
2+
3+
This is a utility "meta" hook, which can be used to effectively debounce or rate limit other hooks based on various parameters.
4+
This can be especially useful for certain UI frameworks and SDKs that frequently re-render and re-evaluate flags (React, Angular, etc).
5+
6+
## Installation
7+
8+
```
9+
$ npm install @openfeature/debounce-hook
10+
```
11+
12+
### Peer dependencies
13+
14+
This package only requires the `@openfeature/core` dependency, which is installed automatically no matter which OpenFeature JavaScript SDK you are using.
15+
16+
## Usage
17+
18+
Simply wrap your hook with the debounce hook by passing it as a constructor arg, and then configure the remaining options.
19+
In the example below, we wrap a logging hook.
20+
This debounces all its stages, so it only logs a maximum of once a minute for each flag key, no matter how many times that flag is evaluated.
21+
22+
```ts
23+
const debounceHook = new DebounceHook(loggingHook, {
24+
debounceTime: 60_000, // how long to wait before the hook can fire again
25+
maxCacheItems: 100, // max amount of items to keep in the cache; if exceeded, the oldest item is dropped
26+
});
27+
28+
// add the hook globally
29+
OpenFeature.addHooks(debounceHook);
30+
31+
// or at a specific client
32+
client.addHooks(debounceHook);
33+
```
34+
35+
The hook maintains a simple expiring cache with a fixed max size and keeps a record of recent evaluations based on an optional key-generation function (cacheKeySupplier).
36+
Be default, the key-generation function is purely based on the flag key.
37+
Particularly in server use-cases, you may want to take the targetingKey or other contextual information into account in your debouncing.
38+
Below we see an example using this, as well as other advanced options:
39+
40+
```ts
41+
const debounceHook = new DebounceHook<string>(loggingHook, {
42+
cacheKeySupplier: (flagKey, context) => flagKey + context.targetingKey, // cache on a combination of user and flag key
43+
debounceTime: 60_000, // debounce for 60 seconds (per user due to our cacheKeySupplier above)
44+
maxCacheItems: 1000, // cache a maximum of 1000 records
45+
cacheErrors: false, // don't debounce errors; always re-run if the last run threw
46+
logger: console, // optional logger
47+
});
48+
```
49+
50+
## Development
51+
52+
### Building
53+
54+
Run `nx package hooks-debounce` to build the library.
55+
56+
### Running unit tests
57+
58+
Run `nx test hooks-debounce` to execute the unit tests via [Jest](https://jestjs.io).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": [["minify", { "builtIns": false }]]
3+
}

libs/hooks/debounce/jest.config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default {
2+
displayName: 'debounce',
3+
preset: '../../../jest.preset.js',
4+
transform: {
5+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
6+
},
7+
moduleFileExtensions: ['ts', 'js', 'html'],
8+
coverageDirectory: '../coverage/hooks',
9+
};

libs/hooks/debounce/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@openfeature/debounce-hook",
3+
"version": "0.0.1",
4+
"dependencies": {
5+
"tslib": "^2.3.0"
6+
},
7+
"main": "./src/index.js",
8+
"typings": "./src/index.d.ts",
9+
"scripts": {
10+
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
11+
"current-version": "echo $npm_package_version"
12+
},
13+
"license": "Apache-2.0",
14+
"peerDependencies": {
15+
"@openfeature/core": "^1.9.1"
16+
}
17+
}

libs/hooks/debounce/project.json

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"name": "debounce",
3+
"$schema": "../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "hooks/src",
5+
"projectType": "library",
6+
"release": {
7+
"version": {
8+
"generatorOptions": {
9+
"packageRoot": "dist/{projectRoot}",
10+
"currentVersionResolver": "git-tag"
11+
}
12+
}
13+
},
14+
"tags": [],
15+
"targets": {
16+
"nx-release-publish": {
17+
"options": {
18+
"packageRoot": "dist/{projectRoot}"
19+
}
20+
},
21+
"lint": {
22+
"executor": "@nx/eslint:lint"
23+
},
24+
"test": {
25+
"executor": "@nx/jest:jest",
26+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
27+
"options": {
28+
"jestConfig": "{projectRoot}/jest.config.ts"
29+
}
30+
},
31+
"package": {
32+
"executor": "@nx/rollup:rollup",
33+
"outputs": ["{options.outputPath}"],
34+
"options": {
35+
"project": "libs/hooks/debounce/package.json",
36+
"outputPath": "dist/libs/hooks/debounce",
37+
"entryFile": "libs/hooks/debounce/src/index.ts",
38+
"tsConfig": "libs/hooks/debounce/tsconfig.lib.json",
39+
"compiler": "tsc",
40+
"generateExportsField": true,
41+
"umdName": "debounce",
42+
"external": "all",
43+
"format": ["cjs", "esm"],
44+
"assets": [
45+
{
46+
"glob": "package.json",
47+
"input": "./assets",
48+
"output": "./src/"
49+
},
50+
{
51+
"glob": "LICENSE",
52+
"input": "./",
53+
"output": "./"
54+
},
55+
{
56+
"glob": "README.md",
57+
"input": "./libs/hooks/debounce",
58+
"output": "./"
59+
}
60+
]
61+
}
62+
},
63+
"publish": {
64+
"executor": "nx:run-commands",
65+
"options": {
66+
"command": "npm run publish-if-not-exists",
67+
"cwd": "dist/libs/hooks/debounce"
68+
},
69+
"dependsOn": [
70+
{
71+
"projects": "self",
72+
"target": "package"
73+
}
74+
]
75+
}
76+
}
77+
}

libs/hooks/debounce/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './lib/debounce-hook';

0 commit comments

Comments
 (0)