@featurevisor/sdk 1.35.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +2 -381
  2. package/coverage/clover.xml +707 -645
  3. package/coverage/coverage-final.json +11 -9
  4. package/coverage/lcov-report/{segments.ts.html → bucketer.ts.html} +155 -77
  5. package/coverage/lcov-report/child.ts.html +940 -0
  6. package/coverage/lcov-report/conditions.ts.html +107 -158
  7. package/coverage/lcov-report/datafileReader.ts.html +763 -103
  8. package/coverage/lcov-report/emitter.ts.html +77 -59
  9. package/coverage/lcov-report/evaluate.ts.html +689 -416
  10. package/coverage/lcov-report/events.ts.html +334 -0
  11. package/coverage/lcov-report/helpers.ts.html +184 -0
  12. package/coverage/lcov-report/{bucket.ts.html → hooks.ts.html} +86 -239
  13. package/coverage/lcov-report/index.html +119 -89
  14. package/coverage/lcov-report/instance.ts.html +341 -773
  15. package/coverage/lcov-report/logger.ts.html +64 -64
  16. package/coverage/lcov.info +1433 -1226
  17. package/dist/bucketer.d.ts +11 -0
  18. package/dist/child.d.ts +26 -0
  19. package/dist/compareVersions.d.ts +4 -0
  20. package/dist/conditions.d.ts +4 -4
  21. package/dist/datafileReader.d.ts +26 -6
  22. package/dist/emitter.d.ts +8 -9
  23. package/dist/evaluate.d.ts +31 -29
  24. package/dist/events.d.ts +5 -0
  25. package/dist/helpers.d.ts +5 -0
  26. package/dist/hooks.d.ts +45 -0
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +1 -1
  31. package/dist/index.mjs.gz +0 -0
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/instance.d.ts +40 -72
  34. package/dist/logger.d.ts +6 -5
  35. package/dist/murmurhash.d.ts +1 -0
  36. package/jest.config.js +2 -0
  37. package/lib/bucketer.d.ts +11 -0
  38. package/lib/child.d.ts +26 -0
  39. package/lib/compareVersions.d.ts +4 -0
  40. package/lib/conditions.d.ts +4 -4
  41. package/lib/datafileReader.d.ts +26 -6
  42. package/lib/emitter.d.ts +8 -9
  43. package/lib/evaluate.d.ts +31 -29
  44. package/lib/events.d.ts +5 -0
  45. package/lib/helpers.d.ts +5 -0
  46. package/lib/hooks.d.ts +45 -0
  47. package/lib/index.d.ts +3 -2
  48. package/lib/instance.d.ts +40 -72
  49. package/lib/logger.d.ts +6 -5
  50. package/lib/murmurhash.d.ts +1 -0
  51. package/package.json +3 -5
  52. package/src/bucketer.spec.ts +165 -0
  53. package/src/bucketer.ts +84 -0
  54. package/src/child.spec.ts +267 -0
  55. package/src/child.ts +285 -0
  56. package/src/compareVersions.ts +93 -0
  57. package/src/conditions.spec.ts +563 -353
  58. package/src/conditions.ts +46 -63
  59. package/src/datafileReader.spec.ts +396 -84
  60. package/src/datafileReader.ts +280 -60
  61. package/src/emitter.spec.ts +27 -86
  62. package/src/emitter.ts +38 -32
  63. package/src/evaluate.ts +349 -258
  64. package/src/events.spec.ts +154 -0
  65. package/src/events.ts +83 -0
  66. package/src/helpers.ts +33 -0
  67. package/src/hooks.ts +88 -0
  68. package/src/index.ts +3 -2
  69. package/src/instance.spec.ts +305 -489
  70. package/src/instance.ts +247 -391
  71. package/src/logger.spec.ts +212 -134
  72. package/src/logger.ts +36 -36
  73. package/src/murmurhash.ts +71 -0
  74. package/coverage/lcov-report/feature.ts.html +0 -508
  75. package/dist/bucket.d.ts +0 -30
  76. package/dist/feature.d.ts +0 -16
  77. package/dist/segments.d.ts +0 -5
  78. package/lib/bucket.d.ts +0 -30
  79. package/lib/feature.d.ts +0 -16
  80. package/lib/segments.d.ts +0 -5
  81. package/src/bucket.spec.ts +0 -37
  82. package/src/bucket.ts +0 -139
  83. package/src/feature.ts +0 -141
  84. package/src/segments.spec.ts +0 -468
  85. package/src/segments.ts +0 -58
@@ -3,7 +3,7 @@
3
3
  <html lang="en">
4
4
 
5
5
  <head>
6
- <title>Code coverage report for bucket.ts</title>
6
+ <title>Code coverage report for hooks.ts</title>
7
7
  <meta charset="utf-8" />
8
8
  <link rel="stylesheet" href="prettify.css" />
9
9
  <link rel="stylesheet" href="base.css" />
@@ -19,34 +19,34 @@
19
19
  <body>
20
20
  <div class='wrapper'>
21
21
  <div class='pad1'>
22
- <h1><a href="index.html">All files</a> bucket.ts</h1>
22
+ <h1><a href="index.html">All files</a> hooks.ts</h1>
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">95.45% </span>
26
+ <span class="strong">62.5% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>42/44</span>
28
+ <span class='fraction'>10/16</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">88.23% </span>
33
+ <span class="strong">50% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>15/17</span>
35
+ <span class='fraction'>1/2</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">100% </span>
40
+ <span class="strong">50% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>4/4</span>
42
+ <span class='fraction'>4/8</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">95.45% </span>
47
+ <span class="strong">71.42% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>42/44</span>
49
+ <span class='fraction'>10/14</span>
50
50
  </div>
51
51
 
52
52
 
@@ -61,7 +61,7 @@
61
61
  </div>
62
62
  </template>
63
63
  </div>
64
- <div class='status-line high'></div>
64
+ <div class='status-line medium'></div>
65
65
  <pre><table class="coverage">
66
66
  <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
67
  <a name='L2'></a><a href='#L2'>2</a>
@@ -151,58 +151,7 @@
151
151
  <a name='L86'></a><a href='#L86'>86</a>
152
152
  <a name='L87'></a><a href='#L87'>87</a>
153
153
  <a name='L88'></a><a href='#L88'>88</a>
154
- <a name='L89'></a><a href='#L89'>89</a>
155
- <a name='L90'></a><a href='#L90'>90</a>
156
- <a name='L91'></a><a href='#L91'>91</a>
157
- <a name='L92'></a><a href='#L92'>92</a>
158
- <a name='L93'></a><a href='#L93'>93</a>
159
- <a name='L94'></a><a href='#L94'>94</a>
160
- <a name='L95'></a><a href='#L95'>95</a>
161
- <a name='L96'></a><a href='#L96'>96</a>
162
- <a name='L97'></a><a href='#L97'>97</a>
163
- <a name='L98'></a><a href='#L98'>98</a>
164
- <a name='L99'></a><a href='#L99'>99</a>
165
- <a name='L100'></a><a href='#L100'>100</a>
166
- <a name='L101'></a><a href='#L101'>101</a>
167
- <a name='L102'></a><a href='#L102'>102</a>
168
- <a name='L103'></a><a href='#L103'>103</a>
169
- <a name='L104'></a><a href='#L104'>104</a>
170
- <a name='L105'></a><a href='#L105'>105</a>
171
- <a name='L106'></a><a href='#L106'>106</a>
172
- <a name='L107'></a><a href='#L107'>107</a>
173
- <a name='L108'></a><a href='#L108'>108</a>
174
- <a name='L109'></a><a href='#L109'>109</a>
175
- <a name='L110'></a><a href='#L110'>110</a>
176
- <a name='L111'></a><a href='#L111'>111</a>
177
- <a name='L112'></a><a href='#L112'>112</a>
178
- <a name='L113'></a><a href='#L113'>113</a>
179
- <a name='L114'></a><a href='#L114'>114</a>
180
- <a name='L115'></a><a href='#L115'>115</a>
181
- <a name='L116'></a><a href='#L116'>116</a>
182
- <a name='L117'></a><a href='#L117'>117</a>
183
- <a name='L118'></a><a href='#L118'>118</a>
184
- <a name='L119'></a><a href='#L119'>119</a>
185
- <a name='L120'></a><a href='#L120'>120</a>
186
- <a name='L121'></a><a href='#L121'>121</a>
187
- <a name='L122'></a><a href='#L122'>122</a>
188
- <a name='L123'></a><a href='#L123'>123</a>
189
- <a name='L124'></a><a href='#L124'>124</a>
190
- <a name='L125'></a><a href='#L125'>125</a>
191
- <a name='L126'></a><a href='#L126'>126</a>
192
- <a name='L127'></a><a href='#L127'>127</a>
193
- <a name='L128'></a><a href='#L128'>128</a>
194
- <a name='L129'></a><a href='#L129'>129</a>
195
- <a name='L130'></a><a href='#L130'>130</a>
196
- <a name='L131'></a><a href='#L131'>131</a>
197
- <a name='L132'></a><a href='#L132'>132</a>
198
- <a name='L133'></a><a href='#L133'>133</a>
199
- <a name='L134'></a><a href='#L134'>134</a>
200
- <a name='L135'></a><a href='#L135'>135</a>
201
- <a name='L136'></a><a href='#L136'>136</a>
202
- <a name='L137'></a><a href='#L137'>137</a>
203
- <a name='L138'></a><a href='#L138'>138</a>
204
- <a name='L139'></a><a href='#L139'>139</a>
205
- <a name='L140'></a><a href='#L140'>140</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">2x</span>
154
+ <a name='L89'></a><a href='#L89'>89</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
206
155
  <span class="cline-any cline-neutral">&nbsp;</span>
207
156
  <span class="cline-any cline-neutral">&nbsp;</span>
208
157
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -210,18 +159,6 @@
210
159
  <span class="cline-any cline-neutral">&nbsp;</span>
211
160
  <span class="cline-any cline-neutral">&nbsp;</span>
212
161
  <span class="cline-any cline-neutral">&nbsp;</span>
213
- <span class="cline-any cline-yes">2x</span>
214
- <span class="cline-any cline-yes">2x</span>
215
- <span class="cline-any cline-neutral">&nbsp;</span>
216
- <span class="cline-any cline-yes">2x</span>
217
- <span class="cline-any cline-neutral">&nbsp;</span>
218
- <span class="cline-any cline-yes">2x</span>
219
- <span class="cline-any cline-yes">105x</span>
220
- <span class="cline-any cline-yes">105x</span>
221
- <span class="cline-any cline-neutral">&nbsp;</span>
222
- <span class="cline-any cline-yes">105x</span>
223
- <span class="cline-any cline-neutral">&nbsp;</span>
224
- <span class="cline-any cline-neutral">&nbsp;</span>
225
162
  <span class="cline-any cline-neutral">&nbsp;</span>
226
163
  <span class="cline-any cline-neutral">&nbsp;</span>
227
164
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -239,57 +176,12 @@
239
176
  <span class="cline-any cline-neutral">&nbsp;</span>
240
177
  <span class="cline-any cline-neutral">&nbsp;</span>
241
178
  <span class="cline-any cline-neutral">&nbsp;</span>
242
- <span class="cline-any cline-yes">2x</span>
243
- <span class="cline-any cline-yes">95x</span>
244
- <span class="cline-any cline-neutral">&nbsp;</span>
245
- <span class="cline-any cline-yes">95x</span>
246
- <span class="cline-any cline-neutral">&nbsp;</span>
247
179
  <span class="cline-any cline-neutral">&nbsp;</span>
248
180
  <span class="cline-any cline-neutral">&nbsp;</span>
249
181
  <span class="cline-any cline-neutral">&nbsp;</span>
250
- <span class="cline-any cline-yes">95x</span>
251
- <span class="cline-any cline-yes">88x</span>
252
- <span class="cline-any cline-yes">88x</span>
253
- <span class="cline-any cline-yes">7x</span>
254
- <span class="cline-any cline-yes">2x</span>
255
- <span class="cline-any cline-yes">2x</span>
256
- <span class="cline-any cline-yes">5x</span>
257
- <span class="cline-any cline-yes">5x</span>
258
- <span class="cline-any cline-yes">5x</span>
259
- <span class="cline-any cline-neutral">&nbsp;</span>
260
- <span class="cline-any cline-no">&nbsp;</span>
261
- <span class="cline-any cline-neutral">&nbsp;</span>
262
- <span class="cline-any cline-no">&nbsp;</span>
263
182
  <span class="cline-any cline-neutral">&nbsp;</span>
264
183
  <span class="cline-any cline-neutral">&nbsp;</span>
265
- <span class="cline-any cline-yes">95x</span>
266
184
  <span class="cline-any cline-neutral">&nbsp;</span>
267
- <span class="cline-any cline-yes">95x</span>
268
- <span class="cline-any cline-yes">102x</span>
269
- <span class="cline-any cline-neutral">&nbsp;</span>
270
- <span class="cline-any cline-yes">102x</span>
271
- <span class="cline-any cline-yes">13x</span>
272
- <span class="cline-any cline-neutral">&nbsp;</span>
273
- <span class="cline-any cline-neutral">&nbsp;</span>
274
- <span class="cline-any cline-yes">89x</span>
275
- <span class="cline-any cline-yes">81x</span>
276
- <span class="cline-any cline-neutral">&nbsp;</span>
277
- <span class="cline-any cline-neutral">&nbsp;</span>
278
- <span class="cline-any cline-yes">8x</span>
279
- <span class="cline-any cline-yes">5x</span>
280
- <span class="cline-any cline-neutral">&nbsp;</span>
281
- <span class="cline-any cline-neutral">&nbsp;</span>
282
- <span class="cline-any cline-neutral">&nbsp;</span>
283
- <span class="cline-any cline-neutral">&nbsp;</span>
284
- <span class="cline-any cline-yes">95x</span>
285
- <span class="cline-any cline-neutral">&nbsp;</span>
286
- <span class="cline-any cline-yes">95x</span>
287
- <span class="cline-any cline-neutral">&nbsp;</span>
288
- <span class="cline-any cline-yes">95x</span>
289
- <span class="cline-any cline-yes">10x</span>
290
- <span class="cline-any cline-neutral">&nbsp;</span>
291
- <span class="cline-any cline-neutral">&nbsp;</span>
292
- <span class="cline-any cline-yes">85x</span>
293
185
  <span class="cline-any cline-neutral">&nbsp;</span>
294
186
  <span class="cline-any cline-neutral">&nbsp;</span>
295
187
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -308,177 +200,132 @@
308
200
  <span class="cline-any cline-neutral">&nbsp;</span>
309
201
  <span class="cline-any cline-neutral">&nbsp;</span>
310
202
  <span class="cline-any cline-neutral">&nbsp;</span>
203
+ <span class="cline-any cline-yes">2x</span>
204
+ <span class="cline-any cline-yes">19x</span>
311
205
  <span class="cline-any cline-neutral">&nbsp;</span>
312
206
  <span class="cline-any cline-neutral">&nbsp;</span>
313
207
  <span class="cline-any cline-neutral">&nbsp;</span>
208
+ <span class="cline-any cline-yes">19x</span>
314
209
  <span class="cline-any cline-neutral">&nbsp;</span>
210
+ <span class="cline-any cline-yes">19x</span>
211
+ <span class="cline-any cline-yes">19x</span>
212
+ <span class="cline-any cline-yes">6x</span>
315
213
  <span class="cline-any cline-neutral">&nbsp;</span>
316
214
  <span class="cline-any cline-neutral">&nbsp;</span>
317
215
  <span class="cline-any cline-neutral">&nbsp;</span>
318
- <span class="cline-any cline-yes">2x</span>
319
216
  <span class="cline-any cline-neutral">&nbsp;</span>
320
- <span class="cline-any cline-yes">95x</span>
321
- <span class="cline-any cline-yes">95x</span>
322
217
  <span class="cline-any cline-neutral">&nbsp;</span>
218
+ <span class="cline-any cline-yes">6x</span>
219
+ <span class="cline-any cline-no">&nbsp;</span>
323
220
  <span class="cline-any cline-neutral">&nbsp;</span>
324
221
  <span class="cline-any cline-neutral">&nbsp;</span>
325
222
  <span class="cline-any cline-neutral">&nbsp;</span>
326
223
  <span class="cline-any cline-neutral">&nbsp;</span>
224
+ <span class="cline-any cline-no">&nbsp;</span>
327
225
  <span class="cline-any cline-neutral">&nbsp;</span>
328
- <span class="cline-any cline-yes">95x</span>
329
226
  <span class="cline-any cline-neutral">&nbsp;</span>
330
- <span class="cline-any cline-yes">95x</span>
331
- <span class="cline-any cline-yes">2x</span>
227
+ <span class="cline-any cline-yes">6x</span>
332
228
  <span class="cline-any cline-neutral">&nbsp;</span>
333
- <span class="cline-any cline-yes">2x</span>
229
+ <span class="cline-any cline-yes">6x</span>
230
+ <span class="cline-any cline-no">&nbsp;</span>
334
231
  <span class="cline-any cline-neutral">&nbsp;</span>
335
232
  <span class="cline-any cline-neutral">&nbsp;</span>
336
233
  <span class="cline-any cline-neutral">&nbsp;</span>
337
234
  <span class="cline-any cline-neutral">&nbsp;</span>
235
+ <span class="cline-any cline-no">&nbsp;</span>
338
236
  <span class="cline-any cline-neutral">&nbsp;</span>
339
- <span class="cline-any cline-yes">93x</span>
340
237
  <span class="cline-any cline-neutral">&nbsp;</span>
341
238
  <span class="cline-any cline-neutral">&nbsp;</span>
239
+ <span class="cline-any cline-yes">415x</span>
342
240
  <span class="cline-any cline-neutral">&nbsp;</span>
343
241
  <span class="cline-any cline-neutral">&nbsp;</span>
344
- <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import * as murmurhash from "murmurhash";
345
- import { Feature, BucketKey, BucketValue, Context, AttributeValue } from "@featurevisor/types";
242
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type { BucketBy, BucketKey, BucketValue, Context, FeatureKey } from "@featurevisor/types";
346
243
  &nbsp;
347
- import { Logger } from "./logger";
244
+ import type { EvaluateOptions, Evaluation } from "./evaluate";
245
+ import type { Logger } from "./logger";
348
246
  &nbsp;
349
247
  /**
350
- * Generic hashing
248
+ * bucketKey
351
249
  */
352
- const HASH_SEED = 1;
353
- const MAX_HASH_VALUE = Math.pow(2, 32);
354
- &nbsp;
355
- export const MAX_BUCKETED_NUMBER = 100000; // 100% * 1000 to include three decimal places in the same integer value
356
- &nbsp;
357
- export function getBucketedNumber(bucketKey: string): number {
358
- const hashValue = murmurhash.v3(bucketKey, HASH_SEED);
359
- const ratio = hashValue / MAX_HASH_VALUE;
360
- &nbsp;
361
- return Math.floor(ratio * MAX_BUCKETED_NUMBER);
250
+ export interface ConfigureBucketKeyOptions {
251
+ featureKey: FeatureKey;
252
+ context: Context;
253
+ bucketBy: BucketBy;
254
+ bucketKey: string; // the initial bucket key, which can be modified by hooks
362
255
  }
363
256
  &nbsp;
257
+ export type ConfigureBucketKey = (options: ConfigureBucketKeyOptions) =&gt; BucketKey;
258
+ &nbsp;
364
259
  /**
365
- * Feature specific bucketing
260
+ * bucketValue
366
261
  */
367
- export type ConfigureBucketKey = (
368
- feature: Feature,
369
- context: Context,
370
- bucketKey: BucketKey,
371
- ) =&gt; BucketKey;
372
- &nbsp;
373
- export interface BucketKeyOptions {
374
- feature: Feature;
262
+ export interface ConfigureBucketValueOptions {
263
+ featureKey: FeatureKey;
264
+ bucketKey: string;
375
265
  context: Context;
376
- logger: Logger;
377
- bucketKeySeparator?: string;
378
- configureBucketKey?: ConfigureBucketKey;
266
+ bucketValue: number; // the initial bucket value, which can be modified by hooks
379
267
  }
380
268
  &nbsp;
381
- export function getBucketKey(options: BucketKeyOptions): BucketKey {
382
- const { feature, context, logger, bucketKeySeparator = <span class="branch-0 cbranch-no" title="branch not covered" >".",</span> configureBucketKey } = options;
383
- &nbsp;
384
- const featureKey = feature.key;
385
- &nbsp;
386
- let type;
387
- let attributeKeys;
388
- &nbsp;
389
- if (typeof feature.bucketBy === "string") {
390
- type = "plain";
391
- attributeKeys = [feature.bucketBy];
392
- } else if (Array.isArray(feature.bucketBy)) {
393
- type = "and";
394
- attributeKeys = feature.bucketBy;
395
- } else if (typeof feature.bucketBy === "object" &amp;&amp; Array.isArray(feature.bucketBy.or)) {
396
- type = "or";
397
- attributeKeys = feature.bucketBy.or;
398
- } else <span class="missing-if-branch" title="else path not taken" >E</span>{
399
- <span class="cstat-no" title="statement not covered" > logger.error("invalid bucketBy", { featureKey, bucketBy: feature.bucketBy });</span>
400
- &nbsp;
401
- <span class="cstat-no" title="statement not covered" > throw new Error("invalid bucketBy");</span>
402
- }
403
- &nbsp;
404
- const bucketKey: AttributeValue[] = [];
405
- &nbsp;
406
- attributeKeys.forEach((attributeKey) =&gt; {
407
- const attributeValue = context[attributeKey];
269
+ export type ConfigureBucketValue = (options: ConfigureBucketValueOptions) =&gt; BucketValue;
408
270
  &nbsp;
409
- if (typeof attributeValue === "undefined") {
410
- return;
411
- }
271
+ /**
272
+ * Hooks
273
+ */
274
+ export interface Hook {
275
+ name: string;
412
276
  &nbsp;
413
- if (type === "plain" || type === "and") {
414
- bucketKey.push(attributeValue);
415
- } else {
416
- // or
417
- if (bucketKey.length === 0) {
418
- bucketKey.push(attributeValue);
419
- }
420
- }
421
- });
277
+ before?: (options: EvaluateOptions) =&gt; EvaluateOptions;
422
278
  &nbsp;
423
- bucketKey.push(featureKey);
279
+ bucketKey?: ConfigureBucketKey;
424
280
  &nbsp;
425
- const result = bucketKey.join(bucketKeySeparator);
281
+ bucketValue?: ConfigureBucketValue;
426
282
  &nbsp;
427
- if (configureBucketKey) {
428
- return configureBucketKey(feature, context, result);
429
- }
430
- &nbsp;
431
- return result;
283
+ after?: (evaluation: Evaluation, options: EvaluateOptions) =&gt; Evaluation;
432
284
  }
433
285
  &nbsp;
434
- export interface Bucket {
435
- bucketKey: BucketKey;
436
- bucketValue: BucketValue;
286
+ export interface HooksManagerOptions {
287
+ hooks?: Hook[];
288
+ logger: Logger;
437
289
  }
438
290
  &nbsp;
439
- export type ConfigureBucketValue = (
440
- feature: Feature,
441
- context: Context,
442
- bucketValue: BucketValue,
443
- ) =&gt; BucketValue;
291
+ export class HooksManager {
292
+ private hooks: Hook[] = [];
293
+ private logger: Logger;
444
294
  &nbsp;
445
- export interface BucketValueOptions {
446
- // common with BucketKeyOptions
447
- feature: Feature;
448
- context: Context;
449
- logger: Logger;
450
- bucketKeySeparator?: string;
451
- configureBucketKey?: ConfigureBucketKey;
295
+ constructor(options: HooksManagerOptions) {
296
+ this.logger = options.logger;
452
297
  &nbsp;
453
- // specific to BucketValueOptions
454
- configureBucketValue?: ConfigureBucketValue;
455
- }
298
+ if (options.hooks) {
299
+ options.hooks.forEach((hook) =&gt; {
300
+ this.add(hook);
301
+ });
302
+ }
303
+ }
304
+ &nbsp;
305
+ add(hook: Hook): (() =&gt; void) | undefined {
306
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (this.hooks.some(<span class="fstat-no" title="function not covered" >(e</span>xistingHook) =&gt; <span class="cstat-no" title="statement not covered" >existingHook.name === hook.name)</span>) {
307
+ <span class="cstat-no" title="statement not covered" > this.logger.error(`Hook with name "${hook.name}" already exists.`, {</span>
308
+ name: hook.name,
309
+ hook: hook,
310
+ });
456
311
  &nbsp;
457
- export function getBucket(options: BucketValueOptions): Bucket {
458
- const { feature, context, logger, bucketKeySeparator, configureBucketKey, configureBucketValue } =
459
- options;
460
- const bucketKey = getBucketKey({
461
- feature,
462
- context,
463
- logger,
464
- bucketKeySeparator,
465
- configureBucketKey,
466
- });
467
- const value = getBucketedNumber(bucketKey);
312
+ <span class="cstat-no" title="statement not covered" > return;</span>
313
+ }
468
314
  &nbsp;
469
- if (configureBucketValue) {
470
- const configuredValue = configureBucketValue(feature, context, value);
315
+ this.hooks.push(hook);
471
316
  &nbsp;
472
- return {
473
- bucketKey,
474
- bucketValue: configuredValue,
317
+ return <span class="fstat-no" title="function not covered" >() =</span>&gt; {
318
+ <span class="cstat-no" title="statement not covered" > this.remove(hook.name);</span>
475
319
  };
476
320
  }
477
321
  &nbsp;
478
- return {
479
- bucketKey,
480
- bucketValue: value,
481
- };
322
+ <span class="fstat-no" title="function not covered" > remove(</span>name: string): void {
323
+ <span class="cstat-no" title="statement not covered" > this.hooks = this.hooks.filter(<span class="fstat-no" title="function not covered" >(h</span>ook) =&gt; <span class="cstat-no" title="statement not covered" >hook.name !== name)</span>;</span>
324
+ }
325
+ &nbsp;
326
+ getAll(): Hook[] {
327
+ return this.hooks;
328
+ }
482
329
  }
483
330
  &nbsp;</pre></td></tr></table></pre>
484
331
 
@@ -487,7 +334,7 @@ export function getBucket(options: BucketValueOptions): Bucket {
487
334
  <div class='footer quiet pad2 space-top1 center small'>
488
335
  Code coverage generated by
489
336
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
490
- at 2025-04-12T20:34:55.662Z
337
+ at 2025-07-13T22:31:55.309Z
491
338
  </div>
492
339
  <script src="prettify.js"></script>
493
340
  <script>