flagsmith-nodejs 3.3.2 → 4.0.0-beta.1

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 (190) hide show
  1. package/.github/workflows/publish.yml +2 -2
  2. package/.github/workflows/pull_request.yaml +3 -4
  3. package/build/{flagsmith-engine → cjs/flagsmith-engine}/environments/models.d.ts +3 -3
  4. package/build/{flagsmith-engine → cjs/flagsmith-engine}/environments/models.js +20 -13
  5. package/build/{flagsmith-engine → cjs/flagsmith-engine}/environments/util.d.ts +1 -1
  6. package/build/cjs/flagsmith-engine/environments/util.js +23 -0
  7. package/build/cjs/flagsmith-engine/features/models.js +118 -0
  8. package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/util.d.ts +1 -1
  9. package/build/cjs/flagsmith-engine/features/util.js +27 -0
  10. package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/models.d.ts +2 -2
  11. package/build/cjs/flagsmith-engine/identities/models.js +48 -0
  12. package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/traits/models.js +5 -4
  13. package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/util.d.ts +2 -2
  14. package/build/cjs/flagsmith-engine/identities/util.js +22 -0
  15. package/build/cjs/flagsmith-engine/index.d.ts +14 -0
  16. package/build/cjs/flagsmith-engine/index.js +75 -0
  17. package/build/cjs/flagsmith-engine/organisations/models.js +21 -0
  18. package/build/{flagsmith-engine → cjs/flagsmith-engine}/organisations/util.d.ts +1 -1
  19. package/build/cjs/flagsmith-engine/organisations/util.js +8 -0
  20. package/build/{flagsmith-engine → cjs/flagsmith-engine}/projects/models.d.ts +2 -2
  21. package/build/{flagsmith-engine → cjs/flagsmith-engine}/projects/models.js +8 -5
  22. package/build/{flagsmith-engine → cjs/flagsmith-engine}/projects/util.d.ts +1 -1
  23. package/build/cjs/flagsmith-engine/projects/util.js +15 -0
  24. package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/evaluators.d.ts +4 -4
  25. package/build/cjs/flagsmith-engine/segments/evaluators.js +37 -0
  26. package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/models.d.ts +1 -1
  27. package/build/cjs/flagsmith-engine/segments/models.js +114 -0
  28. package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/util.d.ts +1 -1
  29. package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/util.js +9 -11
  30. package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/collections.d.ts +1 -1
  31. package/build/cjs/flagsmith-engine/utils/collections.js +6 -0
  32. package/build/cjs/flagsmith-engine/utils/errors.js +6 -0
  33. package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/hashing/index.js +8 -11
  34. package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/index.js +5 -5
  35. package/build/{index.d.ts → cjs/index.d.ts} +3 -3
  36. package/build/{index.js → cjs/index.js} +17 -17
  37. package/build/cjs/package.json +1 -0
  38. package/build/{sdk → cjs/sdk}/analytics.d.ts +3 -0
  39. package/build/cjs/sdk/analytics.js +73 -0
  40. package/build/cjs/sdk/errors.js +9 -0
  41. package/build/{sdk → cjs/sdk}/index.d.ts +19 -18
  42. package/build/cjs/sdk/index.js +400 -0
  43. package/build/{sdk → cjs/sdk}/models.d.ts +2 -2
  44. package/build/cjs/sdk/models.js +101 -0
  45. package/build/{sdk → cjs/sdk}/offline_handlers.d.ts +1 -1
  46. package/build/cjs/sdk/offline_handlers.js +46 -0
  47. package/build/{sdk → cjs/sdk}/polling_manager.d.ts +1 -1
  48. package/build/cjs/sdk/polling_manager.js +29 -0
  49. package/build/{sdk → cjs/sdk}/types.d.ts +15 -7
  50. package/build/cjs/sdk/utils.d.ts +36 -0
  51. package/build/cjs/sdk/utils.js +63 -0
  52. package/build/esm/flagsmith-engine/environments/models.d.ts +22 -0
  53. package/build/esm/flagsmith-engine/environments/models.js +32 -0
  54. package/build/esm/flagsmith-engine/environments/util.d.ts +3 -0
  55. package/build/esm/flagsmith-engine/environments/util.js +18 -0
  56. package/build/esm/flagsmith-engine/features/constants.d.ts +4 -0
  57. package/build/esm/flagsmith-engine/features/constants.js +4 -0
  58. package/build/esm/flagsmith-engine/features/models.d.ts +37 -0
  59. package/build/esm/flagsmith-engine/features/models.js +110 -0
  60. package/build/esm/flagsmith-engine/features/util.d.ts +4 -0
  61. package/build/esm/flagsmith-engine/features/util.js +21 -0
  62. package/build/esm/flagsmith-engine/identities/models.d.ts +15 -0
  63. package/build/esm/flagsmith-engine/identities/models.js +44 -0
  64. package/build/esm/flagsmith-engine/identities/traits/models.d.ts +5 -0
  65. package/build/esm/flagsmith-engine/identities/traits/models.js +8 -0
  66. package/build/esm/flagsmith-engine/identities/util.d.ts +4 -0
  67. package/build/esm/flagsmith-engine/identities/util.js +17 -0
  68. package/build/esm/flagsmith-engine/index.d.ts +14 -0
  69. package/build/esm/flagsmith-engine/index.js +62 -0
  70. package/build/esm/flagsmith-engine/organisations/models.d.ts +9 -0
  71. package/build/esm/flagsmith-engine/organisations/models.js +17 -0
  72. package/build/esm/flagsmith-engine/organisations/util.d.ts +2 -0
  73. package/build/esm/flagsmith-engine/organisations/util.js +4 -0
  74. package/build/esm/flagsmith-engine/projects/models.d.ts +10 -0
  75. package/build/esm/flagsmith-engine/projects/models.js +13 -0
  76. package/build/esm/flagsmith-engine/projects/util.d.ts +2 -0
  77. package/build/esm/flagsmith-engine/projects/util.js +11 -0
  78. package/build/esm/flagsmith-engine/segments/constants.d.ts +34 -0
  79. package/build/esm/flagsmith-engine/segments/constants.js +36 -0
  80. package/build/esm/flagsmith-engine/segments/evaluators.d.ts +7 -0
  81. package/build/esm/flagsmith-engine/segments/evaluators.js +31 -0
  82. package/build/esm/flagsmith-engine/segments/models.d.ts +37 -0
  83. package/build/esm/flagsmith-engine/segments/models.js +102 -0
  84. package/build/esm/flagsmith-engine/segments/util.d.ts +6 -0
  85. package/build/esm/flagsmith-engine/segments/util.js +23 -0
  86. package/build/esm/flagsmith-engine/utils/collections.d.ts +3 -0
  87. package/build/esm/flagsmith-engine/utils/collections.js +2 -0
  88. package/build/esm/flagsmith-engine/utils/errors.d.ts +2 -0
  89. package/build/esm/flagsmith-engine/utils/errors.js +2 -0
  90. package/build/esm/flagsmith-engine/utils/hashing/index.d.ts +9 -0
  91. package/build/esm/flagsmith-engine/utils/hashing/index.js +50 -0
  92. package/build/esm/flagsmith-engine/utils/index.d.ts +1 -0
  93. package/build/esm/flagsmith-engine/utils/index.js +13 -0
  94. package/build/esm/index.d.ts +3 -0
  95. package/build/esm/index.js +4 -0
  96. package/build/esm/sdk/analytics.d.ts +35 -0
  97. package/build/esm/sdk/analytics.js +69 -0
  98. package/build/esm/sdk/errors.d.ts +4 -0
  99. package/build/esm/sdk/errors.js +4 -0
  100. package/build/esm/sdk/index.d.ts +131 -0
  101. package/build/esm/sdk/index.js +390 -0
  102. package/build/esm/sdk/models.d.ts +55 -0
  103. package/build/esm/sdk/models.js +94 -0
  104. package/build/esm/sdk/offline_handlers.d.ts +9 -0
  105. package/build/esm/sdk/offline_handlers.js +18 -0
  106. package/build/esm/sdk/polling_manager.d.ts +9 -0
  107. package/build/esm/sdk/polling_manager.js +25 -0
  108. package/build/esm/sdk/types.d.ts +38 -0
  109. package/build/esm/sdk/types.js +1 -0
  110. package/build/esm/sdk/utils.d.ts +36 -0
  111. package/build/esm/sdk/utils.js +56 -0
  112. package/flagsmith-engine/environments/models.ts +3 -3
  113. package/flagsmith-engine/environments/util.ts +4 -4
  114. package/flagsmith-engine/features/models.ts +1 -1
  115. package/flagsmith-engine/features/util.ts +1 -1
  116. package/flagsmith-engine/identities/models.ts +3 -4
  117. package/flagsmith-engine/identities/traits/models.ts +0 -1
  118. package/flagsmith-engine/identities/util.ts +4 -4
  119. package/flagsmith-engine/index.ts +13 -13
  120. package/flagsmith-engine/organisations/util.ts +1 -1
  121. package/flagsmith-engine/projects/models.ts +2 -2
  122. package/flagsmith-engine/projects/util.ts +4 -4
  123. package/flagsmith-engine/segments/evaluators.ts +6 -6
  124. package/flagsmith-engine/segments/models.ts +4 -4
  125. package/flagsmith-engine/segments/util.ts +3 -3
  126. package/flagsmith-engine/utils/collections.ts +1 -1
  127. package/flagsmith-engine/utils/index.ts +1 -1
  128. package/index.ts +4 -4
  129. package/package.json +21 -9
  130. package/sdk/analytics.ts +7 -5
  131. package/sdk/index.ts +55 -46
  132. package/sdk/models.ts +2 -3
  133. package/sdk/offline_handlers.ts +2 -2
  134. package/sdk/polling_manager.ts +2 -3
  135. package/sdk/types.ts +35 -24
  136. package/sdk/utils.ts +49 -29
  137. package/tests/engine/e2e/engine.test.ts +5 -5
  138. package/tests/engine/unit/engine.test.ts +5 -5
  139. package/tests/engine/unit/segments/segment_evaluators.test.ts +9 -9
  140. package/tests/engine/unit/utils/utils.test.ts +1 -1
  141. package/tests/sdk/analytics.test.ts +8 -13
  142. package/tests/sdk/data/identity-with-transient-traits.json +41 -0
  143. package/tests/sdk/data/transient-identity.json +29 -0
  144. package/tests/sdk/flagsmith-cache.test.ts +16 -32
  145. package/tests/sdk/flagsmith-environment-flags.test.ts +21 -36
  146. package/tests/sdk/flagsmith-identity-flags.test.ts +83 -32
  147. package/tests/sdk/flagsmith.test.ts +67 -99
  148. package/tests/sdk/offline-handlers.test.ts +4 -5
  149. package/tests/sdk/polling.test.ts +6 -8
  150. package/tests/sdk/utils.ts +19 -15
  151. package/tsconfig.cjs.json +7 -0
  152. package/tsconfig.esm.json +7 -0
  153. package/tsconfig.json +7 -3
  154. package/vitest.config.ts +17 -0
  155. package/build/flagsmith-engine/environments/util.js +0 -27
  156. package/build/flagsmith-engine/features/models.js +0 -132
  157. package/build/flagsmith-engine/features/util.js +0 -27
  158. package/build/flagsmith-engine/identities/models.js +0 -113
  159. package/build/flagsmith-engine/identities/util.js +0 -46
  160. package/build/flagsmith-engine/index.d.ts +0 -14
  161. package/build/flagsmith-engine/index.js +0 -127
  162. package/build/flagsmith-engine/organisations/models.js +0 -21
  163. package/build/flagsmith-engine/organisations/util.js +0 -8
  164. package/build/flagsmith-engine/projects/util.js +0 -15
  165. package/build/flagsmith-engine/segments/evaluators.js +0 -45
  166. package/build/flagsmith-engine/segments/models.js +0 -147
  167. package/build/flagsmith-engine/utils/collections.js +0 -26
  168. package/build/flagsmith-engine/utils/errors.js +0 -26
  169. package/build/sdk/analytics.js +0 -120
  170. package/build/sdk/errors.js +0 -34
  171. package/build/sdk/index.js +0 -594
  172. package/build/sdk/models.js +0 -149
  173. package/build/sdk/offline_handlers.js +0 -66
  174. package/build/sdk/polling_manager.js +0 -72
  175. package/build/sdk/utils.d.ts +0 -12
  176. package/build/sdk/utils.js +0 -100
  177. package/jest.config.js +0 -5
  178. package/tests/index.js +0 -0
  179. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/constants.d.ts +0 -0
  180. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/constants.js +0 -0
  181. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/models.d.ts +0 -0
  182. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/traits/models.d.ts +0 -0
  183. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/organisations/models.d.ts +0 -0
  184. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/constants.d.ts +0 -0
  185. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/constants.js +0 -0
  186. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/errors.d.ts +0 -0
  187. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/hashing/index.d.ts +0 -0
  188. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/index.d.ts +0 -0
  189. /package/build/{sdk → cjs/sdk}/errors.d.ts +0 -0
  190. /package/build/{sdk → cjs/sdk}/types.js +0 -0
@@ -0,0 +1,390 @@
1
+ import { getEnvironmentFeatureStates, getIdentityFeatureStates } from '../flagsmith-engine/index.js';
2
+ import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js';
3
+ import { IdentityModel } from '../flagsmith-engine/index.js';
4
+ import { TraitModel } from '../flagsmith-engine/index.js';
5
+ import { AnalyticsProcessor } from './analytics.js';
6
+ import { FlagsmithAPIError } from './errors.js';
7
+ import { Flags } from './models.js';
8
+ import { EnvironmentDataPollingManager } from './polling_manager.js';
9
+ import { generateIdentitiesData, retryFetch } from './utils.js';
10
+ import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js';
11
+ import { pino } from 'pino';
12
+ export { AnalyticsProcessor } from './analytics.js';
13
+ export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
14
+ export { DefaultFlag, Flags } from './models.js';
15
+ export { EnvironmentDataPollingManager } from './polling_manager.js';
16
+ const DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/';
17
+ const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
18
+ export class Flagsmith {
19
+ environmentKey = undefined;
20
+ apiUrl = undefined;
21
+ customHeaders;
22
+ agent;
23
+ requestTimeoutMs;
24
+ enableLocalEvaluation = false;
25
+ environmentRefreshIntervalSeconds = 60;
26
+ retries;
27
+ enableAnalytics = false;
28
+ defaultFlagHandler;
29
+ environmentFlagsUrl;
30
+ identitiesUrl;
31
+ environmentUrl;
32
+ environmentDataPollingManager;
33
+ environment;
34
+ offlineMode = false;
35
+ offlineHandler = undefined;
36
+ identitiesWithOverridesByIdentifier;
37
+ cache;
38
+ onEnvironmentChange;
39
+ analyticsProcessor;
40
+ logger;
41
+ customFetch;
42
+ /**
43
+ * A Flagsmith client.
44
+ *
45
+ * Provides an interface for interacting with the Flagsmith http API.
46
+ * Basic Usage::
47
+ *
48
+ * import flagsmith from Flagsmith
49
+ * const flagsmith = new Flagsmith({environmentKey: '<your API key>'});
50
+ * const environmentFlags = flagsmith.getEnvironmentFlags();
51
+ * const featureEnabled = environmentFlags.isFeatureEnabled('foo');
52
+ * const identityFlags = flagsmith.getIdentityFlags('identifier', {'foo': 'bar'});
53
+ * const featureEnabledForIdentity = identityFlags.isFeatureEnabled("foo")
54
+ *
55
+ * @param {string} data.environmentKey: The environment key obtained from Flagsmith interface
56
+ * Required unless offlineMode is True.
57
+ @param {string} data.apiUrl: Override the URL of the Flagsmith API to communicate with
58
+ @param data.customHeaders: Additional headers to add to requests made to the
59
+ Flagsmith API
60
+ @param {number} data.requestTimeoutSeconds: Number of seconds to wait for a request to
61
+ complete before terminating the request
62
+ @param {boolean} data.enableLocalEvaluation: Enables local evaluation of flags
63
+ @param {number} data.environmentRefreshIntervalSeconds: If using local evaluation,
64
+ specify the interval period between refreshes of local environment data
65
+ @param {number} data.retries: a urllib3.Retry object to use on all http requests to the
66
+ Flagsmith API
67
+ @param {boolean} data.enableAnalytics: if enabled, sends additional requests to the Flagsmith
68
+ API to power flag analytics charts
69
+ @param data.defaultFlagHandler: callable which will be used in the case where
70
+ flags cannot be retrieved from the API or a non-existent feature is
71
+ requested
72
+ @param data.logger: an instance of the pino Logger class to use for logging
73
+ @param {boolean} data.offlineMode: sets the client into offline mode. Relies on offlineHandler for
74
+ evaluating flags.
75
+ @param {BaseOfflineHandler} data.offlineHandler: provide a handler for offline logic. Used to get environment
76
+ document from another source when in offlineMode. Works in place of
77
+ defaultFlagHandler if offlineMode is not set and using remote evaluation.
78
+ */
79
+ constructor(data = {}) {
80
+ // if (!data.offlineMode && !data.environmentKey) {
81
+ // throw new Error('ValueError: environmentKey is required.');
82
+ // }
83
+ this.agent = data.agent;
84
+ this.customFetch = data.fetch ?? fetch;
85
+ this.environmentKey = data.environmentKey;
86
+ this.apiUrl = data.apiUrl || this.apiUrl;
87
+ this.customHeaders = data.customHeaders;
88
+ this.requestTimeoutMs =
89
+ 1000 * (data.requestTimeoutSeconds ?? DEFAULT_REQUEST_TIMEOUT_SECONDS);
90
+ this.enableLocalEvaluation = data.enableLocalEvaluation;
91
+ this.environmentRefreshIntervalSeconds =
92
+ data.environmentRefreshIntervalSeconds || this.environmentRefreshIntervalSeconds;
93
+ this.retries = data.retries;
94
+ this.enableAnalytics = data.enableAnalytics || false;
95
+ this.defaultFlagHandler = data.defaultFlagHandler;
96
+ this.onEnvironmentChange = data.onEnvironmentChange;
97
+ this.logger = data.logger || pino();
98
+ this.offlineMode = data.offlineMode || false;
99
+ this.offlineHandler = data.offlineHandler;
100
+ // argument validation
101
+ if (this.offlineMode && !this.offlineHandler) {
102
+ throw new Error('ValueError: offlineHandler must be provided to use offline mode.');
103
+ }
104
+ else if (this.defaultFlagHandler && this.offlineHandler) {
105
+ throw new Error('ValueError: Cannot use both defaultFlagHandler and offlineHandler.');
106
+ }
107
+ if (this.offlineHandler) {
108
+ this.environment = this.offlineHandler.getEnvironment();
109
+ }
110
+ if (!!data.cache) {
111
+ const missingMethods = ['has', 'get', 'set'].filter(method => data.cache && !data.cache[method]);
112
+ if (missingMethods.length > 0) {
113
+ throw new Error(`Please implement the following methods in your cache: ${missingMethods.join(', ')}`);
114
+ }
115
+ this.cache = data.cache;
116
+ }
117
+ if (!this.offlineMode) {
118
+ if (!this.environmentKey) {
119
+ throw new Error('ValueError: environmentKey is required.');
120
+ }
121
+ const apiUrl = data.apiUrl || DEFAULT_API_URL;
122
+ this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
123
+ this.environmentFlagsUrl = `${this.apiUrl}flags/`;
124
+ this.identitiesUrl = `${this.apiUrl}identities/`;
125
+ this.environmentUrl = `${this.apiUrl}environment-document/`;
126
+ if (this.enableLocalEvaluation) {
127
+ if (!this.environmentKey.startsWith('ser.')) {
128
+ console.error('In order to use local evaluation, please generate a server key in the environment settings page.');
129
+ }
130
+ this.environmentDataPollingManager = new EnvironmentDataPollingManager(this, this.environmentRefreshIntervalSeconds);
131
+ this.environmentDataPollingManager.start();
132
+ this.updateEnvironment();
133
+ }
134
+ this.analyticsProcessor = data.enableAnalytics
135
+ ? new AnalyticsProcessor({
136
+ environmentKey: this.environmentKey,
137
+ baseApiUrl: this.apiUrl,
138
+ requestTimeoutMs: this.requestTimeoutMs,
139
+ logger: this.logger
140
+ })
141
+ : undefined;
142
+ }
143
+ }
144
+ /**
145
+ * Get all the default for flags for the current environment.
146
+ *
147
+ * @returns Flags object holding all the flags for the current environment.
148
+ */
149
+ async getEnvironmentFlags() {
150
+ const cachedItem = !!this.cache && (await this.cache.get(`flags`));
151
+ if (!!cachedItem) {
152
+ return cachedItem;
153
+ }
154
+ if (this.enableLocalEvaluation && !this.offlineMode) {
155
+ return new Promise((resolve, reject) => this.environmentPromise.then(() => {
156
+ resolve(this.getEnvironmentFlagsFromDocument());
157
+ }).catch(e => reject(e)));
158
+ }
159
+ if (this.environment) {
160
+ return this.getEnvironmentFlagsFromDocument();
161
+ }
162
+ return this.getEnvironmentFlagsFromApi();
163
+ }
164
+ /**
165
+ * Get all the flags for the current environment for a given identity. Will also
166
+ upsert all traits to the Flagsmith API for future evaluations. Providing a
167
+ trait with a value of None will remove the trait from the identity if it exists.
168
+ *
169
+ * @param {string} identifier a unique identifier for the identity in the current
170
+ environment, e.g. email address, username, uuid
171
+ * @param {{[key:string]:any | ITraitConfig}} traits? a dictionary of traits to add / update on the identity in
172
+ Flagsmith, e.g. {"num_orders": 10} or {age: {value: 30, transient: true}}
173
+ * @returns Flags object holding all the flags for the given identity.
174
+ */
175
+ async getIdentityFlags(identifier, traits, transient = false) {
176
+ if (!identifier) {
177
+ throw new Error('`identifier` argument is missing or invalid.');
178
+ }
179
+ const cachedItem = !!this.cache && (await this.cache.get(`flags-${identifier}`));
180
+ if (!!cachedItem) {
181
+ return cachedItem;
182
+ }
183
+ traits = traits || {};
184
+ if (this.enableLocalEvaluation) {
185
+ return new Promise((resolve, reject) => this.environmentPromise.then(() => {
186
+ resolve(this.getIdentityFlagsFromDocument(identifier, traits || {}));
187
+ }).catch(e => reject(e)));
188
+ }
189
+ if (this.offlineMode) {
190
+ return this.getIdentityFlagsFromDocument(identifier, traits || {});
191
+ }
192
+ return this.getIdentityFlagsFromApi(identifier, traits, transient);
193
+ }
194
+ /**
195
+ * Get the segments for the current environment for a given identity. Will also
196
+ upsert all traits to the Flagsmith API for future evaluations. Providing a
197
+ trait with a value of None will remove the trait from the identity if it exists.
198
+ *
199
+ * @param {string} identifier a unique identifier for the identity in the current
200
+ environment, e.g. email address, username, uuid
201
+ * @param {{[key:string]:any}} traits? a dictionary of traits to add / update on the identity in
202
+ Flagsmith, e.g. {"num_orders": 10}
203
+ * @returns Segments that the given identity belongs to.
204
+ */
205
+ getIdentitySegments(identifier, traits) {
206
+ if (!identifier) {
207
+ throw new Error('`identifier` argument is missing or invalid.');
208
+ }
209
+ traits = traits || {};
210
+ if (this.enableLocalEvaluation) {
211
+ return new Promise((resolve, reject) => {
212
+ return this.environmentPromise.then(() => {
213
+ const identityModel = this.getIdentityModel(identifier, Object.keys(traits || {}).map(key => ({
214
+ key,
215
+ value: traits?.[key]
216
+ })));
217
+ const segments = getIdentitySegments(this.environment, identityModel);
218
+ return resolve(segments);
219
+ }).catch(e => reject(e));
220
+ });
221
+ }
222
+ console.error('This function is only permitted with local evaluation.');
223
+ return Promise.resolve([]);
224
+ }
225
+ /**
226
+ * Updates the environment state for local flag evaluation.
227
+ * Sets a local promise to prevent race conditions in getIdentityFlags / getIdentitySegments.
228
+ * You only need to call this if you wish to bypass environmentRefreshIntervalSeconds.
229
+ */
230
+ async updateEnvironment() {
231
+ try {
232
+ const request = this.getEnvironmentFromApi();
233
+ if (!this.environmentPromise) {
234
+ this.environmentPromise = request.then(res => {
235
+ this.environment = res;
236
+ });
237
+ await this.environmentPromise;
238
+ }
239
+ else {
240
+ this.environment = await request;
241
+ }
242
+ if (this.environment.identityOverrides?.length) {
243
+ this.identitiesWithOverridesByIdentifier = new Map(this.environment.identityOverrides.map(identity => [
244
+ identity.identifier,
245
+ identity
246
+ ]));
247
+ }
248
+ if (this.onEnvironmentChange) {
249
+ this.onEnvironmentChange(null, this.environment);
250
+ }
251
+ }
252
+ catch (e) {
253
+ if (this.onEnvironmentChange) {
254
+ this.onEnvironmentChange(e, this.environment);
255
+ }
256
+ }
257
+ }
258
+ async close() {
259
+ this.environmentDataPollingManager?.stop();
260
+ }
261
+ async getJSONResponse(url, method, body) {
262
+ const headers = { 'Content-Type': 'application/json' };
263
+ if (this.environmentKey) {
264
+ headers['X-Environment-Key'] = this.environmentKey;
265
+ }
266
+ if (this.customHeaders) {
267
+ for (const [k, v] of Object.entries(this.customHeaders)) {
268
+ headers[k] = v;
269
+ }
270
+ }
271
+ const data = await retryFetch(url, {
272
+ dispatcher: this.agent,
273
+ method: method,
274
+ body: JSON.stringify(body),
275
+ headers: headers
276
+ }, this.retries, this.requestTimeoutMs, this.customFetch);
277
+ if (data.status !== 200) {
278
+ throw new FlagsmithAPIError(`Invalid request made to Flagsmith API. Response status code: ${data.status}`);
279
+ }
280
+ return data.json();
281
+ }
282
+ /**
283
+ * This promise ensures that the environment is retrieved before attempting to locally evaluate.
284
+ */
285
+ environmentPromise;
286
+ async getEnvironmentFromApi() {
287
+ if (!this.environmentUrl) {
288
+ throw new Error('`apiUrl` argument is missing or invalid.');
289
+ }
290
+ const environment_data = await this.getJSONResponse(this.environmentUrl, 'GET');
291
+ return buildEnvironmentModel(environment_data);
292
+ }
293
+ async getEnvironmentFlagsFromDocument() {
294
+ const flags = Flags.fromFeatureStateModels({
295
+ featureStates: getEnvironmentFeatureStates(this.environment),
296
+ analyticsProcessor: this.analyticsProcessor,
297
+ defaultFlagHandler: this.defaultFlagHandler
298
+ });
299
+ if (!!this.cache) {
300
+ await this.cache.set('flags', flags);
301
+ }
302
+ return flags;
303
+ }
304
+ async getIdentityFlagsFromDocument(identifier, traits) {
305
+ const identityModel = this.getIdentityModel(identifier, Object.keys(traits).map(key => ({
306
+ key,
307
+ value: traits[key]
308
+ })));
309
+ const featureStates = getIdentityFeatureStates(this.environment, identityModel);
310
+ const flags = Flags.fromFeatureStateModels({
311
+ featureStates: featureStates,
312
+ analyticsProcessor: this.analyticsProcessor,
313
+ defaultFlagHandler: this.defaultFlagHandler,
314
+ identityID: identityModel.djangoID || identityModel.compositeKey
315
+ });
316
+ if (!!this.cache) {
317
+ await this.cache.set(`flags-${identifier}`, flags);
318
+ }
319
+ return flags;
320
+ }
321
+ async getEnvironmentFlagsFromApi() {
322
+ if (!this.environmentFlagsUrl) {
323
+ throw new Error('`apiUrl` argument is missing or invalid.');
324
+ }
325
+ try {
326
+ const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
327
+ const flags = Flags.fromAPIFlags({
328
+ apiFlags: apiFlags,
329
+ analyticsProcessor: this.analyticsProcessor,
330
+ defaultFlagHandler: this.defaultFlagHandler
331
+ });
332
+ if (!!this.cache) {
333
+ await this.cache.set('flags', flags);
334
+ }
335
+ return flags;
336
+ }
337
+ catch (e) {
338
+ if (this.offlineHandler) {
339
+ return this.getEnvironmentFlagsFromDocument();
340
+ }
341
+ if (this.defaultFlagHandler) {
342
+ return new Flags({
343
+ flags: {},
344
+ defaultFlagHandler: this.defaultFlagHandler
345
+ });
346
+ }
347
+ throw e;
348
+ }
349
+ }
350
+ async getIdentityFlagsFromApi(identifier, traits, transient = false) {
351
+ if (!this.identitiesUrl) {
352
+ throw new Error('`apiUrl` argument is missing or invalid.');
353
+ }
354
+ try {
355
+ const data = generateIdentitiesData(identifier, traits, transient);
356
+ const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
357
+ const flags = Flags.fromAPIFlags({
358
+ apiFlags: jsonResponse['flags'],
359
+ analyticsProcessor: this.analyticsProcessor,
360
+ defaultFlagHandler: this.defaultFlagHandler
361
+ });
362
+ if (!!this.cache) {
363
+ await this.cache.set(`flags-${identifier}`, flags);
364
+ }
365
+ return flags;
366
+ }
367
+ catch (e) {
368
+ if (this.offlineHandler) {
369
+ return this.getIdentityFlagsFromDocument(identifier, traits);
370
+ }
371
+ if (this.defaultFlagHandler) {
372
+ return new Flags({
373
+ flags: {},
374
+ defaultFlagHandler: this.defaultFlagHandler
375
+ });
376
+ }
377
+ throw e;
378
+ }
379
+ }
380
+ getIdentityModel(identifier, traits) {
381
+ const traitModels = traits.map(trait => new TraitModel(trait.key, trait.value));
382
+ let identityWithOverrides = this.identitiesWithOverridesByIdentifier?.get(identifier);
383
+ if (identityWithOverrides) {
384
+ identityWithOverrides.updateTraits(traitModels);
385
+ return identityWithOverrides;
386
+ }
387
+ return new IdentityModel('0', traitModels, [], this.environment.apiKey, identifier);
388
+ }
389
+ }
390
+ export default Flagsmith;
@@ -0,0 +1,55 @@
1
+ import { FeatureStateModel } from '../flagsmith-engine/features/models.js';
2
+ import { AnalyticsProcessor } from './analytics.js';
3
+ export declare class BaseFlag {
4
+ enabled: boolean;
5
+ value: string | number | boolean | undefined;
6
+ isDefault: boolean;
7
+ constructor(value: string | number | boolean | undefined, enabled: boolean, isDefault: boolean);
8
+ }
9
+ export declare class DefaultFlag extends BaseFlag {
10
+ constructor(value: string | number | boolean | undefined, enabled: boolean);
11
+ }
12
+ export declare class Flag extends BaseFlag {
13
+ featureId: number;
14
+ featureName: string;
15
+ constructor(params: {
16
+ value: string | number | boolean | undefined;
17
+ enabled: boolean;
18
+ isDefault?: boolean;
19
+ featureId: number;
20
+ featureName: string;
21
+ });
22
+ static fromFeatureStateModel(fsm: FeatureStateModel, identityId: number | string | undefined): Flag;
23
+ static fromAPIFlag(flagData: any): Flag;
24
+ }
25
+ export declare class Flags {
26
+ flags: {
27
+ [key: string]: Flag;
28
+ };
29
+ defaultFlagHandler?: (featureName: string) => DefaultFlag;
30
+ analyticsProcessor?: AnalyticsProcessor;
31
+ constructor(data: {
32
+ flags: {
33
+ [key: string]: Flag;
34
+ };
35
+ defaultFlagHandler?: (v: string) => DefaultFlag;
36
+ analyticsProcessor?: AnalyticsProcessor;
37
+ });
38
+ static fromFeatureStateModels(data: {
39
+ featureStates: FeatureStateModel[];
40
+ analyticsProcessor?: AnalyticsProcessor;
41
+ defaultFlagHandler?: (f: string) => DefaultFlag;
42
+ identityID?: string | number;
43
+ }): Flags;
44
+ static fromAPIFlags(data: {
45
+ apiFlags: {
46
+ [key: string]: any;
47
+ }[];
48
+ analyticsProcessor?: AnalyticsProcessor;
49
+ defaultFlagHandler?: (v: string) => DefaultFlag;
50
+ }): Flags;
51
+ allFlags(): Flag[];
52
+ getFlag(featureName: string): BaseFlag;
53
+ getFeatureValue(featureName: string): any;
54
+ isFeatureEnabled(featureName: string): boolean;
55
+ }
@@ -0,0 +1,94 @@
1
+ export class BaseFlag {
2
+ enabled;
3
+ value;
4
+ isDefault;
5
+ constructor(value, enabled, isDefault) {
6
+ this.value = value;
7
+ this.enabled = enabled;
8
+ this.isDefault = isDefault;
9
+ }
10
+ }
11
+ export class DefaultFlag extends BaseFlag {
12
+ constructor(value, enabled) {
13
+ super(value, enabled, true);
14
+ }
15
+ }
16
+ export class Flag extends BaseFlag {
17
+ featureId;
18
+ featureName;
19
+ constructor(params) {
20
+ super(params.value, params.enabled, !!params.isDefault);
21
+ this.featureId = params.featureId;
22
+ this.featureName = params.featureName;
23
+ }
24
+ static fromFeatureStateModel(fsm, identityId) {
25
+ return new Flag({
26
+ value: fsm.getValue(identityId),
27
+ enabled: fsm.enabled,
28
+ featureId: fsm.feature.id,
29
+ featureName: fsm.feature.name
30
+ });
31
+ }
32
+ static fromAPIFlag(flagData) {
33
+ return new Flag({
34
+ enabled: flagData['enabled'],
35
+ value: flagData['feature_state_value'] || flagData['value'],
36
+ featureId: flagData['feature']['id'],
37
+ featureName: flagData['feature']['name']
38
+ });
39
+ }
40
+ }
41
+ export class Flags {
42
+ flags = {};
43
+ defaultFlagHandler;
44
+ analyticsProcessor;
45
+ constructor(data) {
46
+ this.flags = data.flags;
47
+ this.defaultFlagHandler = data.defaultFlagHandler;
48
+ this.analyticsProcessor = data.analyticsProcessor;
49
+ }
50
+ static fromFeatureStateModels(data) {
51
+ const flags = {};
52
+ for (const fs of data.featureStates) {
53
+ flags[fs.feature.name] = Flag.fromFeatureStateModel(fs, data.identityID);
54
+ }
55
+ return new Flags({
56
+ flags: flags,
57
+ defaultFlagHandler: data.defaultFlagHandler,
58
+ analyticsProcessor: data.analyticsProcessor
59
+ });
60
+ }
61
+ static fromAPIFlags(data) {
62
+ const flags = {};
63
+ for (const flagData of data.apiFlags) {
64
+ flags[flagData['feature']['name']] = Flag.fromAPIFlag(flagData);
65
+ }
66
+ return new Flags({
67
+ flags: flags,
68
+ defaultFlagHandler: data.defaultFlagHandler,
69
+ analyticsProcessor: data.analyticsProcessor
70
+ });
71
+ }
72
+ allFlags() {
73
+ return Object.values(this.flags);
74
+ }
75
+ getFlag(featureName) {
76
+ const flag = this.flags[featureName];
77
+ if (!flag) {
78
+ if (this.defaultFlagHandler) {
79
+ return this.defaultFlagHandler(featureName);
80
+ }
81
+ return { enabled: false, isDefault: true, value: undefined };
82
+ }
83
+ if (this.analyticsProcessor && flag.featureId) {
84
+ this.analyticsProcessor.trackFeature(flag.featureName);
85
+ }
86
+ return flag;
87
+ }
88
+ getFeatureValue(featureName) {
89
+ return this.getFlag(featureName).value;
90
+ }
91
+ isFeatureEnabled(featureName) {
92
+ return this.getFlag(featureName).enabled;
93
+ }
94
+ }
@@ -0,0 +1,9 @@
1
+ import { EnvironmentModel } from '../flagsmith-engine/environments/models.js';
2
+ export declare class BaseOfflineHandler {
3
+ getEnvironment(): EnvironmentModel;
4
+ }
5
+ export declare class LocalFileHandler extends BaseOfflineHandler {
6
+ environment: EnvironmentModel;
7
+ constructor(environment_document_path: string);
8
+ getEnvironment(): EnvironmentModel;
9
+ }
@@ -0,0 +1,18 @@
1
+ import * as fs from 'fs';
2
+ import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js';
3
+ export class BaseOfflineHandler {
4
+ getEnvironment() {
5
+ throw new Error('Not implemented');
6
+ }
7
+ }
8
+ export class LocalFileHandler extends BaseOfflineHandler {
9
+ environment;
10
+ constructor(environment_document_path) {
11
+ super();
12
+ const environment_document = fs.readFileSync(environment_document_path, 'utf8');
13
+ this.environment = buildEnvironmentModel(JSON.parse(environment_document));
14
+ }
15
+ getEnvironment() {
16
+ return this.environment;
17
+ }
18
+ }
@@ -0,0 +1,9 @@
1
+ import Flagsmith from './index.js';
2
+ export declare class EnvironmentDataPollingManager {
3
+ private interval?;
4
+ private main;
5
+ private refreshIntervalSeconds;
6
+ constructor(main: Flagsmith, refreshIntervalSeconds: number);
7
+ start(): void;
8
+ stop(): void;
9
+ }
@@ -0,0 +1,25 @@
1
+ export class EnvironmentDataPollingManager {
2
+ interval;
3
+ main;
4
+ refreshIntervalSeconds;
5
+ constructor(main, refreshIntervalSeconds) {
6
+ this.main = main;
7
+ this.refreshIntervalSeconds = refreshIntervalSeconds;
8
+ }
9
+ start() {
10
+ const updateEnvironment = () => {
11
+ if (this.interval)
12
+ clearInterval(this.interval);
13
+ this.interval = setInterval(async () => {
14
+ await this.main.updateEnvironment();
15
+ }, this.refreshIntervalSeconds * 1000);
16
+ };
17
+ updateEnvironment();
18
+ }
19
+ stop() {
20
+ if (!this.interval) {
21
+ return;
22
+ }
23
+ clearInterval(this.interval);
24
+ }
25
+ }
@@ -0,0 +1,38 @@
1
+ import { DefaultFlag, Flags } from './models.js';
2
+ import { EnvironmentModel } from '../flagsmith-engine/index.js';
3
+ import { Dispatcher } from 'undici-types';
4
+ import { Logger } from 'pino';
5
+ import { BaseOfflineHandler } from './offline_handlers.js';
6
+ export type IFlagsmithValue<T = string | number | boolean | null> = T;
7
+ export interface FlagsmithCache {
8
+ get(key: string): Promise<Flags | undefined> | undefined;
9
+ set(key: string, value: Flags, ttl?: string | number): boolean | Promise<boolean>;
10
+ has(key: string): boolean | Promise<boolean>;
11
+ [key: string]: any;
12
+ }
13
+ export type Fetch = typeof fetch;
14
+ export interface FlagsmithConfig {
15
+ environmentKey?: string;
16
+ apiUrl?: string;
17
+ agent?: Dispatcher;
18
+ fetch?: Fetch;
19
+ customHeaders?: {
20
+ [key: string]: any;
21
+ };
22
+ requestTimeoutSeconds?: number;
23
+ enableLocalEvaluation?: boolean;
24
+ environmentRefreshIntervalSeconds?: number;
25
+ retries?: number;
26
+ enableAnalytics?: boolean;
27
+ defaultFlagHandler?: (featureName: string) => DefaultFlag;
28
+ cache?: FlagsmithCache;
29
+ onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void;
30
+ logger?: Logger;
31
+ offlineMode?: boolean;
32
+ offlineHandler?: BaseOfflineHandler;
33
+ }
34
+ export interface ITraitConfig {
35
+ value: FlagsmithTraitValue;
36
+ transient?: boolean;
37
+ }
38
+ export declare type FlagsmithTraitValue = IFlagsmithValue;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { Fetch, FlagsmithTraitValue, ITraitConfig } from './types.js';
2
+ import { Dispatcher } from "undici-types";
3
+ type Traits = {
4
+ [key: string]: ITraitConfig | FlagsmithTraitValue;
5
+ };
6
+ export declare function isTraitConfig(traitValue: ITraitConfig | FlagsmithTraitValue): traitValue is ITraitConfig;
7
+ export declare function generateIdentitiesData(identifier: string, traits: Traits, transient: boolean): {
8
+ identifier: string;
9
+ traits: ({
10
+ trait_key: string;
11
+ trait_value: string | number | boolean | null;
12
+ transient: boolean | undefined;
13
+ } | {
14
+ trait_key: string;
15
+ trait_value: string | number | boolean | null;
16
+ transient?: undefined;
17
+ })[];
18
+ transient: boolean;
19
+ } | {
20
+ identifier: string;
21
+ traits: ({
22
+ trait_key: string;
23
+ trait_value: string | number | boolean | null;
24
+ transient: boolean | undefined;
25
+ } | {
26
+ trait_key: string;
27
+ trait_value: string | number | boolean | null;
28
+ transient?: undefined;
29
+ })[];
30
+ transient?: undefined;
31
+ };
32
+ export declare const delay: (ms: number) => Promise<unknown>;
33
+ export declare const retryFetch: (url: string, fetchOptions: RequestInit & {
34
+ dispatcher?: Dispatcher;
35
+ }, retries: number | undefined, timeoutMs: number | undefined, customFetch: Fetch) => Promise<Response>;
36
+ export {};