feature-toggle-api 3.4.1 → 4.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 (38) hide show
  1. package/README.md +115 -96
  2. package/dist/feature-toggle.d.ts +120 -0
  3. package/dist/feature-toggle.js +388 -0
  4. package/dist/feature-toggle.min.cjs +2 -0
  5. package/dist/feature-toggle.min.cjs.map +1 -0
  6. package/dist/feature-toggle.min.js +2 -0
  7. package/dist/feature-toggle.min.js.map +1 -0
  8. package/dist/feature-toggle.umd.min.js +2 -0
  9. package/dist/feature-toggle.umd.min.js.map +1 -0
  10. package/dist/html-plugin.umd.min.js +2 -0
  11. package/dist/html-plugin.umd.min.js.map +1 -0
  12. package/dist/url-plugin.umd.min.js +2 -0
  13. package/dist/url-plugin.umd.min.js.map +1 -0
  14. package/examples/01_basic_esmodule.js +10 -0
  15. package/examples/02_basic_cjsmodule.cjs +6 -0
  16. package/examples/{basic.html → 03_basic_scripttag.html} +3 -2
  17. package/examples/{example-htmlplugin.html → 04_example-htmlplugin.html} +2 -2
  18. package/examples/{example-urlplugin.html → 05_example-urlplugin.html} +2 -2
  19. package/examples/{withListener.html → 06_withListener.html} +1 -1
  20. package/package.json +32 -38
  21. package/plugin-html.js +1 -1
  22. package/plugin-url.js +1 -1
  23. package/rollup.config.js +107 -0
  24. package/src/{index.js → featureToggle.ts} +172 -51
  25. package/src/index.ts +10 -0
  26. package/src/plugins/htmlplugin/{plugin-html.js → plugin-html.ts} +22 -10
  27. package/src/plugins/urlplugin/{plugin-url.js → plugin-url.ts} +36 -10
  28. package/tests/featuretoggle.test.ts +407 -0
  29. package/tests/polyfills.ts +20 -0
  30. package/tsconfig.cjs.json +12 -0
  31. package/tsconfig.json +12 -0
  32. package/feature-toggle-api.js +0 -273
  33. package/feature-toggle-api.min.js +0 -1
  34. package/feature-toggle-api.module.js +0 -273
  35. package/spec/support/index.spec.js +0 -378
  36. package/spec/support/jasmine.json +0 -11
  37. package/spec/support/polyfills.js +0 -6
  38. package/src/index.cjs.js +0 -8
@@ -1,31 +1,131 @@
1
+ interface OnConfiguration {
2
+ ignorePreviousRules: boolean
3
+ }
4
+ type Plugin = (api) => void;
5
+
6
+ interface OnEvent {
7
+ name: string,
8
+ variant: string,
9
+ data: any,
10
+ result?: boolean
11
+ }
12
+
13
+ type FirstCharOfFeatureFlagKey = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' |
14
+ 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z';
15
+
16
+ type FeatureFlagKey = `${FirstCharOfFeatureFlagKey}${string}`;
17
+
18
+ interface FeatureToggleConfig {
19
+ [key: FeatureFlagKey]: boolean | (() => boolean),
20
+ $plugins?: Plugin[]
21
+ /**
22
+ * @deprecated Use key`$plugins` instead.
23
+ */
24
+ _plugins?: Plugin[]
25
+ }
26
+
27
+ interface Rule {
28
+ name: string,
29
+ variant: string,
30
+ data: any,
31
+ _internalCall?: true,
32
+ description?: string
33
+ }
1
34
 
2
- const parseToFn = function (fnOrBool) {
35
+ interface FeatureToggleApi {
36
+ name: string,
37
+ setData(name: string, dataParam?: any): void;
38
+ setData(name: string, variant: string, dataParam?: any): void,
39
+ setData(nameParam: string, variantOrDataParam: string | { [key: string]: any },
40
+ dataParam?: any): void;
41
+
42
+ on(eventType: string, fn: (event: OnEvent) => void, config?: OnConfiguration): void;
43
+ trigger(eventtype: string, param?: any);
44
+ showLogs(showLogs?: boolean): void
45
+
46
+ /**
47
+ * @deprecated Use `featureToggle.isActive` instead.
48
+ */
49
+ isVisible(name: string, variant?: string, data?: any): boolean
50
+
51
+ isActive(name: string, variant?: string, data?: any): boolean
52
+
53
+ /**
54
+ * @deprecated Use `featureToggle.setFlag` instead.
55
+ */
56
+ visibility(name: string, result: boolean | ((rule: Rule) => boolean)): void,
57
+ /**
58
+ * @deprecated Use `featureToggle.setFlag` instead.
59
+ */
60
+ visibility(name: string, variant: string | null, result: boolean | ((rule: Rule) => boolean)): void
61
+ /**
62
+ * @deprecated Use `featureToggle.setFlag` instead.
63
+ */
64
+ visibility(name: string, variant: string | null, data: any, result: boolean | ((rule: Rule) => boolean)): void,
65
+ /**
66
+ * @deprecated Use `featureToggle.setFlag` instead.
67
+ */
68
+ visibility(name: string, resultOrVariant: string | null | boolean | ((rule: Rule) => boolean), resultOrData?: any, result?: boolean | (() => boolean)): void
69
+
70
+ setFlag(name: string, result: boolean | ((rule: Rule) => boolean)): void,
71
+ setFlag(name: string, variant: string | null, result: boolean | ((rule: Rule) => boolean)): void,
72
+ setFlag(name: string, variant: string | null, data: any, result: boolean | ((rule: Rule) => boolean)): void,
73
+ setFlag(name: string, resultOrVariant: string | null | boolean | ((rule: Rule) => boolean), resultOrData?: any, result?: boolean | (() => boolean)): void
74
+
75
+ /**
76
+ * @deprecated Use `featureToggle.setRequiredFlag` instead.
77
+ */
78
+ requiredVisibility(fn: boolean | ((result: Rule) => boolean)): void
79
+
80
+ /**
81
+ * @deprecated Use `featureToggle.setDefaultFlag` instead.
82
+ */
83
+ defaultVisibility(fn: boolean | ((result: Rule) => boolean)): void
84
+
85
+ /**
86
+ * This rule will run first and only if it is true, the feature.setFlag() - rules apply.
87
+ * In other words: if the required-rule returns false, all feature.setFlag-rules return false - regardless of its normal result.
88
+ * @param fn
89
+ */
90
+ setRequiredFlag(fn: boolean | ((result: Rule) => boolean)): void
91
+
92
+ /**
93
+ * This is the default-rule and will be overwritten by feature.setFlag() - rules.
94
+ * In other words: If feature.setFlag == false, the result of the defaultRule applies.
95
+ * @param fn DefaultRule
96
+ */
97
+ setDefaultFlag(fn: boolean | ((result: Rule) => boolean)): void
98
+
99
+ addPlugin(plugin: Plugin)
100
+ }
101
+
102
+ function parseToFn(fnOrBool: boolean | ((param?: any) => boolean)) {
3
103
  if (typeof fnOrBool == 'boolean')
4
104
  return function () { return fnOrBool };
5
105
 
6
106
  return fnOrBool;
7
107
  }
8
108
 
9
- const getKey = function (name, variant) {
109
+ function getKey(name: string, variant?: string): string {
10
110
  var _name = name.toLowerCase();
11
- if (typeof variant == 'string') {
111
+ if (variant && typeof variant == 'string') {
12
112
  _name += "#" + variant.toLowerCase();
13
113
  }
14
114
 
15
115
  return _name;
16
116
  }
17
117
 
18
- function initVisibilities(visibilities = {}) {
118
+ function initVisibilities(visibilities: FeatureToggleConfig = {}) {
19
119
  const returnVisibilities = {};
20
120
  Object.keys(visibilities).forEach(key => {
21
- if(key.startsWith('_'))
121
+ if (key.startsWith('_') || key.startsWith('$'))
22
122
  return;
23
123
  returnVisibilities[getKey(key)] = parseToFn(visibilities[key]);
24
124
  });
25
125
  return returnVisibilities;
26
126
  }
27
127
 
28
- export default function featuretoggleapi(config={}) {
128
+ function useFeatureToggle(config: FeatureToggleConfig = {}): FeatureToggleApi {
29
129
 
30
130
  const globals = {
31
131
  datas: {},
@@ -35,29 +135,26 @@ export default function featuretoggleapi(config={}) {
35
135
  usedPlugins: [],
36
136
  }
37
137
 
38
- function init(api){
39
- if(config._plugins)
40
- {
41
- if(!Array.isArray(config._plugins))
42
- throw new Error('featuretoggleapi()-constructor: config.plugins must be an array.');
43
-
44
- config._plugins.forEach(plugin =>{
45
- if(typeof plugin !== 'function')
138
+ function init(api) {
139
+ const allPlugins = [...(config.$plugins||[]),...(config._plugins||[])];
140
+ if(config._plugins){
141
+ console.log('useFeatureToggle({_plugins:[]}): Key _plugins is deprecated. Use $plugins instead. This attribute will be removed in one of the next major versions.');
142
+ }
143
+
144
+
145
+ if (allPlugins.length) {
146
+ allPlugins.forEach(plugin => {
147
+ if (typeof plugin !== 'function')
46
148
  throw new Error('featuretoggleapi()-constructor: config.plugins needs functions as entries, not ' + typeof plugin + '.');
47
-
48
- addPlugin(plugin,api);
149
+
150
+ plugin(api);
49
151
  });
50
152
  }
51
153
 
52
154
  triggerEvent('init');
53
155
  }
54
156
 
55
- function addPlugin(plugin,api)
56
- {
57
- plugin(api);
58
- }
59
-
60
- function triggerEvent(eventtype,param) {
157
+ function triggerEvent(eventtype: string, param?: any) {
61
158
  (globals.listeners[eventtype] || []).forEach(listener => {
62
159
  listener(param);
63
160
  });
@@ -114,7 +211,7 @@ export default function featuretoggleapi(config={}) {
114
211
  return logAndReturn(false, `The ${functionname} returns ${calculatedVisibility}. => Please return true or false. This result (and all non-boolean results) will return false.`);
115
212
  }
116
213
 
117
- function parseKey(key) {
214
+ function parseKey(key: string): OnEvent {
118
215
  const parts = key.split('#');
119
216
  return {
120
217
  name: parts[0],
@@ -165,11 +262,11 @@ export default function featuretoggleapi(config={}) {
165
262
  }
166
263
  }
167
264
 
168
- function getEvent(name, variant, data, result) {
265
+ function getEvent(name: string, variant: string, data?, result?: any) {
169
266
 
170
267
  let event;
171
268
 
172
- event = { name: name, variant: variant, data: data };
269
+ event = { name, variant, data };
173
270
 
174
271
  event.key = getKey(event.name, event.variant);
175
272
 
@@ -177,19 +274,19 @@ export default function featuretoggleapi(config={}) {
177
274
  return event;
178
275
 
179
276
  event.visibilityFunction = parseToFn(result);
180
- event.result = event.visibilityFunction({
181
- name: event.name,
182
- variant: event.variant,
183
- data: event.data || {},
277
+ event.result = event.visibilityFunction({
278
+ name: event.name,
279
+ variant: event.variant,
280
+ data: event.data || {},
184
281
  _internalCall: true,
185
- description: 'When attaching a function, the result must be calculated internally. You can filter this out with the _internalCall:true -Flag.'
282
+ description: 'When attaching a function, the result must be calculated internally. You can filter this out with the _internalCall:true -Flag.'
186
283
  })
187
284
  return event;
188
285
  }
189
286
 
190
287
 
191
288
 
192
- function isVisible(name, variant, data) {
289
+ function isActive(name: string, variant?: string, data?: any): boolean {
193
290
  const visibilities = globals.visibilities;
194
291
 
195
292
  log(`\nCheck Visibility of <b>Feature "${name}", variant "${variant == undefined ? '' : variant}"${data ? " with data " + JSON.stringify(data) : ""}.`);
@@ -242,77 +339,101 @@ export default function featuretoggleapi(config={}) {
242
339
  return logAndReturn(false, 'No rules were found. This feature will be hidden.');
243
340
  }
244
341
 
245
- const api = {
342
+ const api: FeatureToggleApi = {
246
343
  name: 'feature-toggle-api',
247
- setData: function (nameParam, variantOrDataParam, dataParam) {
344
+ setData: function (nameParam, variantOrDataParam, dataParam?): void {
248
345
  if (nameParam == undefined)
249
346
  throw new Error('setData(): The name must of the feature must be defined, but ist undefined');
250
347
 
251
- const variant = dataParam != undefined ? variantOrDataParam : undefined;
348
+ const variant = (dataParam != undefined ? variantOrDataParam : undefined) as string;
252
349
  const data = dataParam || variantOrDataParam;
253
350
 
254
351
  const event = getEvent(nameParam, variant, data);
255
352
 
256
353
  globals.datas[event.key] = event.data;
257
354
 
258
- triggerEvent('visibilityrule',event);
355
+ triggerEvent('visibilityrule', event);
259
356
  },
260
- on: function (eventtype, fn, config) {
357
+ on: function (eventtype: string, fn, config?) {
261
358
  globals.listeners[eventtype] = globals.listeners[eventtype] || [];
262
359
  globals.listeners[eventtype].push(fn);
263
360
 
264
- triggerEvent('registerEvent',{
361
+ triggerEvent('registerEvent', {
265
362
  type: eventtype
266
363
  })
267
364
  if (config != undefined && config.ignorePreviousRules)
268
365
  return;
269
366
 
367
+
270
368
  Object.keys(globals.visibilities).forEach(key => {
271
- const event = parseKey(key, globals);
369
+ const event = parseKey(key);
272
370
  const rule = globals.visibilities[key];
273
371
  event.result = rule(event);
274
372
  fn(event);
275
373
  });
276
374
  },
277
375
  trigger: triggerEvent,
278
- showLogs: function (showLogs) {
376
+ showLogs: function (showLogs?: boolean): void {
279
377
  globals.showLogs = showLogs == undefined ? true : showLogs;
280
378
  },
281
- isVisible,
282
- /*
379
+ isVisible(name,variant,data){
380
+ console.log('featureToggle.isVisible is deprecated. use featureToggle.isActive instead. This function will be removed in one of the next major versions.');
381
+ return isActive(name,variant,data);
382
+ },
383
+ isActive,
384
+ /**
283
385
  the following function calls are possible:
284
386
  visibility(name,result);
285
387
  visibility(name,variant,result);
286
388
  visibility(name,variant,data,result);
287
- */
288
- visibility: function (param1, param2, param3, param4) {
289
- const params = visibilityFnParams(param1, param2, param3, param4);
389
+ */
390
+ setFlag(name, resultOrVariant, resultOrData?, result?) {
391
+ const params = visibilityFnParams(name, resultOrVariant, resultOrData, result);
290
392
  const event = getEvent(params.name, params.variant, params.data, params.result);
291
393
 
292
394
  globals.visibilities[event.key] = event.visibilityFunction;
293
395
  globals.datas[event.key] = event.data;
294
- triggerEvent('visibilityrule',event);
396
+ triggerEvent('visibilityrule', event);
397
+ },
398
+ visibility: function (name, resultOrVariant, resultOrData?, result?) {
399
+ console.log('featureToggle.visibility is deprecated. use featureToggle.setVisibility instead. This function will be removed in one of the next major versions.');
400
+
401
+ api.setFlag(name, resultOrVariant, resultOrData, result);
295
402
  },
296
403
  requiredVisibility: function (fn) {
404
+ console.log('featureToggle.requiredVisibility is deprecated. use featureToggle.setRequiredFlag instead. This function will be removed in one of the next major versions.');
405
+
406
+ api.setRequiredFlag(fn);
407
+ },
408
+ defaultVisibility: function (fn) {
409
+ console.log('featureToggle.requiredVisibility is deprecated. use featureToggle.setRequiredFlag instead. This function will be removed in one of the next major versions.');
410
+
411
+ api.setDefaultFlag(fn);
412
+ },
413
+ setRequiredFlag(fn) {
297
414
  if (typeof fn != "function")
298
- throw new Error('feature.requiredVisibility(): 1st parameter must be a function, but is ' + typeof fn);
415
+ throw new Error('feature.setRequiredFlag(): 1st parameter must be a function, but is ' + typeof fn);
299
416
 
300
417
  globals.visibilities['_required'] = parseToFn(fn);
301
418
  },
302
- defaultVisibility: function (fn) {
419
+ setDefaultFlag(fn) {
303
420
  if (typeof fn != "function")
304
421
  throw new Error('feature.defaultVisibility(): 1st parameter must be a function, but is ' + typeof fn);
305
422
 
306
423
  globals.visibilities['_default'] = parseToFn(fn);
307
424
  },
308
- addPlugin: function(plugin){
309
- if(globals.usedPlugins.includes(plugin))
425
+ addPlugin: function (plugin) {
426
+ if (globals.usedPlugins.includes(plugin))
310
427
  return;
311
-
312
- addPlugin(plugin,api);
428
+
429
+ plugin(api);
430
+
313
431
  globals.usedPlugins.push(plugin);
314
432
  },
315
433
  };
316
434
  init(api);
435
+
317
436
  return api;
318
437
  }
438
+
439
+ export default useFeatureToggle;
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { urlPlugin } from "./plugins/urlplugin/plugin-url"
2
+ import { htmlPlugin } from "./plugins/htmlplugin/plugin-html"
3
+ import useFeatureToggle from "./featureToggle"
4
+
5
+
6
+ export {
7
+ useFeatureToggle,
8
+ urlPlugin,
9
+ htmlPlugin
10
+ }
@@ -1,3 +1,16 @@
1
+ type Display = 'block' | 'inline-block' | 'inline' | 'flex' | 'inline-flex' | 'grid' | 'inline-grid';
2
+
3
+ interface HtmlPluginConfig {
4
+ renderedTag?: string,
5
+ featureTagName?: string,
6
+ tagAttributeName?: string,
7
+ nameAttributeName?:string,
8
+ variantAttributeName?: string,
9
+ dataAttributeName?: string,
10
+ displayAttributeName?: string,
11
+ defaultDisplay?: Display,
12
+ }
13
+
1
14
  /*
2
15
  creates a tag that is shown / hidden, depending on the visibility rules.
3
16
  if not configured dynamically, it looks like this:
@@ -8,7 +21,7 @@
8
21
  - nameAttributeName: Name of the Name-Attribute: default: "name"
9
22
  - variantAttributeName: Name of the Variant-Attribute: default: "variant"
10
23
  */
11
- const defaultparams = {
24
+ const defaultparams :HtmlPluginConfig = {
12
25
  renderedTag: 'div',
13
26
  featureTagName: 'feature',
14
27
  tagAttributeName: 'tag',
@@ -31,10 +44,10 @@ function parseDataAttribute(attrAsString){
31
44
  }
32
45
  }
33
46
 
34
- function htmlplugin(config = {}) {
47
+ function htmlPlugin(config :HtmlPluginConfig = {}) {
35
48
  config = Object.assign({},defaultparams,config);
36
49
 
37
- function renderFeatureTag(elem,isVisible){
50
+ function renderFeatureTag(elem:HTMLElement,isVisible){
38
51
  const tagname = elem.getAttribute(config.tagAttributeName) || config.renderedTag;
39
52
  const attributes = Array.from(elem.attributes);
40
53
  let attributesAsString = "";
@@ -43,15 +56,15 @@ function htmlplugin(config = {}) {
43
56
  });
44
57
  const display = isVisible ? (elem.getAttribute(config.displayAttributeName) || config.defaultDisplay) : 'none';
45
58
  elem.outerHTML = `<${tagname} style="display:${display}" _feature="true" ${attributesAsString}>${elem.innerHTML}</${tagname}>`;
46
- }
59
+ }
47
60
  return function (api) {
48
- var renderedTags = window.document.querySelectorAll(config.featureTagName);
61
+ var renderedTags :NodeListOf<HTMLElement> = window.document.querySelectorAll(config.featureTagName);
49
62
  renderedTags.forEach(tag => { renderFeatureTag(tag,false) });
50
63
 
51
64
  api.on('visibilityrule', function (event) {
52
65
  var selector = `[_feature][${config.nameAttributeName}="${event.name}"]`;
53
66
  if (event.variant) selector += `[${config.variantAttributeName}="${event.variant}"]`;
54
- var elements = document.querySelectorAll(selector);
67
+ var elements :NodeListOf<HTMLElement> = document.querySelectorAll(selector);
55
68
  elements.forEach(elem => {
56
69
  const dataAsString = elem.getAttribute(config.dataAttributeName);
57
70
  const data = parseDataAttribute(dataAsString);
@@ -64,8 +77,7 @@ function htmlplugin(config = {}) {
64
77
  return { name: 'htmlplugin' };
65
78
  }
66
79
  }
67
- if(typeof window !== 'undefined')
68
- window.htmlplugin = htmlplugin;
69
80
 
70
- if(typeof module !== 'undefined')
71
- module.exports = htmlplugin;
81
+ export {
82
+ htmlPlugin
83
+ }
@@ -1,3 +1,27 @@
1
+ interface URLPluginConfig{
2
+ useMockedWindow?: boolean,
3
+ url?: string,
4
+ prefix?: string
5
+
6
+ }
7
+
8
+ function useWindowMock(){
9
+ return {
10
+ isMocked: true,
11
+ decodeURIComponent:function(param1){
12
+ return param1;
13
+ }
14
+ }
15
+ }
16
+
17
+ declare global {
18
+ interface Window {
19
+ isMocked: boolean
20
+ }
21
+ }
22
+
23
+ declare var window: Window;
24
+
1
25
  function parseValue(value){
2
26
  if(value === 'true')
3
27
  return true;
@@ -28,12 +52,16 @@ function getParams(url,window) {
28
52
  return params;
29
53
  }
30
54
 
31
- function urlplugin(config={}) {
32
- if(config.window)
33
- window = config.window;
55
+ function urlPlugin(config: URLPluginConfig={}) {
56
+ let _window;
57
+
58
+ if(config.useMockedWindow)
59
+ _window = useWindowMock();
60
+ else
61
+ _window = window;
34
62
 
35
63
  config = Object.assign({},{
36
- url: window.isMocked ? "" : window.location.href,
64
+ url: _window.isMocked ? "" : _window.location.href,
37
65
  prefix: ""
38
66
  },config);
39
67
 
@@ -41,7 +69,7 @@ function getParams(url,window) {
41
69
 
42
70
  return function(api){
43
71
  api.url = config.url;
44
- const urlparams = getParams(config.url,window);
72
+ const urlparams = getParams(config.url,_window);
45
73
  const prefix = config.prefix;
46
74
 
47
75
  Object.keys(urlparams).forEach(key => {
@@ -57,8 +85,6 @@ function getParams(url,window) {
57
85
 
58
86
  }
59
87
 
60
- if(typeof window !== 'undefined')
61
- window.urlplugin = urlplugin;
62
-
63
- if(typeof module !== 'undefined')
64
- module.exports = urlplugin;
88
+ export {
89
+ urlPlugin
90
+ }