flagsmith-nodejs 1.0.9 → 1.1.2

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 (75) hide show
  1. package/.idea/flagsmith-nodejs-client.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/.prettierignore +1 -0
  5. package/.prettierrc.js +9 -0
  6. package/CONTRIBUTING.md +5 -4
  7. package/{LICENCE.md → LICENCE} +5 -6
  8. package/README.md +1 -1
  9. package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
  10. package/build/flagsmith-engine/environments/integrations/models.js +8 -0
  11. package/build/flagsmith-engine/environments/models.d.ts +25 -0
  12. package/build/flagsmith-engine/environments/models.js +40 -0
  13. package/build/flagsmith-engine/environments/util.d.ts +3 -0
  14. package/build/flagsmith-engine/environments/util.js +19 -0
  15. package/build/flagsmith-engine/features/constants.d.ts +4 -0
  16. package/build/flagsmith-engine/features/constants.js +7 -0
  17. package/build/flagsmith-engine/features/models.d.ts +32 -0
  18. package/build/flagsmith-engine/features/models.js +85 -0
  19. package/build/flagsmith-engine/features/util.d.ts +3 -0
  20. package/build/flagsmith-engine/features/util.js +20 -0
  21. package/build/flagsmith-engine/identities/models.d.ts +15 -0
  22. package/build/flagsmith-engine/identities/models.js +47 -0
  23. package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
  24. package/build/flagsmith-engine/identities/traits/models.js +12 -0
  25. package/build/flagsmith-engine/identities/util.d.ts +4 -0
  26. package/build/flagsmith-engine/identities/util.js +22 -0
  27. package/build/flagsmith-engine/index.d.ts +8 -0
  28. package/build/flagsmith-engine/index.js +61 -0
  29. package/build/flagsmith-engine/organisations/models.d.ts +9 -0
  30. package/build/flagsmith-engine/organisations/models.js +21 -0
  31. package/build/flagsmith-engine/organisations/util.d.ts +2 -0
  32. package/build/flagsmith-engine/organisations/util.js +8 -0
  33. package/build/flagsmith-engine/projects/models.d.ts +10 -0
  34. package/build/flagsmith-engine/projects/models.js +17 -0
  35. package/build/flagsmith-engine/projects/util.d.ts +2 -0
  36. package/build/flagsmith-engine/projects/util.js +15 -0
  37. package/build/flagsmith-engine/segments/constants.d.ts +26 -0
  38. package/build/flagsmith-engine/segments/constants.js +31 -0
  39. package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
  40. package/build/flagsmith-engine/segments/evaluators.js +29 -0
  41. package/build/flagsmith-engine/segments/models.d.ts +31 -0
  42. package/build/flagsmith-engine/segments/models.js +83 -0
  43. package/build/flagsmith-engine/segments/util.d.ts +4 -0
  44. package/build/flagsmith-engine/segments/util.js +23 -0
  45. package/build/flagsmith-engine/utils/collections.d.ts +4 -0
  46. package/build/flagsmith-engine/utils/collections.js +16 -0
  47. package/build/flagsmith-engine/utils/errors.d.ts +2 -0
  48. package/build/flagsmith-engine/utils/errors.js +6 -0
  49. package/build/flagsmith-engine/utils/hashing/index.d.ts +9 -0
  50. package/build/flagsmith-engine/utils/hashing/index.js +54 -0
  51. package/build/flagsmith-engine/utils/index.d.ts +1 -0
  52. package/build/flagsmith-engine/utils/index.js +14 -0
  53. package/build/index.d.ts +1 -0
  54. package/build/index.js +11 -0
  55. package/build/sdk/analytics.d.ts +28 -0
  56. package/build/sdk/analytics.js +60 -0
  57. package/build/sdk/errors.d.ts +4 -0
  58. package/build/sdk/errors.js +9 -0
  59. package/build/sdk/index.d.ts +98 -0
  60. package/build/sdk/index.js +215 -0
  61. package/build/sdk/models.d.ts +55 -0
  62. package/build/sdk/models.js +102 -0
  63. package/build/sdk/polling_manager.d.ts +9 -0
  64. package/build/sdk/polling_manager.js +31 -0
  65. package/build/sdk/utils.d.ts +12 -0
  66. package/build/sdk/utils.js +45 -0
  67. package/example/README.md +10 -10
  68. package/example/package-lock.json +12 -12
  69. package/example/package.json +1 -1
  70. package/example/server/api/index.js +16 -17
  71. package/example/server/index.js +3 -6
  72. package/flagsmith-core.js +189 -184
  73. package/index.d.ts +28 -21
  74. package/index.js +2 -4
  75. package/package.json +7 -1
package/flagsmith-core.js CHANGED
@@ -1,238 +1,243 @@
1
- let fetch;
2
-
3
- const FlagsmithCore = class {
4
-
5
- constructor(props) {
6
- fetch = props.fetch;
7
-
8
- this.checkFeatureEnabled = this.checkFeatureEnabled.bind(this);
9
- this.getFlags = this.getFlags.bind(this);
10
- this.getFlagsForUser = this.getFlagsForUser.bind(this);
11
- this.getUserIdentity = this.getUserIdentity.bind(this);
12
- this.getValue = this.getValue.bind(this);
13
- this.getValueFromFeatures = this.getValueFromFeatures.bind(this);
14
- this.hasFeature = this.hasFeature.bind(this);
15
- this.init = this.init.bind(this);
16
-
17
- this.getJSON = function (url, method, body) {
18
- const { environmentID } = this;
19
- const options = {
20
- method: method || 'GET',
21
- body,
22
- headers: {
23
- 'x-environment-key': environmentID
24
- }
1
+ let fetch = require('node-fetch');
2
+ // https://github.com/node-fetch/node-fetch/issues/450
3
+ if (typeof fetch.default !== "undefined") fetch = fetch.default
4
+
5
+ module.exports = class FlagsmithCore {
6
+ normalizeFlags(flags) {
7
+ const _flags = {};
8
+
9
+ for (const { feature, enabled, feature_state_value } of flags) {
10
+ const normalizedKey = feature.name.toLowerCase().replace(/ /g, '_');
11
+ _flags[normalizedKey] = {
12
+ enabled,
13
+ value: feature_state_value
25
14
  };
26
- if (method !== "GET") {
27
- options.headers['Content-Type'] = 'application/json; charset=utf-8';
28
- }
29
- return fetch(url, options)
30
- .then(res => {
31
- return res.json()
32
- .then(result => {
33
- if (res.status < 200 || res.status >= 400) {
34
- Promise.reject(new Error(result.detail))
35
- } else return result;
36
- })
37
- });
38
- };
15
+ }
16
+
17
+ return _flags;
39
18
  }
40
19
 
41
- getFlagsForUser (identity) {
42
- const { onError, api } = this;
20
+ normalizeTraits(traits) {
21
+ const _traits = {};
43
22
 
44
- if (!identity) {
45
- onError && onError({message: 'getFlagsForUser() called without a user identity'});
46
- return Promise.reject('getFlagsForUser() called without a user identity');
47
- }
48
-
49
- const handleResponse = (res) => {
50
- // Handle server response
51
- let flags = {};
52
- res.flags.forEach(feature => {
53
- flags[feature.feature.name.toLowerCase().replace(/ /g, '_')] = {
54
- enabled: feature.enabled,
55
- value: feature.feature_state_value
56
- };
57
- });
58
- return flags;
59
- };
23
+ for (const { trait_key, trait_value } of traits) {
24
+ const normalizedKey = trait_key.toLowerCase().replace(/ /g, '_');
25
+ _traits[normalizedKey] = trait_value;
26
+ }
60
27
 
61
- return this.getJSON(api + 'identities/?identifier=' + identity)
62
- .then(res => {
63
- return handleResponse(res);
64
- }).catch(({ message }) => {
65
- onError && onError({ message });
66
- return Promise.reject(message);
67
- });
28
+ return _traits;
68
29
  }
69
30
 
70
- getUserIdentity (identity) {
71
- const { onError, api } = this;
72
-
73
- if (!identity) {
74
- onError && onError({message: 'getUserIdentity() called without a user identity'});
75
- return Promise.reject('getUserIdentity() called without a user identity');
76
- }
77
-
78
- const handleResponse = (res) => {
79
- // Handle server response
80
- let flags = {};
81
- let traits = {};
82
- res.flags.forEach(feature => {
83
- flags[feature.feature.name.toLowerCase().replace(/ /g, '_')] = {
84
- enabled: feature.enabled,
85
- value: feature.feature_state_value
86
- };
87
- });
88
- res.traits.forEach(({trait_key, trait_value}) => {
89
- traits[trait_key.toLowerCase().replace(/ /g, '_')] = trait_value;
90
- });
91
- return { flags, traits };
31
+ async getJSON(url, method, body) {
32
+ const { environmentID } = this;
33
+ const options = {
34
+ method: method || 'GET',
35
+ body,
36
+ headers: {
37
+ 'x-environment-key': environmentID
38
+ }
92
39
  };
93
40
 
94
- return this.getJSON(api + 'identities/?identifier=' + identity)
95
- .then(res => {
96
- return handleResponse(res);
97
- }).catch(({ message }) => {
98
- onError && onError({ message });
99
- return Promise.reject(message);
100
- });
101
- }
41
+ if (method !== 'GET') {
42
+ options.headers['Content-Type'] = 'application/json; charset=utf-8';
43
+ }
102
44
 
103
- getFlags() {
104
- const { onError, api } = this;
45
+ const res = await fetch(url, options);
46
+ const result = await res.json();
105
47
 
106
- const handleResponse = (res) => {
107
- // Handle server response
108
- let flags = {};
109
- res.forEach(feature => {
110
- flags[feature.feature.name.toLowerCase().replace(/ /g, '_')] = {
111
- enabled: feature.enabled,
112
- value: feature.feature_state_value
113
- };
114
- });
115
- return flags;
116
- };
48
+ if (res.status >= 400) {
49
+ throw new Error(result.detail);
50
+ }
117
51
 
118
- return this.getJSON(api + "flags/")
119
- .then(res => {
120
- return handleResponse(res);
121
- }).catch(({ message }) => {
122
- onError && onError({ message });
123
- return Promise.reject(message);
124
- });
125
- };
126
-
127
- init({
128
- environmentID,
129
- api,
130
- disableCache,
131
- onError,
132
- }) {
52
+ return result;
53
+ }
54
+
55
+ init({ environmentID, api, onError, cache }) {
133
56
  if (!environmentID) {
134
57
  throw new Error('Please specify a environment id');
135
58
  }
136
59
 
137
60
  this.environmentID = environmentID;
138
61
 
139
- this.api = api || "https://api.bullet-train.io/api/v1/";
140
- this.disableCache = disableCache;
62
+ this.api = api || 'https://api.flagsmith.com/api/v1';
141
63
  this.onError = onError;
142
- }
143
64
 
144
- getValue (key, userId) {
145
- if (userId) {
146
- return this.getFlagsForUser(userId).then((flags) => {
147
- return this.getValueFromFeatures(key, flags);
148
- })
149
- } else {
150
- return this.getFlags().then((flags) => {
151
- return this.getValueFromFeatures(key, flags);
65
+ if (cache) {
66
+ const missingMethods = [];
67
+
68
+ ['has', 'get', 'set'].forEach(method => {
69
+ if (!cache[method]) missingMethods.push(method);
152
70
  });
71
+
72
+ if (missingMethods.length > 0) {
73
+ throw new Error(
74
+ `Please implement the following methods in your cache: ${missingMethods.join(
75
+ ', '
76
+ )}`
77
+ );
78
+ }
153
79
  }
80
+
81
+ this.cache = cache;
154
82
  }
155
83
 
156
- hasFeature (key, userId) {
157
- if (userId) {
158
- return this.getFlagsForUser(userId).then((flags) => {
159
- return this.checkFeatureEnabled(key, flags);
160
- })
161
- } else {
162
- return this.getFlags().then((flags) => {
163
- return this.checkFeatureEnabled(key, flags);
164
- });
84
+ async getFlags() {
85
+ if (this.cache && (await this.cache.has('flags'))) {
86
+ return this.cache.get('flags');
87
+ }
88
+
89
+ const { onError, api } = this;
90
+
91
+ try {
92
+ const flags = await this.getJSON(`${api}/flags/`);
93
+ const normalizedFlags = this.normalizeFlags(flags);
94
+
95
+ if (this.cache) await this.cache.set('flags', normalizedFlags);
96
+
97
+ return normalizedFlags;
98
+ } catch (err) {
99
+ onError && onError({ message: err.message });
100
+ throw err;
165
101
  }
166
102
  }
167
103
 
168
- getValueFromFeatures (key, flags) {
169
- if (!flags) {
170
- return null;
104
+ async getFlagsForUser(identity) {
105
+ const cacheKey = `flags-${identity}`;
106
+
107
+ if (this.cache && (await this.cache.has(cacheKey))) {
108
+ return this.cache.get(cacheKey);
171
109
  }
172
- const flag = flags[key];
173
- let res = null;
174
- if (flag) {
175
- res = flag.value;
110
+
111
+ const { onError, api } = this;
112
+
113
+ if (!identity) {
114
+ const errMsg = 'getFlagsForUser() called without a user identity';
115
+ onError && onError({ message: errMsg });
116
+ throw new Error(errMsg);
176
117
  }
177
- //todo record check for value
178
118
 
179
- return res;
119
+ try {
120
+ const { flags } = await this.getJSON(`${api}/identities/?identifier=${identity}`);
121
+ const normalizedFlags = this.normalizeFlags(flags);
122
+
123
+ if (this.cache) await this.cache.set(cacheKey, normalizedFlags);
124
+
125
+ return normalizedFlags;
126
+ } catch (err) {
127
+ onError && onError({ message: err.message });
128
+ throw err;
129
+ }
180
130
  }
181
131
 
182
- checkFeatureEnabled (key, flags) {
183
- if (!flags) {
184
- return false;
132
+ async getUserIdentity(identity) {
133
+ const cacheKey = `flags_traits-${identity}`;
134
+
135
+ if (this.cache && (await this.cache.has(cacheKey))) {
136
+ return this.cache.get(cacheKey);
185
137
  }
186
- const flag = flags[key];
187
- let res = false;
188
- if (flag && flag.enabled) {
189
- res = true;
138
+
139
+ const { onError, api } = this;
140
+
141
+ if (!identity) {
142
+ const errMsg = 'getUserIdentity() called without a user identity';
143
+ onError && onError({ message: errMsg });
144
+ throw new Error(errMsg);
145
+ }
146
+
147
+ try {
148
+ const { flags, traits } = await this.getJSON(
149
+ `${api}/identities/?identifier=${identity}`
150
+ );
151
+
152
+ const normalizedFlags = this.normalizeFlags(flags);
153
+ const normalizedTraits = this.normalizeTraits(traits);
154
+ const res = { flags: normalizedFlags, traits: normalizedTraits };
155
+
156
+ if (this.cache) await this.cache.set(cacheKey, res);
157
+
158
+ return res;
159
+ } catch (err) {
160
+ onError && onError({ message: err.message });
161
+ throw err;
190
162
  }
163
+ }
164
+
165
+ async getValue(key, userId) {
166
+ const flags = userId ? await this.getFlagsForUser(userId) : await this.getFlags();
167
+
168
+ return this.getValueFromFeatures(key, flags);
169
+ }
170
+
171
+ async hasFeature(key, userId) {
172
+ const flags = userId ? await this.getFlagsForUser(userId) : await this.getFlags();
191
173
 
192
- return res;
174
+ return this.checkFeatureEnabled(key, flags);
193
175
  }
194
176
 
195
- getTrait (identity, key) {
177
+ getValueFromFeatures(key, flags) {
178
+ if (!flags) return null;
179
+
180
+ const flag = flags[key];
181
+
182
+ //todo record check for value
183
+ return flag ? flag.value : null;
184
+ }
185
+
186
+ checkFeatureEnabled(key, flags) {
187
+ if (!flags) return false;
188
+
189
+ const flag = flags[key];
190
+ return flag && flag.enabled;
191
+ }
192
+
193
+ async getTrait(identity, key) {
196
194
  const { onError } = this;
197
195
 
198
196
  if (!identity || !key) {
199
- onError && onError({message: `getTrait() called without a ${!identity ? 'user identity' : 'trait key'}`});
200
- return Promise.reject(`getTrait() called without a ${!identity ? 'user identity' : 'trait key'}`);
197
+ const errMsg = `getTrait() called without a ${
198
+ !identity ? 'user identity' : 'trait key'
199
+ }`;
200
+ onError && onError({ message: errMsg });
201
+ throw new Error(errMsg);
201
202
  }
202
203
 
203
- return this.getUserIdentity(identity)
204
- .then(({traits}) => traits[key])
205
- .catch(({ message }) => {
206
- onError && onError({ message });
207
- return Promise.reject(message);
208
- });
204
+ try {
205
+ const { traits } = await this.getUserIdentity(identity);
206
+ return traits[key];
207
+ } catch (err) {
208
+ onError && onError({ message: err.message });
209
+ throw err;
210
+ }
209
211
  }
210
212
 
211
- setTrait (identity, key, value) {
213
+ async setTrait(identity, key, value) {
212
214
  const { onError, api } = this;
213
215
 
214
216
  if (!identity || !key) {
215
- onError && onError({message: `setTrait() called without a ${!identity ? 'user identity' : 'trait key'}`});
216
- return Promise.reject(`setTrait() called without a ${!identity ? 'user identity' : 'trait key'}`);
217
+ const errMsg = `setTrait() called without a ${
218
+ !identity ? 'user identity' : 'trait key'
219
+ }`;
220
+ onError &&
221
+ onError({
222
+ message: errMsg
223
+ });
224
+ throw new Error(errMsg);
217
225
  }
218
226
 
219
227
  const body = {
220
- "identity": {
221
- "identifier": identity
228
+ identity: {
229
+ identifier: identity
222
230
  },
223
- "trait_key": key,
224
- "trait_value": value
225
- }
231
+ trait_key: key,
232
+ trait_value: value
233
+ };
226
234
 
227
- return this.getJSON(`${api}traits/`, 'POST', JSON.stringify(body))
228
- .then(() => this.getUserIdentity(identity))
229
- .catch(({ message }) => {
230
- onError && onError({ message });
231
- return Promise.reject(message);
232
- });
235
+ try {
236
+ await this.getJSON(`${api}/traits/`, 'POST', JSON.stringify(body));
237
+ return await this.getUserIdentity(identity);
238
+ } catch (err) {
239
+ onError && onError({ message: err.message });
240
+ throw err;
241
+ }
233
242
  }
234
243
  };
235
-
236
- module.exports = function ({ fetch }) {
237
- return new FlagsmithCore({ fetch });
238
- };
package/index.d.ts CHANGED
@@ -3,51 +3,52 @@ declare module 'flagsmith-nodejs' {
3
3
  * Initialise the sdk against a particular environment
4
4
  */
5
5
  export function init(config: {
6
- environmentID: string
7
- onError?: Function
8
- defaultFlags?: string[]
9
- api?: string
10
- }): void
6
+ environmentID: string;
7
+ onError?: Function;
8
+ defaultFlags?: string[];
9
+ api?: string;
10
+ cache?: ICache;
11
+ }): void;
11
12
 
12
13
  /**
13
14
  * Get the whether a flag is enabled e.g. flagsmith.hasFeature("powerUserFeature")
14
15
  */
15
- export function hasFeature(key: string): Promise<boolean>
16
+ export function hasFeature(key: string): Promise<boolean>;
16
17
 
17
18
  /**
18
19
  * Get the value of a whether a flag is enabled for a user e.g. flagsmith.hasFeature("powerUserFeature", 1234)
19
20
  */
20
- export function hasFeature(key: string, userId: string): Promise<boolean>
21
+ export function hasFeature(key: string, userId: string): Promise<boolean>;
21
22
 
22
23
  /**
23
24
  * Get the value of a particular remote config e.g. flagsmith.getValue("font_size")
24
25
  */
25
- export function getValue(key: string): Promise<string|number|boolean>
26
+ export function getValue(key: string): Promise<string | number | boolean>;
26
27
 
27
28
  /**
28
29
  * Get the value of a particular remote config for a specified user e.g. flagsmith.getValue("font_size", 1234)
29
30
  */
30
- export function getValue(key: string, userId: string): Promise<string|number|boolean>
31
+ export function getValue(key: string, userId: string): Promise<string | number | boolean>;
31
32
 
32
33
  /**
33
34
  * Trigger a manual fetch of the environment features
34
35
  */
35
- export function getFlags(): Promise<IFlags>
36
+ export function getFlags(): Promise<IFlags>;
36
37
 
37
38
  /**
38
39
  * Trigger a manual fetch of the environment features for a given user id
39
40
  */
40
- export function getFlagsForUser(userId: string): Promise<IFlags>
41
+ export function getFlagsForUser(userId: string): Promise<IFlags>;
41
42
 
42
43
  /**
43
44
  * Trigger a manual fetch of both the environment features and users' traits for a given user id
44
45
  */
45
- export function getUserIdentity(userId: string): Promise<IUserIdentity>
46
+ export function getUserIdentity(userId: string): Promise<IUserIdentity>;
46
47
 
47
48
  /**
48
49
  * Trigger a manual fetch of a specific trait for a given user id
49
50
  */
50
- export function getTrait(userId: string, key: string): Promise<ITraits>
51
+ export function getTrait(userId: string, key: string): Promise<ITraits>;
51
52
 
52
53
  /**
53
54
  * Set a specific trait for a given user id
@@ -55,24 +56,30 @@ declare module 'flagsmith-nodejs' {
55
56
  export function setTrait(
56
57
  userId: string,
57
58
  key: string,
58
- value: string|number|boolean
59
- ): IUserIdentity
59
+ value: string | number | boolean
60
+ ): Promise<IUserIdentity>;
60
61
 
61
62
  interface IFeature {
62
- enabled: boolean
63
- value?: string|number|boolean
63
+ enabled: boolean;
64
+ value?: string | number | boolean;
64
65
  }
65
66
 
66
67
  interface IFlags {
67
- [key: string]: IFeature
68
+ [key: string]: IFeature;
68
69
  }
69
70
 
70
71
  interface ITraits {
71
- [key: string]: string
72
+ [key: string]: string;
72
73
  }
73
74
 
74
75
  interface IUserIdentity {
75
- flags: IFeature
76
- traits: ITraits
76
+ flags: IFeature;
77
+ traits: ITraits;
78
+ }
79
+
80
+ interface ICache {
81
+ has(key: string): boolean | Promise<boolean>;
82
+ get(key: string): any | Promise<any>;
83
+ set(key: string, val: any): void | Promise<void>;
77
84
  }
78
85
  }
package/index.js CHANGED
@@ -1,5 +1,3 @@
1
- const fetch = require('node-fetch').default;
2
- const core = require('./flagsmith-core');
3
- const flagsmith = core({fetch: fetch});
1
+ const FlagsmithCore = require('./flagsmith-core');
4
2
 
5
- module.exports = flagsmith;
3
+ module.exports = new FlagsmithCore();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagsmith-nodejs",
3
- "version": "1.0.9",
3
+ "version": "1.1.2",
4
4
  "description": "Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -39,7 +39,13 @@
39
39
  }
40
40
  ],
41
41
  "license": "MIT",
42
+ "scripts": {
43
+ "lint": "prettier --write ."
44
+ },
42
45
  "dependencies": {
43
46
  "node-fetch": "^2.1.2"
47
+ },
48
+ "devDependencies": {
49
+ "prettier": "^2.2.1"
44
50
  }
45
51
  }