flagsmith-nodejs 3.3.3 → 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 (193) hide show
  1. package/.github/workflows/publish.yml +2 -2
  2. package/.github/workflows/pull_request.yaml +7 -14
  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 +111 -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/cjs/flagsmith-engine/utils/hashing/index.js +29 -0
  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/cjs/index.js +18 -0
  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 +23 -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 +25 -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 +2 -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 +2 -2
  115. package/flagsmith-engine/features/util.ts +1 -1
  116. package/flagsmith-engine/identities/models.ts +4 -5
  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 +5 -5
  125. package/flagsmith-engine/segments/util.ts +3 -3
  126. package/flagsmith-engine/utils/collections.ts +1 -1
  127. package/flagsmith-engine/utils/hashing/index.ts +5 -29
  128. package/flagsmith-engine/utils/index.ts +1 -1
  129. package/index.ts +4 -8
  130. package/package.json +21 -16
  131. package/sdk/analytics.ts +7 -5
  132. package/sdk/index.ts +55 -46
  133. package/sdk/models.ts +2 -3
  134. package/sdk/offline_handlers.ts +2 -2
  135. package/sdk/polling_manager.ts +2 -3
  136. package/sdk/types.ts +35 -24
  137. package/sdk/utils.ts +49 -37
  138. package/tests/engine/e2e/engine.test.ts +8 -11
  139. package/tests/engine/unit/engine.test.ts +5 -5
  140. package/tests/engine/unit/segments/segment_evaluators.test.ts +9 -9
  141. package/tests/engine/unit/utils/utils.test.ts +2 -2
  142. package/tests/sdk/analytics.test.ts +8 -13
  143. package/tests/sdk/data/identity-with-transient-traits.json +41 -0
  144. package/tests/sdk/data/transient-identity.json +29 -0
  145. package/tests/sdk/flagsmith-cache.test.ts +16 -32
  146. package/tests/sdk/flagsmith-environment-flags.test.ts +21 -36
  147. package/tests/sdk/flagsmith-identity-flags.test.ts +83 -32
  148. package/tests/sdk/flagsmith.test.ts +67 -99
  149. package/tests/sdk/offline-handlers.test.ts +5 -6
  150. package/tests/sdk/polling.test.ts +6 -8
  151. package/tests/sdk/utils.ts +19 -15
  152. package/tsconfig.cjs.json +7 -0
  153. package/tsconfig.esm.json +7 -0
  154. package/tsconfig.json +8 -4
  155. package/vitest.config.ts +17 -0
  156. package/build/flagsmith-engine/environments/util.js +0 -27
  157. package/build/flagsmith-engine/features/models.js +0 -132
  158. package/build/flagsmith-engine/features/util.js +0 -27
  159. package/build/flagsmith-engine/identities/models.js +0 -113
  160. package/build/flagsmith-engine/identities/util.js +0 -46
  161. package/build/flagsmith-engine/index.d.ts +0 -14
  162. package/build/flagsmith-engine/index.js +0 -127
  163. package/build/flagsmith-engine/organisations/models.js +0 -21
  164. package/build/flagsmith-engine/organisations/util.js +0 -8
  165. package/build/flagsmith-engine/projects/util.js +0 -15
  166. package/build/flagsmith-engine/segments/evaluators.js +0 -45
  167. package/build/flagsmith-engine/segments/models.js +0 -147
  168. package/build/flagsmith-engine/utils/collections.js +0 -26
  169. package/build/flagsmith-engine/utils/errors.js +0 -26
  170. package/build/flagsmith-engine/utils/hashing/index.js +0 -60
  171. package/build/index.js +0 -23
  172. package/build/sdk/analytics.js +0 -120
  173. package/build/sdk/errors.js +0 -34
  174. package/build/sdk/index.js +0 -594
  175. package/build/sdk/models.js +0 -149
  176. package/build/sdk/offline_handlers.js +0 -66
  177. package/build/sdk/polling_manager.js +0 -72
  178. package/build/sdk/utils.d.ts +0 -12
  179. package/build/sdk/utils.js +0 -107
  180. package/jest.config.js +0 -5
  181. package/tests/index.js +0 -0
  182. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/constants.d.ts +0 -0
  183. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/constants.js +0 -0
  184. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/models.d.ts +0 -0
  185. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/traits/models.d.ts +0 -0
  186. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/organisations/models.d.ts +0 -0
  187. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/constants.d.ts +0 -0
  188. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/constants.js +0 -0
  189. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/errors.d.ts +0 -0
  190. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/hashing/index.d.ts +0 -0
  191. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/index.d.ts +0 -0
  192. /package/build/{sdk → cjs/sdk}/errors.d.ts +0 -0
  193. /package/build/{sdk → cjs/sdk}/types.js +0 -0
package/sdk/index.ts CHANGED
@@ -1,28 +1,28 @@
1
- import { RequestInit } from 'node-fetch';
2
- import { getEnvironmentFeatureStates, getIdentityFeatureStates } from '../flagsmith-engine';
3
- import { EnvironmentModel } from '../flagsmith-engine/environments/models';
4
- import { buildEnvironmentModel } from '../flagsmith-engine/environments/util';
5
- import { IdentityModel } from '../flagsmith-engine/identities/models';
6
- import { TraitModel } from '../flagsmith-engine/identities/traits/models';
7
-
8
- import { AnalyticsProcessor } from './analytics';
9
- import { BaseOfflineHandler } from './offline_handlers';
10
- import { FlagsmithAPIError, FlagsmithClientError } from './errors';
11
-
12
- import { DefaultFlag, Flags } from './models';
13
- import { EnvironmentDataPollingManager } from './polling_manager';
14
- import { generateIdentitiesData, retryFetch } from './utils';
15
- import { SegmentModel } from '../flagsmith-engine/segments/models';
16
- import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators';
17
- import { FlagsmithCache, FlagsmithConfig } from './types';
18
- import pino, { Logger } from 'pino';
19
-
20
- export { AnalyticsProcessor } from './analytics';
21
- export { FlagsmithAPIError, FlagsmithClientError } from './errors';
22
-
23
- export { DefaultFlag, Flags } from './models';
24
- export { EnvironmentDataPollingManager } from './polling_manager';
25
- export { FlagsmithCache, FlagsmithConfig } from './types';
1
+ import { Dispatcher } from 'undici-types';
2
+ import { getEnvironmentFeatureStates, getIdentityFeatureStates } from '../flagsmith-engine/index.js';
3
+ import { EnvironmentModel } from '../flagsmith-engine/index.js';
4
+ import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js';
5
+ import { IdentityModel } from '../flagsmith-engine/index.js';
6
+ import { TraitModel } from '../flagsmith-engine/index.js';
7
+
8
+ import { AnalyticsProcessor } from './analytics.js';
9
+ import { BaseOfflineHandler } from './offline_handlers.js';
10
+ import { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
11
+
12
+ import { DefaultFlag, Flags } from './models.js';
13
+ import { EnvironmentDataPollingManager } from './polling_manager.js';
14
+ import { generateIdentitiesData, retryFetch } from './utils.js';
15
+ import { SegmentModel } from '../flagsmith-engine/index.js';
16
+ import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js';
17
+ import { Fetch, FlagsmithCache, FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
18
+ import { pino, Logger } from 'pino';
19
+
20
+ export { AnalyticsProcessor } from './analytics.js';
21
+ export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
22
+
23
+ export { DefaultFlag, Flags } from './models.js';
24
+ export { EnvironmentDataPollingManager } from './polling_manager.js';
25
+ export { FlagsmithCache, FlagsmithConfig } from './types.js';
26
26
 
27
27
  const DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/';
28
28
  const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
@@ -31,7 +31,7 @@ export class Flagsmith {
31
31
  environmentKey?: string = undefined;
32
32
  apiUrl?: string = undefined;
33
33
  customHeaders?: { [key: string]: any };
34
- agent: RequestInit['agent'];
34
+ agent?: Dispatcher;
35
35
  requestTimeoutMs?: number;
36
36
  enableLocalEvaluation?: boolean = false;
37
37
  environmentRefreshIntervalSeconds: number = 60;
@@ -39,7 +39,6 @@ export class Flagsmith {
39
39
  enableAnalytics: boolean = false;
40
40
  defaultFlagHandler?: (featureName: string) => DefaultFlag;
41
41
 
42
-
43
42
  environmentFlagsUrl?: string;
44
43
  identitiesUrl?: string;
45
44
  environmentUrl?: string;
@@ -55,6 +54,7 @@ export class Flagsmith {
55
54
  private onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void;
56
55
  private analyticsProcessor?: AnalyticsProcessor;
57
56
  private logger: Logger;
57
+ private customFetch: Fetch;
58
58
  /**
59
59
  * A Flagsmith client.
60
60
  *
@@ -98,6 +98,7 @@ export class Flagsmith {
98
98
  // }
99
99
 
100
100
  this.agent = data.agent;
101
+ this.customFetch = data.fetch ?? fetch;
101
102
  this.environmentKey = data.environmentKey;
102
103
  this.apiUrl = data.apiUrl || this.apiUrl;
103
104
  this.customHeaders = data.customHeaders;
@@ -168,11 +169,11 @@ export class Flagsmith {
168
169
 
169
170
  this.analyticsProcessor = data.enableAnalytics
170
171
  ? new AnalyticsProcessor({
171
- environmentKey: this.environmentKey,
172
- baseApiUrl: this.apiUrl,
173
- requestTimeoutMs: this.requestTimeoutMs,
174
- logger: this.logger
175
- })
172
+ environmentKey: this.environmentKey,
173
+ baseApiUrl: this.apiUrl,
174
+ requestTimeoutMs: this.requestTimeoutMs,
175
+ logger: this.logger
176
+ })
176
177
  : undefined;
177
178
  }
178
179
  }
@@ -207,11 +208,15 @@ export class Flagsmith {
207
208
  *
208
209
  * @param {string} identifier a unique identifier for the identity in the current
209
210
  environment, e.g. email address, username, uuid
210
- * @param {{[key:string]:any}} traits? a dictionary of traits to add / update on the identity in
211
- Flagsmith, e.g. {"num_orders": 10}
211
+ * @param {{[key:string]:any | ITraitConfig}} traits? a dictionary of traits to add / update on the identity in
212
+ Flagsmith, e.g. {"num_orders": 10} or {age: {value: 30, transient: true}}
212
213
  * @returns Flags object holding all the flags for the given identity.
213
214
  */
214
- async getIdentityFlags(identifier: string, traits?: { [key: string]: any }): Promise<Flags> {
215
+ async getIdentityFlags(
216
+ identifier: string,
217
+ traits?: { [key: string]: FlagsmithTraitValue | ITraitConfig },
218
+ transient: boolean = false
219
+ ): Promise<Flags> {
215
220
  if (!identifier) {
216
221
  throw new Error('`identifier` argument is missing or invalid.');
217
222
  }
@@ -232,7 +237,7 @@ export class Flagsmith {
232
237
  return this.getIdentityFlagsFromDocument(identifier, traits || {});
233
238
  }
234
239
 
235
- return this.getIdentityFlagsFromApi(identifier, traits);
240
+ return this.getIdentityFlagsFromApi(identifier, traits, transient);
236
241
  }
237
242
 
238
243
  /**
@@ -293,8 +298,11 @@ export class Flagsmith {
293
298
  }
294
299
  if (this.environment.identityOverrides?.length) {
295
300
  this.identitiesWithOverridesByIdentifier = new Map<string, IdentityModel>(
296
- this.environment.identityOverrides.map(identity => [identity.identifier, identity]
297
- ));
301
+ this.environment.identityOverrides.map(identity => [
302
+ identity.identifier,
303
+ identity
304
+ ])
305
+ );
298
306
  }
299
307
  if (this.onEnvironmentChange) {
300
308
  this.onEnvironmentChange(null, this.environment);
@@ -329,13 +337,14 @@ export class Flagsmith {
329
337
  const data = await retryFetch(
330
338
  url,
331
339
  {
332
- agent: this.agent,
340
+ dispatcher: this.agent,
333
341
  method: method,
334
342
  body: JSON.stringify(body),
335
343
  headers: headers
336
344
  },
337
345
  this.retries,
338
- this.requestTimeoutMs || undefined
346
+ this.requestTimeoutMs,
347
+ this.customFetch,
339
348
  );
340
349
 
341
350
  if (data.status !== 200) {
@@ -367,7 +376,6 @@ export class Flagsmith {
367
376
  defaultFlagHandler: this.defaultFlagHandler
368
377
  });
369
378
  if (!!this.cache) {
370
- // @ts-ignore node-cache types are incorrect, ttl should be optional
371
379
  await this.cache.set('flags', flags);
372
380
  }
373
381
  return flags;
@@ -395,7 +403,6 @@ export class Flagsmith {
395
403
  });
396
404
 
397
405
  if (!!this.cache) {
398
- // @ts-ignore node-cache types are incorrect, ttl should be optional
399
406
  await this.cache.set(`flags-${identifier}`, flags);
400
407
  }
401
408
 
@@ -414,7 +421,6 @@ export class Flagsmith {
414
421
  defaultFlagHandler: this.defaultFlagHandler
415
422
  });
416
423
  if (!!this.cache) {
417
- // @ts-ignore node-cache types are incorrect, ttl should be optional
418
424
  await this.cache.set('flags', flags);
419
425
  }
420
426
  return flags;
@@ -433,12 +439,16 @@ export class Flagsmith {
433
439
  }
434
440
  }
435
441
 
436
- private async getIdentityFlagsFromApi(identifier: string, traits: { [key: string]: any }) {
442
+ private async getIdentityFlagsFromApi(
443
+ identifier: string,
444
+ traits: { [key: string]: FlagsmithTraitValue | ITraitConfig },
445
+ transient: boolean = false
446
+ ) {
437
447
  if (!this.identitiesUrl) {
438
448
  throw new Error('`apiUrl` argument is missing or invalid.');
439
449
  }
440
450
  try {
441
- const data = generateIdentitiesData(identifier, traits);
451
+ const data = generateIdentitiesData(identifier, traits, transient);
442
452
  const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
443
453
  const flags = Flags.fromAPIFlags({
444
454
  apiFlags: jsonResponse['flags'],
@@ -446,7 +456,6 @@ export class Flagsmith {
446
456
  defaultFlagHandler: this.defaultFlagHandler
447
457
  });
448
458
  if (!!this.cache) {
449
- // @ts-ignore node-cache types are incorrect, ttl should be optional
450
459
  await this.cache.set(`flags-${identifier}`, flags);
451
460
  }
452
461
  return flags;
package/sdk/models.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { FeatureStateModel } from '../flagsmith-engine/features/models';
2
- import { AnalyticsProcessor } from './analytics';
3
- import { FlagsmithClientError } from './errors';
1
+ import { FeatureStateModel } from '../flagsmith-engine/features/models.js';
2
+ import { AnalyticsProcessor } from './analytics.js';
4
3
 
5
4
  export class BaseFlag {
6
5
  enabled: boolean;
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'fs';
2
- import { buildEnvironmentModel } from '../flagsmith-engine/environments/util';
3
- import { EnvironmentModel } from '../flagsmith-engine/environments/models';
2
+ import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js';
3
+ import { EnvironmentModel } from '../flagsmith-engine/environments/models.js';
4
4
 
5
5
  export class BaseOfflineHandler {
6
6
  getEnvironment() : EnvironmentModel {
@@ -1,7 +1,7 @@
1
- import Flagsmith from '.';
1
+ import Flagsmith from './index.js';
2
2
 
3
3
  export class EnvironmentDataPollingManager {
4
- private interval?: NodeJS.Timer;
4
+ private interval?: NodeJS.Timeout;
5
5
  private main: Flagsmith;
6
6
  private refreshIntervalSeconds: number;
7
7
 
@@ -17,7 +17,6 @@ export class EnvironmentDataPollingManager {
17
17
  await this.main.updateEnvironment();
18
18
  }, this.refreshIntervalSeconds * 1000);
19
19
  };
20
- this.main.updateEnvironment();
21
20
  updateEnvironment();
22
21
  }
23
22
 
package/sdk/types.ts CHANGED
@@ -1,30 +1,41 @@
1
- import { DefaultFlag, Flags } from "./models";
2
- import { EnvironmentModel } from "../flagsmith-engine";
3
- import { RequestInit } from "node-fetch";
4
- import { Logger } from "pino";
5
- import { BaseOfflineHandler } from "./offline_handlers";
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
6
 
7
+ export type IFlagsmithValue<T = string | number | boolean | null> = T;
7
8
  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;
9
+ get(key: string): Promise<Flags | undefined> | undefined;
10
+ set(key: string, value: Flags, ttl?: string | number): boolean | Promise<boolean>;
11
+ has(key: string): boolean | Promise<boolean>;
12
+ [key: string]: any;
12
13
  }
13
14
 
15
+ export type Fetch = typeof fetch
16
+
14
17
  export interface FlagsmithConfig {
15
- environmentKey?: string;
16
- apiUrl?: string;
17
- agent?:RequestInit['agent'];
18
- customHeaders?: { [key: string]: any };
19
- requestTimeoutSeconds?: number;
20
- enableLocalEvaluation?: boolean;
21
- environmentRefreshIntervalSeconds?: number;
22
- retries?: number;
23
- enableAnalytics?: boolean;
24
- defaultFlagHandler?: (featureName: string) => DefaultFlag;
25
- cache?: FlagsmithCache,
26
- onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void,
27
- logger?: Logger
28
- offlineMode?: boolean;
29
- offlineHandler?: BaseOfflineHandler;
18
+ environmentKey?: string;
19
+ apiUrl?: string;
20
+ agent?: Dispatcher;
21
+ fetch?: Fetch;
22
+ customHeaders?: { [key: string]: any };
23
+ requestTimeoutSeconds?: number;
24
+ enableLocalEvaluation?: boolean;
25
+ environmentRefreshIntervalSeconds?: number;
26
+ retries?: number;
27
+ enableAnalytics?: boolean;
28
+ defaultFlagHandler?: (featureName: string) => DefaultFlag;
29
+ cache?: FlagsmithCache;
30
+ onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void;
31
+ logger?: Logger;
32
+ offlineMode?: boolean;
33
+ offlineHandler?: BaseOfflineHandler;
34
+ }
35
+
36
+ export interface ITraitConfig {
37
+ value: FlagsmithTraitValue;
38
+ transient?: boolean;
30
39
  }
40
+
41
+ export declare type FlagsmithTraitValue = IFlagsmithValue;
package/sdk/utils.ts CHANGED
@@ -1,12 +1,36 @@
1
- import fetch, { RequestInit, Response } from 'node-fetch';
2
- // @ts-ignore
3
- if (typeof fetch.default !== 'undefined') fetch = fetch.default;
1
+ import {Fetch, FlagsmithTraitValue, ITraitConfig} from './types.js';
2
+ import {Dispatcher} from "undici-types";
4
3
 
5
- export function generateIdentitiesData(identifier: string, traits: { [key: string]: any }) {
6
- const traitsGenerated = Object.entries(traits).map(trait => ({
7
- trait_key: trait[0],
8
- trait_value: trait[1]
9
- }));
4
+ type Traits = { [key: string]: ITraitConfig | FlagsmithTraitValue };
5
+
6
+ export function isTraitConfig(
7
+ traitValue: ITraitConfig | FlagsmithTraitValue
8
+ ): traitValue is ITraitConfig {
9
+ return !!traitValue && typeof traitValue == 'object' && traitValue.value !== undefined;
10
+ }
11
+
12
+ export function generateIdentitiesData(identifier: string, traits: Traits, transient: boolean) {
13
+ const traitsGenerated = Object.entries(traits).map(([key, value]) => {
14
+ if (isTraitConfig(value)) {
15
+ return {
16
+ trait_key: key,
17
+ trait_value: value?.value,
18
+ transient: value?.transient,
19
+ };
20
+ } else {
21
+ return {
22
+ trait_key: key,
23
+ trait_value: value,
24
+ };
25
+ }
26
+ });
27
+ if (transient) {
28
+ return {
29
+ identifier: identifier,
30
+ traits: traitsGenerated,
31
+ transient: true
32
+ };
33
+ }
10
34
  return {
11
35
  identifier: identifier,
12
36
  traits: traitsGenerated
@@ -18,40 +42,28 @@ export const delay = (ms: number) =>
18
42
 
19
43
  export const retryFetch = (
20
44
  url: string,
21
- fetchOptions: RequestInit,
45
+ // built-in RequestInit type doesn't have dispatcher/agent
46
+ fetchOptions: RequestInit & { dispatcher?: Dispatcher },
22
47
  retries: number = 3,
23
- timeout: number = 10// set an overall timeout for this function
48
+ timeoutMs: number = 10, // set an overall timeout for this function
49
+ customFetch: Fetch,
24
50
  ): Promise<Response> => {
25
51
  return new Promise((resolve, reject) => {
26
52
  const retryWrapper = (n: number) => {
27
- requestWrapper()
28
- .then(res => resolve(res))
29
- .catch(async err => {
30
- if (n > 0) {
31
- await delay(1000);
32
- retryWrapper(--n);
33
- } else {
34
- reject(err);
35
- }
36
- });
37
- };
38
-
39
- const requestWrapper = (): Promise<Response> => {
40
- return new Promise((resolve, reject) => {
41
- let timeoutId: NodeJS.Timeout;
42
- if (timeout) {
43
- timeoutId = setTimeout(() => reject('error: timeout'), timeout);
44
- }
45
- return fetch(url, fetchOptions)
46
- .then(res => resolve(res))
47
- .catch(err => reject(err))
48
- .finally(() => {
49
- if (timeoutId) {
50
- clearTimeout(timeoutId);
51
- }
52
- })
53
+ customFetch(url, {
54
+ ...fetchOptions,
55
+ signal: AbortSignal.timeout(timeoutMs)
53
56
  })
54
- }
57
+ .then(res => resolve(res))
58
+ .catch(async err => {
59
+ if (n > 0) {
60
+ await delay(1000);
61
+ retryWrapper(--n);
62
+ } else {
63
+ reject(err);
64
+ }
65
+ });
66
+ };
55
67
 
56
68
  retryWrapper(retries);
57
69
  });
@@ -1,18 +1,17 @@
1
- import { readFileSync } from 'fs';
2
- import { getIdentityFeatureStates } from '../../../flagsmith-engine';
3
- import { EnvironmentModel } from '../../../flagsmith-engine/environments/models';
4
- import { buildEnvironmentModel } from '../../../flagsmith-engine/environments/util';
5
- import { IdentityModel } from '../../../flagsmith-engine/identities/models';
6
- import { buildIdentityModel } from '../../../flagsmith-engine/identities/util';
1
+ import { getIdentityFeatureStates } from '../../../flagsmith-engine/index.js';
2
+ import { EnvironmentModel } from '../../../flagsmith-engine/environments/models.js';
3
+ import { buildEnvironmentModel } from '../../../flagsmith-engine/environments/util.js';
4
+ import { IdentityModel } from '../../../flagsmith-engine/identities/models.js';
5
+ import { buildIdentityModel } from '../../../flagsmith-engine/identities/util.js';
6
+ import * as testData from '../engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json'
7
7
 
8
8
  function extractTestCases(
9
- filePath: string
9
+ data: any
10
10
  ): {
11
11
  environment: EnvironmentModel;
12
12
  identity: IdentityModel;
13
13
  response: any;
14
14
  }[] {
15
- const data = JSON.parse(readFileSync(filePath, 'utf-8'));
16
15
  const environmentModel = buildEnvironmentModel(data['environment']);
17
16
  const test_data = data['identities_and_responses'].map((test_case: any) => {
18
17
  const identity = buildIdentityModel(test_case['identity']);
@@ -27,9 +26,7 @@ function extractTestCases(
27
26
  }
28
27
 
29
28
  test('Test Engine', () => {
30
- const testCases = extractTestCases(
31
- __dirname + '/../engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json'
32
- );
29
+ const testCases = extractTestCases(testData);
33
30
  for (const testCase of testCases) {
34
31
  const engine_response = getIdentityFeatureStates(testCase.environment, testCase.identity);
35
32
  const sortedEngineFlags = engine_response.sort((a, b) =>
@@ -3,10 +3,10 @@ import {
3
3
  getEnvironmentFeatureStates,
4
4
  getIdentityFeatureState,
5
5
  getIdentityFeatureStates
6
- } from '../../../flagsmith-engine';
7
- import { CONSTANTS } from '../../../flagsmith-engine/features/constants';
8
- import { FeatureModel, FeatureStateModel } from '../../../flagsmith-engine/features/models';
9
- import { TraitModel } from '../../../flagsmith-engine/identities/traits/models';
6
+ } from '../../../flagsmith-engine/index.js';
7
+ import { CONSTANTS } from '../../../flagsmith-engine/features/constants.js';
8
+ import { FeatureModel, FeatureStateModel } from '../../../flagsmith-engine/features/models.js';
9
+ import { TraitModel } from '../../../flagsmith-engine/identities/traits/models.js';
10
10
  import {
11
11
  environment,
12
12
  environmentWithSegmentOverride,
@@ -17,7 +17,7 @@ import {
17
17
  identityInSegment,
18
18
  segmentConditionProperty,
19
19
  segmentConditionStringValue
20
- } from './utils';
20
+ } from './utils.js';
21
21
 
22
22
  test('test_identity_get_feature_state_without_any_override', () => {
23
23
  const feature_state = getIdentityFeatureState(environment(), identity(), feature1().name);
@@ -2,18 +2,18 @@ import {
2
2
  ALL_RULE,
3
3
  CONDITION_OPERATORS,
4
4
  PERCENTAGE_SPLIT,
5
- } from '../../../../flagsmith-engine/segments/constants';
6
- import {SegmentConditionModel} from '../../../../flagsmith-engine/segments/models';
7
- import {traitsMatchSegmentCondition, evaluateIdentityInSegment} from "../../../../flagsmith-engine/segments/evaluators";
8
- import {TraitModel, IdentityModel} from "../../../../flagsmith-engine";
9
- import {environment} from "../utils";
10
- import { buildSegmentModel } from '../../../../flagsmith-engine/segments/util';
11
- import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing';
5
+ } from '../../../../flagsmith-engine/segments/constants.js';
6
+ import {SegmentConditionModel} from '../../../../flagsmith-engine/segments/models.js';
7
+ import {traitsMatchSegmentCondition, evaluateIdentityInSegment} from "../../../../flagsmith-engine/segments/evaluators.js";
8
+ import {TraitModel, IdentityModel} from "../../../../flagsmith-engine/index.js";
9
+ import {environment} from "../utils.js";
10
+ import { buildSegmentModel } from '../../../../flagsmith-engine/segments/util.js';
11
+ import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
12
12
 
13
13
 
14
14
  // todo: work out how to implement this in a test function or before hook
15
- jest.mock('../../../../flagsmith-engine/utils/hashing', () => ({
16
- getHashedPercentateForObjIds: jest.fn(() => 1)
15
+ vi.mock('../../../../flagsmith-engine/utils/hashing', () => ({
16
+ getHashedPercentateForObjIds: vi.fn(() => 1)
17
17
  }));
18
18
 
19
19
 
@@ -1,5 +1,5 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
- import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing';
1
+ import { randomUUID as uuidv4 } from 'node:crypto';
2
+ import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
3
3
 
4
4
  describe('getHashedPercentageForObjIds', () => {
5
5
  it.each([
@@ -1,10 +1,7 @@
1
- import fetch from 'node-fetch';
2
- import { analyticsProcessor } from './utils';
3
-
4
- jest.mock('node-fetch', () => jest.fn());
1
+ import {analyticsProcessor, fetch} from './utils.js';
5
2
 
6
3
  afterEach(() => {
7
- jest.clearAllMocks();
4
+ vi.resetAllMocks();
8
5
  });
9
6
 
10
7
  test('test_analytics_processor_track_feature_updates_analytics_data', () => {
@@ -29,15 +26,14 @@ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', a
29
26
  aP.trackFeature("myFeature2");
30
27
  await aP.flush();
31
28
  expect(fetch).toHaveBeenCalledTimes(1);
32
- expect(fetch).toHaveBeenCalledWith('http://testUrlanalytics/flags/', {
29
+ expect(fetch).toHaveBeenCalledWith('http://testUrlanalytics/flags/', expect.objectContaining({
33
30
  body: '{"myFeature1":1,"myFeature2":1}',
34
31
  headers: { 'Content-Type': 'application/json', 'X-Environment-Key': 'test-key' },
35
32
  method: 'POST',
36
- timeout: 3000
37
- });
33
+ }));
38
34
  });
39
35
 
40
- jest.useFakeTimers()
36
+ vi.useFakeTimers()
41
37
  test('test_analytics_processor_flush_post_request_data_match_ananlytics_data_test', async () => {
42
38
  const aP = analyticsProcessor();
43
39
  aP.trackFeature("myFeature1");
@@ -45,7 +41,7 @@ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data_tes
45
41
  aP.trackFeature("myFeature2");
46
42
  expect(fetch).toHaveBeenCalledTimes(1);
47
43
  }, 15000);
48
- jest.runOnlyPendingTimers();
44
+ vi.runOnlyPendingTimers();
49
45
  });
50
46
 
51
47
  test('test_analytics_processor_flush_early_exit_if_analytics_data_is_empty', async () => {
@@ -58,7 +54,7 @@ test('test_analytics_processor_flush_early_exit_if_analytics_data_is_empty', asy
58
54
  test('errors in fetch sending analytics data are swallowed', async () => {
59
55
  // Given
60
56
  // we mock the fetch function to throw and error to mimick a network failure
61
- (fetch as jest.MockedFunction<typeof fetch>).mockRejectedValue(new Error('some error'));
57
+ fetch.mockRejectedValue('some error');
62
58
 
63
59
  // and create the processor and track a feature so there is some analytics data
64
60
  const processor = analyticsProcessor();
@@ -76,7 +72,6 @@ test('errors in fetch sending analytics data are swallowed', async () => {
76
72
  test('analytics is only flushed once even if requested concurrently', async () => {
77
73
  const processor = analyticsProcessor();
78
74
  processor.trackFeature('myFeature');
79
- // @ts-ignore
80
75
  fetch.mockImplementation(() => {
81
76
  return new Promise((resolve, _) => {
82
77
  setTimeout(resolve, 1000)
@@ -86,7 +81,7 @@ test('analytics is only flushed once even if requested concurrently', async () =
86
81
  processor.flush(),
87
82
  processor.flush(),
88
83
  ])
89
- jest.runOnlyPendingTimers();
84
+ vi.runOnlyPendingTimers();
90
85
  await flushes;
91
86
  expect(fetch).toHaveBeenCalledTimes(1)
92
87
  })
@@ -0,0 +1,41 @@
1
+ {
2
+ "traits": [
3
+ {
4
+ "id": 1,
5
+ "trait_key": "some_trait",
6
+ "trait_value": "some_value"
7
+ },
8
+ {
9
+ "id": 2,
10
+ "trait_key": "transient_key",
11
+ "trait_value": "transient_value",
12
+ "transient": true
13
+ },
14
+ {
15
+ "id": 3,
16
+ "trait_key": "explicitly_non_transient_trait",
17
+ "trait_value": "non_transient_value",
18
+ "transient": false
19
+ }
20
+ ],
21
+ "flags": [
22
+ {
23
+ "id": 1,
24
+ "feature": {
25
+ "id": 1,
26
+ "name": "some_feature",
27
+ "created_date": "2019-08-27T14:53:45.698555Z",
28
+ "initial_value": null,
29
+ "description": null,
30
+ "default_enabled": false,
31
+ "type": "STANDARD",
32
+ "project": 1
33
+ },
34
+ "feature_state_value": "some-identity-with-transient-trait-value",
35
+ "enabled": true,
36
+ "environment": 1,
37
+ "identity": null,
38
+ "feature_segment": null
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "traits": [
3
+ {
4
+ "id": 1,
5
+ "trait_key": "some_trait",
6
+ "trait_value": "some_value"
7
+ }
8
+ ],
9
+ "flags": [
10
+ {
11
+ "id": 1,
12
+ "feature": {
13
+ "id": 1,
14
+ "name": "some_feature",
15
+ "created_date": "2019-08-27T14:53:45.698555Z",
16
+ "initial_value": null,
17
+ "description": null,
18
+ "default_enabled": false,
19
+ "type": "STANDARD",
20
+ "project": 1
21
+ },
22
+ "feature_state_value": "some-transient-identity-value",
23
+ "enabled": false,
24
+ "environment": 1,
25
+ "identity": null,
26
+ "feature_segment": null
27
+ }
28
+ ]
29
+ }