Skip to content

Commit 28a20cd

Browse files
committed
fixup: pr feedback
Signed-off-by: Todd Baert <[email protected]>
1 parent 5076b50 commit 28a20cd

File tree

3 files changed

+28
-31
lines changed

3 files changed

+28
-31
lines changed

libs/hooks/debounce/README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ In the example below, we wrap a logging hook.
2020
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.
2121

2222
```ts
23-
const debounceHook = new DebounceHook<string>(loggingHook, {
23+
const debounceHook = new DebounceHook(loggingHook, {
2424
debounceTime: 60_000, // how long to wait before the hook can fire again
2525
maxCacheItems: 100, // max amount of items to keep in the cache; if exceeded, the oldest item is dropped
2626
});
@@ -34,13 +34,16 @@ client.addHooks(debounceHook);
3434

3535
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).
3636
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:
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:
3839

3940
```ts
4041
const debounceHook = new DebounceHook<string>(loggingHook, {
4142
cacheKeySupplier: (flagKey, context) => flagKey + context.targetingKey, // cache on a combination of user and flag key
42-
debounceTime: 60_000,
43-
maxCacheItems: 1000,
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
4447
});
4548
```
4649

libs/hooks/debounce/src/lib/debounce-hook.spec.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('DebounceHook', () => {
1818
finally: jest.fn(),
1919
};
2020

21-
const hook = new DebounceHook<string>(innerHook, {
21+
const hook = new DebounceHook(innerHook, {
2222
debounceTime: 60_000,
2323
maxCacheItems: 100,
2424
});
@@ -76,7 +76,7 @@ describe('DebounceHook', () => {
7676
after: jest.fn(),
7777
};
7878

79-
const hook = new DebounceHook<boolean>(innerHook, {
79+
const hook = new DebounceHook(innerHook, {
8080
debounceTime: 60_000,
8181
maxCacheItems: 100,
8282
});
@@ -106,7 +106,7 @@ describe('DebounceHook', () => {
106106
before: jest.fn(),
107107
};
108108

109-
const hook = new DebounceHook<string>(innerHook, {
109+
const hook = new DebounceHook(innerHook, {
110110
debounceTime: 60_000,
111111
maxCacheItems: 1,
112112
});
@@ -156,7 +156,7 @@ describe('DebounceHook', () => {
156156
};
157157
const hints = {};
158158

159-
const hook = new DebounceHook<number>(innerHook, {
159+
const hook = new DebounceHook(innerHook, {
160160
cacheKeySupplier: (_, context) => context.targetingKey, // we are caching purely based on the targetingKey in the context, so we will only ever cache one entry
161161
debounceTime: 60_000,
162162
maxCacheItems: 100,
@@ -190,7 +190,7 @@ describe('DebounceHook', () => {
190190
const context = {};
191191

192192
// this hook caches error invocations
193-
const hook = new DebounceHook<string[]>(innerErrorHook, {
193+
const hook = new DebounceHook(innerErrorHook, {
194194
maxCacheItems: 100,
195195
debounceTime: 60_000,
196196
cacheErrors,
@@ -208,7 +208,7 @@ describe('DebounceHook', () => {
208208
it('should have type compatibility with API, client, evaluation', () => {
209209
const webSdkHook = {} as WebSdkHook;
210210

211-
const hook = new DebounceHook<number>(webSdkHook, {
211+
const hook = new DebounceHook(webSdkHook, {
212212
debounceTime: 60_000,
213213
maxCacheItems: 100,
214214
});
@@ -230,7 +230,7 @@ describe('DebounceHook', () => {
230230
finally: jest.fn(),
231231
};
232232

233-
const hook = new DebounceHook<string>(innerWebSdkHook, {
233+
const hook = new DebounceHook(innerWebSdkHook, {
234234
debounceTime: 60_000,
235235
maxCacheItems: 100,
236236
});
@@ -261,7 +261,7 @@ describe('DebounceHook', () => {
261261
it('should have type compatibility with API, client, evaluation', () => {
262262
const serverHook = {} as ServerSdkHook;
263263

264-
const hook = new DebounceHook<number>(serverHook, {
264+
const hook = new DebounceHook(serverHook, {
265265
debounceTime: 60_000,
266266
maxCacheItems: 100,
267267
});
@@ -285,7 +285,7 @@ describe('DebounceHook', () => {
285285
finally: jest.fn(),
286286
};
287287

288-
const hook = new DebounceHook<number>(innerServerSdkHook, {
288+
const hook = new DebounceHook(innerServerSdkHook, {
289289
debounceTime: 60_000,
290290
maxCacheItems: 100,
291291
});
@@ -331,9 +331,11 @@ describe('DebounceHook', () => {
331331
}),
332332
};
333333

334-
const hook = new DebounceHook<number>(innerServerSdkHook, {
334+
const hook = new DebounceHook(innerServerSdkHook, {
335335
debounceTime: 60_000,
336336
maxCacheItems: 100,
337+
cacheErrors: false,
338+
logger: console,
337339
});
338340

339341
const evaluationDetails: EvaluationDetails<number> = {

libs/hooks/debounce/src/lib/debounce-hook.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,7 @@ export type Options = {
8181
* The cacheKeySupplier is used to generate a cache key for the hook, which is used to determine if the hook should be executed or skipped.
8282
* If no cache key supplier is provided for a stage, that stage will always run.
8383
*/
84-
export class DebounceHook<T extends FlagValue = FlagValue>
85-
implements
86-
BaseHook<
87-
T,
88-
Record<string, unknown>,
89-
Promise<EvaluationContext | void> | EvaluationContext | void,
90-
Promise<void> | void
91-
>
92-
{
84+
export class DebounceHook implements BaseHook {
9385
private readonly cache: FixedSizeExpiringCache<HookStagesEntry>;
9486
private readonly cacheErrors: boolean;
9587
private readonly cacheKeySupplier: Options['cacheKeySupplier'];
@@ -112,31 +104,31 @@ export class DebounceHook<T extends FlagValue = FlagValue>
112104
});
113105
}
114106

115-
before(hookContext: HookContext<T>, hookHints?: HookHints) {
107+
before(hookContext: HookContext, hookHints?: HookHints) {
116108
return this.maybeSkipAndCache(
117109
'before',
118110
() => this.cacheKeySupplier?.(hookContext.flagKey, hookContext.context),
119111
() => this.innerHook?.before?.(hookContext, hookHints),
120112
);
121113
}
122114

123-
after(hookContext: HookContext<T>, evaluationDetails: EvaluationDetails<T>, hookHints?: HookHints) {
115+
after(hookContext: HookContext, evaluationDetails: EvaluationDetails<FlagValue>, hookHints?: HookHints) {
124116
return this.maybeSkipAndCache(
125117
'after',
126118
() => this.cacheKeySupplier?.(hookContext.flagKey, hookContext.context),
127119
() => this.innerHook?.after?.(hookContext, evaluationDetails, hookHints),
128120
);
129121
}
130122

131-
error(hookContext: HookContext<T>, err: unknown, hookHints?: HookHints) {
123+
error(hookContext: HookContext, err: unknown, hookHints?: HookHints) {
132124
return this.maybeSkipAndCache(
133125
'error',
134126
() => this.cacheKeySupplier?.(hookContext.flagKey, hookContext.context),
135127
() => this.innerHook?.error?.(hookContext, err, hookHints),
136128
);
137129
}
138130

139-
finally(hookContext: HookContext<T>, evaluationDetails: EvaluationDetails<T>, hookHints?: HookHints) {
131+
finally(hookContext: HookContext, evaluationDetails: EvaluationDetails<FlagValue>, hookHints?: HookHints) {
140132
return this.maybeSkipAndCache(
141133
'finally',
142134
() => this.cacheKeySupplier?.(hookContext.flagKey, hookContext.context),
@@ -150,10 +142,10 @@ export class DebounceHook<T extends FlagValue = FlagValue>
150142
hookCallback: () => T,
151143
): T | void {
152144
// the cache key is a concatenation of the result of calling keyGenCallback and the stage
153-
let dynamicKey: string | null | undefined;
145+
let cacheKey: string | null | undefined;
154146

155147
try {
156-
dynamicKey = keyGenCallback();
148+
cacheKey = keyGenCallback();
157149
} catch (e) {
158150
// if the keyGenCallback throws, we log and run the hook stage
159151
this.options.logger?.error(
@@ -163,11 +155,10 @@ export class DebounceHook<T extends FlagValue = FlagValue>
163155
}
164156

165157
// if the keyGenCallback returns nothing, we don't do any caching
166-
if (!dynamicKey) {
158+
if (!cacheKey) {
167159
return hookCallback.call(this.innerHook);
168160
}
169161

170-
const cacheKey = `${dynamicKey}::cache-key`;
171162
const got = this.cache.get(cacheKey);
172163

173164
if (got) {
@@ -222,6 +213,7 @@ export class DebounceHook<T extends FlagValue = FlagValue>
222213
cached: HookStagesEntry | undefined,
223214
maybeContext: EvaluationContext | void,
224215
): void {
216+
// cache the context if we have one, otherwise just a true to indicate we ran this stage
225217
this.cache.set(key, { ...cached, [stage]: maybeContext || true });
226218
}
227219

0 commit comments

Comments
 (0)