flagsmith-nodejs 6.0.1 → 6.2.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 (79) hide show
  1. package/.github/workflows/conventional-commit.yml +29 -0
  2. package/.github/workflows/pull_request.yaml +1 -1
  3. package/.github/workflows/release-please.yml +18 -0
  4. package/.gitmodules +1 -0
  5. package/.husky/pre-commit +0 -0
  6. package/.prettierignore +2 -1
  7. package/.prettierrc.cjs +9 -1
  8. package/.release-please-manifest.json +1 -0
  9. package/CHANGELOG.md +552 -0
  10. package/CODEOWNERS +1 -0
  11. package/README.md +2 -3
  12. package/build/cjs/flagsmith-engine/features/util.js +3 -3
  13. package/build/cjs/flagsmith-engine/index.d.ts +1 -1
  14. package/build/cjs/flagsmith-engine/index.js +2 -1
  15. package/build/cjs/flagsmith-engine/segments/models.js +7 -7
  16. package/build/cjs/flagsmith-engine/utils/hashing/index.js +1 -1
  17. package/build/cjs/index.d.ts +4 -4
  18. package/build/cjs/index.js +3 -1
  19. package/build/cjs/sdk/analytics.d.ts +1 -1
  20. package/build/cjs/sdk/analytics.js +3 -1
  21. package/build/cjs/sdk/index.d.ts +5 -5
  22. package/build/cjs/sdk/index.js +48 -10
  23. package/build/cjs/sdk/models.d.ts +25 -0
  24. package/build/cjs/sdk/models.js +25 -0
  25. package/build/cjs/sdk/types.d.ts +14 -4
  26. package/build/cjs/sdk/utils.d.ts +5 -4
  27. package/build/cjs/sdk/utils.js +16 -3
  28. package/build/esm/flagsmith-engine/features/models.js +1 -1
  29. package/build/esm/flagsmith-engine/features/util.js +3 -3
  30. package/build/esm/flagsmith-engine/index.d.ts +1 -1
  31. package/build/esm/flagsmith-engine/index.js +1 -1
  32. package/build/esm/flagsmith-engine/segments/models.js +7 -7
  33. package/build/esm/flagsmith-engine/utils/hashing/index.js +2 -2
  34. package/build/esm/flagsmith-engine/utils/index.js +1 -1
  35. package/build/esm/index.d.ts +4 -4
  36. package/build/esm/index.js +3 -3
  37. package/build/esm/sdk/analytics.d.ts +1 -1
  38. package/build/esm/sdk/analytics.js +3 -1
  39. package/build/esm/sdk/index.d.ts +5 -5
  40. package/build/esm/sdk/index.js +48 -11
  41. package/build/esm/sdk/models.d.ts +25 -0
  42. package/build/esm/sdk/models.js +25 -0
  43. package/build/esm/sdk/types.d.ts +14 -4
  44. package/build/esm/sdk/utils.d.ts +5 -4
  45. package/build/esm/sdk/utils.js +14 -2
  46. package/flagsmith-engine/environments/util.ts +2 -2
  47. package/flagsmith-engine/features/models.ts +1 -1
  48. package/flagsmith-engine/features/util.ts +14 -14
  49. package/flagsmith-engine/identities/models.ts +1 -1
  50. package/flagsmith-engine/index.ts +1 -1
  51. package/flagsmith-engine/segments/evaluators.ts +2 -3
  52. package/flagsmith-engine/segments/models.ts +25 -15
  53. package/flagsmith-engine/utils/hashing/index.ts +3 -3
  54. package/flagsmith-engine/utils/index.ts +4 -2
  55. package/index.ts +19 -22
  56. package/package.json +1 -1
  57. package/release-please-config.json +62 -0
  58. package/sdk/analytics.ts +10 -6
  59. package/sdk/index.ts +91 -28
  60. package/sdk/models.ts +25 -0
  61. package/sdk/offline_handlers.ts +1 -1
  62. package/sdk/types.ts +17 -8
  63. package/sdk/utils.ts +21 -8
  64. package/tests/engine/e2e/engine.test.ts +2 -4
  65. package/tests/engine/unit/engine.test.ts +1 -6
  66. package/tests/engine/unit/features/models.test.ts +2 -2
  67. package/tests/engine/unit/identities/identities_builders.test.ts +1 -1
  68. package/tests/engine/unit/segments/segment_evaluators.test.ts +52 -23
  69. package/tests/engine/unit/segments/segments_model.test.ts +35 -37
  70. package/tests/engine/unit/utils/utils.test.ts +28 -30
  71. package/tests/sdk/analytics.test.ts +30 -26
  72. package/tests/sdk/flagsmith-cache.test.ts +84 -76
  73. package/tests/sdk/flagsmith-environment-flags.test.ts +121 -93
  74. package/tests/sdk/flagsmith-identity-flags.test.ts +155 -149
  75. package/tests/sdk/flagsmith.test.ts +202 -43
  76. package/tests/sdk/offline-handlers.test.ts +32 -32
  77. package/tests/sdk/polling.test.ts +0 -1
  78. package/tests/sdk/utils.ts +26 -18
  79. package/vitest.config.ts +10 -15
package/index.ts CHANGED
@@ -1,29 +1,26 @@
1
1
  export {
2
- AnalyticsProcessor,
3
- AnalyticsProcessorOptions,
4
- FlagsmithAPIError,
5
- FlagsmithClientError,
6
- EnvironmentDataPollingManager,
7
- FlagsmithCache,
8
- DefaultFlag,
9
- Flags,
10
- Flagsmith,
2
+ AnalyticsProcessor,
3
+ AnalyticsProcessorOptions,
4
+ FlagsmithAPIError,
5
+ FlagsmithClientError,
6
+ EnvironmentDataPollingManager,
7
+ FlagsmithCache,
8
+ BaseFlag,
9
+ DefaultFlag,
10
+ Flags,
11
+ Flagsmith
11
12
  } from './sdk/index.js';
12
13
 
13
- export {
14
- BaseOfflineHandler,
15
- LocalFileHandler,
16
- } from './sdk/offline_handlers.js';
14
+ export { BaseOfflineHandler, LocalFileHandler } from './sdk/offline_handlers.js';
17
15
 
18
- export {
19
- FlagsmithConfig
20
- } from './sdk/types.js'
16
+ export { FlagsmithConfig, FlagsmithValue, TraitConfig } from './sdk/types.js';
21
17
 
22
18
  export {
23
- EnvironmentModel,
24
- FeatureStateModel,
25
- IdentityModel,
26
- TraitModel,
27
- SegmentModel,
28
- OrganisationModel
19
+ EnvironmentModel,
20
+ FeatureModel,
21
+ FeatureStateModel,
22
+ IdentityModel,
23
+ TraitModel,
24
+ SegmentModel,
25
+ OrganisationModel
29
26
  } from './flagsmith-engine/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagsmith-nodejs",
3
- "version": "6.0.1",
3
+ "version": "6.2.0",
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": "./build/cjs/index.js",
6
6
  "type": "module",
@@ -0,0 +1,62 @@
1
+ {
2
+ "bootstrap-sha": "644c5c883ecbb3786507b50cea01903dc2e533bf",
3
+ "packages": {
4
+ ".": {
5
+ "release-type": "node",
6
+ "changelog-path": "CHANGELOG.md",
7
+ "bump-minor-pre-major": false,
8
+ "bump-patch-for-minor-pre-major": false,
9
+ "draft": false,
10
+ "prerelease": false,
11
+ "include-component-in-tag": false
12
+ }
13
+ },
14
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
15
+ "changelog-sections": [
16
+ {
17
+ "type": "feat",
18
+ "hidden": false,
19
+ "section": "Features"
20
+ },
21
+ {
22
+ "type": "fix",
23
+ "hidden": false,
24
+ "section": "Bug Fixes"
25
+ },
26
+ {
27
+ "type": "ci",
28
+ "hidden": false,
29
+ "section": "CI"
30
+ },
31
+ {
32
+ "type": "docs",
33
+ "hidden": false,
34
+ "section": "Docs"
35
+ },
36
+ {
37
+ "type": "deps",
38
+ "hidden": false,
39
+ "section": "Dependency Updates"
40
+ },
41
+ {
42
+ "type": "perf",
43
+ "hidden": false,
44
+ "section": "Performance Improvements"
45
+ },
46
+ {
47
+ "type": "refactor",
48
+ "hidden": false,
49
+ "section": "Refactoring"
50
+ },
51
+ {
52
+ "type": "test",
53
+ "hidden": false,
54
+ "section": "Tests"
55
+ },
56
+ {
57
+ "type": "chore",
58
+ "hidden": false,
59
+ "section": "Other"
60
+ }
61
+ ]
62
+ }
package/sdk/analytics.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import { pino, Logger } from 'pino';
2
- import { Fetch } from "./types.js";
3
- import { FlagsmithConfig } from "./types.js";
2
+ import { Fetch } from './types.js';
3
+ import { FlagsmithConfig } from './types.js';
4
+ import { getUserAgent } from './utils.js';
4
5
 
5
6
  export const ANALYTICS_ENDPOINT = './analytics/flags/';
6
7
 
7
8
  /** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
8
9
  const ANALYTICS_TIMER = 10;
9
10
 
10
- const DEFAULT_REQUEST_TIMEOUT_MS = 3000
11
+ const DEFAULT_REQUEST_TIMEOUT_MS = 3000;
11
12
 
12
13
  export interface AnalyticsProcessorOptions {
13
14
  /** URL of the Flagsmith analytics events API endpoint
@@ -20,7 +21,7 @@ export interface AnalyticsProcessorOptions {
20
21
  requestTimeoutMs?: number;
21
22
  logger?: Logger;
22
23
  /** Custom {@link fetch} implementation to use for API requests. **/
23
- fetch?: Fetch
24
+ fetch?: Fetch;
24
25
 
25
26
  /** @deprecated Use {@link analyticsUrl} instead. **/
26
27
  baseApiUrl?: string;
@@ -69,14 +70,17 @@ export class AnalyticsProcessor {
69
70
  signal: AbortSignal.timeout(this.requestTimeoutMs),
70
71
  headers: {
71
72
  'Content-Type': 'application/json',
72
- 'X-Environment-Key': this.environmentKey
73
+ 'X-Environment-Key': this.environmentKey,
74
+ 'User-Agent': getUserAgent()
73
75
  }
74
76
  });
75
77
  await this.currentFlush;
76
78
  } catch (error) {
77
79
  // We don't want failing to write analytics to cause any exceptions in the main
78
80
  // thread so we just swallow them here.
79
- this.logger.warn('Failed to post analytics to Flagsmith API. Not clearing data, will retry.')
81
+ this.logger.warn(
82
+ 'Failed to post analytics to Flagsmith API. Not clearing data, will retry.'
83
+ );
80
84
  return;
81
85
  } finally {
82
86
  this.currentFlush = undefined;
package/sdk/index.ts CHANGED
@@ -1,26 +1,35 @@
1
1
  import { Dispatcher } from 'undici-types';
2
- import { getEnvironmentFeatureStates, getIdentityFeatureStates } from '../flagsmith-engine/index.js';
2
+ import {
3
+ getEnvironmentFeatureStates,
4
+ getIdentityFeatureStates
5
+ } from '../flagsmith-engine/index.js';
3
6
  import { EnvironmentModel } from '../flagsmith-engine/index.js';
4
7
  import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js';
5
8
  import { IdentityModel } from '../flagsmith-engine/index.js';
6
9
  import { TraitModel } from '../flagsmith-engine/index.js';
7
10
 
8
- import {ANALYTICS_ENDPOINT, AnalyticsProcessor} from './analytics.js';
11
+ import { ANALYTICS_ENDPOINT, AnalyticsProcessor } from './analytics.js';
9
12
  import { BaseOfflineHandler } from './offline_handlers.js';
10
13
  import { FlagsmithAPIError } from './errors.js';
11
14
 
12
15
  import { DefaultFlag, Flags } from './models.js';
13
16
  import { EnvironmentDataPollingManager } from './polling_manager.js';
14
- import { Deferred, generateIdentitiesData, retryFetch } from './utils.js';
17
+ import { Deferred, generateIdentitiesData, getUserAgent, retryFetch } from './utils.js';
15
18
  import { SegmentModel } from '../flagsmith-engine/index.js';
16
19
  import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js';
17
- import { Fetch, FlagsmithCache, FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
20
+ import {
21
+ Fetch,
22
+ FlagsmithCache,
23
+ FlagsmithConfig,
24
+ FlagsmithTraitValue,
25
+ TraitConfig
26
+ } from './types.js';
18
27
  import { pino, Logger } from 'pino';
19
28
 
20
29
  export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js';
21
30
  export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
22
31
 
23
- export { DefaultFlag, Flags } from './models.js';
32
+ export { BaseFlag, DefaultFlag, Flags } from './models.js';
24
33
  export { EnvironmentDataPollingManager } from './polling_manager.js';
25
34
  export { FlagsmithCache, FlagsmithConfig } from './types.js';
26
35
 
@@ -50,7 +59,7 @@ const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
50
59
  * const bannerVariation: string = identityFlags.getFeatureValue('banner_flag')
51
60
  *
52
61
  * @see FlagsmithConfig
53
- */
62
+ */
54
63
  export class Flagsmith {
55
64
  environmentKey?: string = undefined;
56
65
  apiUrl?: string = undefined;
@@ -128,20 +137,23 @@ export class Flagsmith {
128
137
 
129
138
  const apiUrl = data.apiUrl || DEFAULT_API_URL;
130
139
  this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
131
- this.analyticsUrl = this.analyticsUrl || new URL(ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href
140
+ this.analyticsUrl =
141
+ this.analyticsUrl || new URL(ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href;
132
142
  this.environmentFlagsUrl = `${this.apiUrl}flags/`;
133
143
  this.identitiesUrl = `${this.apiUrl}identities/`;
134
144
  this.environmentUrl = `${this.apiUrl}environment-document/`;
135
145
 
136
146
  if (this.enableLocalEvaluation) {
137
147
  if (!this.environmentKey.startsWith('ser.')) {
138
- throw new Error('Using local evaluation requires a server-side environment key');
148
+ throw new Error(
149
+ 'Using local evaluation requires a server-side environment key'
150
+ );
139
151
  }
140
- if (this.environmentRefreshIntervalSeconds > 0){
152
+ if (this.environmentRefreshIntervalSeconds > 0) {
141
153
  this.environmentDataPollingManager = new EnvironmentDataPollingManager(
142
154
  this,
143
155
  this.environmentRefreshIntervalSeconds,
144
- this.logger,
156
+ this.logger
145
157
  );
146
158
  this.environmentDataPollingManager.start();
147
159
  }
@@ -152,8 +164,8 @@ export class Flagsmith {
152
164
  environmentKey: this.environmentKey,
153
165
  analyticsUrl: this.analyticsUrl,
154
166
  requestTimeoutMs: this.requestTimeoutMs,
155
- logger: this.logger,
156
- })
167
+ logger: this.logger
168
+ });
157
169
  }
158
170
  }
159
171
  }
@@ -174,7 +186,10 @@ export class Flagsmith {
174
186
  return await this.getEnvironmentFlagsFromApi();
175
187
  } catch (error) {
176
188
  if (!this.defaultFlagHandler) {
177
- throw new Error('getEnvironmentFlags failed and no default flag handler was provided', { cause: error });
189
+ throw new Error(
190
+ 'getEnvironmentFlags failed and no default flag handler was provided',
191
+ { cause: error }
192
+ );
178
193
  }
179
194
  this.logger.error(error, 'getEnvironmentFlags failed');
180
195
  return new Flags({
@@ -191,13 +206,13 @@ export class Flagsmith {
191
206
  *
192
207
  * @param {string} identifier a unique identifier for the identity in the current
193
208
  environment, e.g. email address, username, uuid
194
- * @param {{[key:string]:any | ITraitConfig}} traits? a dictionary of traits to add / update on the identity in
209
+ * @param {{[key:string]:any | TraitConfig}} traits? a dictionary of traits to add / update on the identity in
195
210
  Flagsmith, e.g. {"num_orders": 10} or {age: {value: 30, transient: true}}
196
211
  * @returns Flags object holding all the flags for the given identity.
197
212
  */
198
213
  async getIdentityFlags(
199
214
  identifier: string,
200
- traits?: { [key: string]: FlagsmithTraitValue | ITraitConfig },
215
+ traits?: { [key: string]: FlagsmithTraitValue | TraitConfig },
201
216
  transient: boolean = false
202
217
  ): Promise<Flags> {
203
218
  if (!identifier) {
@@ -216,7 +231,10 @@ export class Flagsmith {
216
231
  return await this.getIdentityFlagsFromApi(identifier, traits, transient);
217
232
  } catch (error) {
218
233
  if (!this.defaultFlagHandler) {
219
- throw new Error('getIdentityFlags failed and no default flag handler was provided', { cause: error })
234
+ throw new Error(
235
+ 'getIdentityFlags failed and no default flag handler was provided',
236
+ { cause: error }
237
+ );
220
238
  }
221
239
  this.logger.error(error, 'getIdentityFlags failed');
222
240
  return new Flags({
@@ -292,8 +310,8 @@ export class Flagsmith {
292
310
  async updateEnvironment(): Promise<void> {
293
311
  try {
294
312
  if (this.environmentPromise) {
295
- await this.environmentPromise
296
- return
313
+ await this.environmentPromise;
314
+ return;
297
315
  }
298
316
  const environment = await this.fetchEnvironment();
299
317
  this.onEnvironmentChange(null, environment);
@@ -311,12 +329,14 @@ export class Flagsmith {
311
329
  url: string,
312
330
  method: string,
313
331
  body?: { [key: string]: any }
314
- ): Promise<any> {
332
+ ): Promise<{ response: Response; data: any }> {
315
333
  const headers: { [key: string]: any } = { 'Content-Type': 'application/json' };
316
334
  if (this.environmentKey) {
317
335
  headers['X-Environment-Key'] = this.environmentKey as string;
318
336
  }
319
337
 
338
+ headers['User-Agent'] = getUserAgent();
339
+
320
340
  if (this.customHeaders) {
321
341
  for (const [k, v] of Object.entries(this.customHeaders)) {
322
342
  headers[k] = v;
@@ -334,7 +354,7 @@ export class Flagsmith {
334
354
  this.retries,
335
355
  this.requestTimeoutMs,
336
356
  this.requestRetryDelayMilliseconds,
337
- this.customFetch,
357
+ this.customFetch
338
358
  );
339
359
 
340
360
  if (data.status !== 200) {
@@ -343,7 +363,7 @@ export class Flagsmith {
343
363
  );
344
364
  }
345
365
 
346
- return data.json();
366
+ return { response: data, data: await data.json() };
347
367
  }
348
368
 
349
369
  /**
@@ -373,8 +393,52 @@ export class Flagsmith {
373
393
  if (!this.environmentUrl) {
374
394
  throw new Error('`apiUrl` argument is missing or invalid.');
375
395
  }
376
- const environment_data = await this.getJSONResponse(this.environmentUrl, 'GET');
377
- return buildEnvironmentModel(environment_data);
396
+ const startTime = Date.now();
397
+ const documents: any[] = [];
398
+ let url = this.environmentUrl;
399
+ let loggedWarning = false;
400
+
401
+ while (true) {
402
+ try {
403
+ if (!loggedWarning) {
404
+ const elapsedMs = Date.now() - startTime;
405
+ if (elapsedMs > this.environmentRefreshIntervalSeconds * 1000) {
406
+ this.logger.warn(
407
+ `Environment document retrieval exceeded the polling interval of ${this.environmentRefreshIntervalSeconds} seconds.`
408
+ );
409
+ loggedWarning = true;
410
+ }
411
+ }
412
+
413
+ const { response, data } = await this.getJSONResponse(url, 'GET');
414
+
415
+ documents.push(data);
416
+
417
+ const linkHeader = response.headers.get('link');
418
+ if (linkHeader) {
419
+ const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
420
+
421
+ if (nextMatch) {
422
+ const relativeUrl = decodeURIComponent(nextMatch[1]);
423
+ url = new URL(relativeUrl, this.apiUrl).href;
424
+
425
+ continue;
426
+ }
427
+ }
428
+ break;
429
+ } catch (error) {
430
+ throw error;
431
+ }
432
+ }
433
+
434
+ // Compile the document
435
+ const compiledDocument = documents[0];
436
+ for (let i = 1; i < documents.length; i++) {
437
+ compiledDocument.identity_overrides = compiledDocument.identity_overrides || [];
438
+ compiledDocument.identity_overrides.push(...(documents[i].identity_overrides || []));
439
+ }
440
+
441
+ return buildEnvironmentModel(compiledDocument);
378
442
  }
379
443
 
380
444
  private async getEnvironmentFlagsFromDocument(): Promise<Flags> {
@@ -424,7 +488,7 @@ export class Flagsmith {
424
488
  if (!this.environmentFlagsUrl) {
425
489
  throw new Error('`apiUrl` argument is missing or invalid.');
426
490
  }
427
- const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
491
+ const { data: apiFlags } = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
428
492
  const flags = Flags.fromAPIFlags({
429
493
  apiFlags: apiFlags,
430
494
  analyticsProcessor: this.analyticsProcessor,
@@ -438,14 +502,14 @@ export class Flagsmith {
438
502
 
439
503
  private async getIdentityFlagsFromApi(
440
504
  identifier: string,
441
- traits: { [key: string]: FlagsmithTraitValue | ITraitConfig },
505
+ traits: { [key: string]: FlagsmithTraitValue | TraitConfig },
442
506
  transient: boolean = false
443
507
  ) {
444
508
  if (!this.identitiesUrl) {
445
509
  throw new Error('`apiUrl` argument is missing or invalid.');
446
510
  }
447
511
  const data = generateIdentitiesData(identifier, traits, transient);
448
- const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
512
+ const { data: jsonResponse } = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
449
513
  const flags = Flags.fromAPIFlags({
450
514
  apiFlags: jsonResponse['flags'],
451
515
  analyticsProcessor: this.analyticsProcessor,
@@ -463,8 +527,7 @@ export class Flagsmith {
463
527
  traits: { key: string; value: any }[]
464
528
  ) {
465
529
  const traitModels = traits.map(trait => new TraitModel(trait.key, trait.value));
466
- let identityWithOverrides =
467
- this.identitiesWithOverridesByIdentifier?.get(identifier);
530
+ let identityWithOverrides = this.identitiesWithOverridesByIdentifier?.get(identifier);
468
531
  if (identityWithOverrides) {
469
532
  identityWithOverrides.updateTraits(traitModels);
470
533
  return identityWithOverrides;
package/sdk/models.ts CHANGED
@@ -3,9 +3,21 @@ import { AnalyticsProcessor } from './analytics.js';
3
3
 
4
4
  type FlagValue = string | number | boolean | undefined;
5
5
 
6
+ /**
7
+ * A Flagsmith feature. It has an enabled/disabled state, and an optional {@link FlagValue}.
8
+ */
6
9
  export class BaseFlag {
10
+ /**
11
+ * Indicates whether this feature is enabled.
12
+ */
7
13
  enabled: boolean;
14
+ /**
15
+ * An optional {@link FlagValue} for this feature.
16
+ */
8
17
  value: FlagValue;
18
+ /**
19
+ * If true, the state for this feature was determined by a default flag handler. See {@link DefaultFlag}.
20
+ */
9
21
  isDefault: boolean;
10
22
 
11
23
  constructor(value: FlagValue, enabled: boolean, isDefault: boolean) {
@@ -15,14 +27,27 @@ export class BaseFlag {
15
27
  }
16
28
  }
17
29
 
30
+ /**
31
+ * A {@link BaseFlag} returned by a default flag handler when flag evaluation fails.
32
+ * @see FlagsmithConfig#defaultFlagHandler
33
+ */
18
34
  export class DefaultFlag extends BaseFlag {
19
35
  constructor(value: FlagValue, enabled: boolean) {
20
36
  super(value, enabled, true);
21
37
  }
22
38
  }
23
39
 
40
+ /**
41
+ * A Flagsmith feature retrieved from a successful flag evaluation.
42
+ */
24
43
  export class Flag extends BaseFlag {
44
+ /**
45
+ * An identifier for this feature, unique in a single Flagsmith installation.
46
+ */
25
47
  featureId: number;
48
+ /**
49
+ * The programmatic name for this feature, unique per Flagsmith project.
50
+ */
26
51
  featureName: string;
27
52
 
28
53
  constructor(params: {
@@ -3,7 +3,7 @@ import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js'
3
3
  import { EnvironmentModel } from '../flagsmith-engine/environments/models.js';
4
4
 
5
5
  export class BaseOfflineHandler {
6
- getEnvironment() : EnvironmentModel {
6
+ getEnvironment(): EnvironmentModel {
7
7
  throw new Error('Not implemented');
8
8
  }
9
9
  }
package/sdk/types.ts CHANGED
@@ -3,10 +3,12 @@ import { EnvironmentModel } from '../flagsmith-engine/index.js';
3
3
  import { Dispatcher } from 'undici-types';
4
4
  import { Logger } from 'pino';
5
5
  import { BaseOfflineHandler } from './offline_handlers.js';
6
- import { Flagsmith } from './index.js'
7
-
8
- export type IFlagsmithValue<T = string | number | boolean | null> = T;
6
+ import { Flagsmith } from './index.js';
9
7
 
8
+ /**
9
+ * A concrete type for the possible values of a feature.
10
+ */
11
+ export type FlagsmithValue<T = string | number | boolean | null> = T;
10
12
 
11
13
  /**
12
14
  * Stores and retrieves {@link Flags} from a cache.
@@ -26,7 +28,7 @@ export interface FlagsmithCache {
26
28
  set(key: string, value: Flags): Promise<void>;
27
29
  }
28
30
 
29
- export type Fetch = typeof fetch
31
+ export type Fetch = typeof fetch;
30
32
 
31
33
  /**
32
34
  * The configuration options for a {@link Flagsmith} client.
@@ -53,7 +55,7 @@ export interface FlagsmithConfig {
53
55
  /**
54
56
  * Custom headers to use in all HTTP requests.
55
57
  */
56
- customHeaders?: HeadersInit
58
+ customHeaders?: HeadersInit;
57
59
  /**
58
60
  * The network request timeout duration, in seconds.
59
61
  *
@@ -96,7 +98,7 @@ export interface FlagsmithConfig {
96
98
  * const defaultHandler = () => new DefaultFlag(undefined, false)
97
99
  *
98
100
  * // Enable only VIP flags by default
99
- * const vipDefaultHandler = (key: string) => new Default(undefined, key.startsWith('vip_'))
101
+ * const vipDefaultHandler = (key: string) => new DefaultFlag(undefined, key.startsWith('vip_'))
100
102
  */
101
103
  defaultFlagHandler?: (flagKey: string) => DefaultFlag;
102
104
  cache?: FlagsmithCache;
@@ -117,9 +119,16 @@ export interface FlagsmithConfig {
117
119
  offlineHandler?: BaseOfflineHandler;
118
120
  }
119
121
 
120
- export interface ITraitConfig {
122
+ /**
123
+ * Represents the configuration for a trait in Flagsmith.
124
+ *
125
+ * @property value The {@link FlagsmithTraitValue} for this trait.
126
+ * @property [transient] Indicates whether the trait should be persisted when used in a remote flag evaluation context.
127
+ * Defaults to false.
128
+ */
129
+ export interface TraitConfig {
121
130
  value: FlagsmithTraitValue;
122
131
  transient?: boolean;
123
132
  }
124
133
 
125
- export declare type FlagsmithTraitValue = IFlagsmithValue;
134
+ export declare type FlagsmithTraitValue = FlagsmithValue;
package/sdk/utils.ts CHANGED
@@ -1,11 +1,14 @@
1
- import {Fetch, FlagsmithTraitValue, ITraitConfig} from './types.js';
2
- import {Dispatcher} from "undici-types";
1
+ import { Fetch, FlagsmithTraitValue, TraitConfig } from './types.js';
2
+ import { Dispatcher } from 'undici-types';
3
3
 
4
- type Traits = { [key: string]: ITraitConfig | FlagsmithTraitValue };
4
+ type Traits = { [key: string]: TraitConfig | FlagsmithTraitValue };
5
+
6
+ const FLAGSMITH_USER_AGENT = 'flagsmith-nodejs-sdk';
7
+ const FLAGSMITH_UNKNOWN_VERSION = 'unknown';
5
8
 
6
9
  export function isTraitConfig(
7
- traitValue: ITraitConfig | FlagsmithTraitValue
8
- ): traitValue is ITraitConfig {
10
+ traitValue: TraitConfig | FlagsmithTraitValue
11
+ ): traitValue is TraitConfig {
9
12
  return !!traitValue && typeof traitValue == 'object' && traitValue.value !== undefined;
10
13
  }
11
14
 
@@ -15,12 +18,12 @@ export function generateIdentitiesData(identifier: string, traits: Traits, trans
15
18
  return {
16
19
  trait_key: key,
17
20
  trait_value: value?.value,
18
- transient: value?.transient,
21
+ transient: value?.transient
19
22
  };
20
23
  } else {
21
24
  return {
22
25
  trait_key: key,
23
- trait_value: value,
26
+ trait_value: value
24
27
  };
25
28
  }
26
29
  });
@@ -47,7 +50,7 @@ export const retryFetch = (
47
50
  retries: number = 3,
48
51
  timeoutMs: number = 10, // set an overall timeout for this function
49
52
  retryDelayMs: number = 1000,
50
- customFetch: Fetch,
53
+ customFetch: Fetch
51
54
  ): Promise<Response> => {
52
55
  const retryWrapper = async (n: number): Promise<Response> => {
53
56
  try {
@@ -102,3 +105,13 @@ export class Deferred<T> {
102
105
  this.rejectPromise(reason);
103
106
  }
104
107
  }
108
+
109
+ export function getUserAgent(): string {
110
+ try {
111
+ const packageJson = require('../package.json');
112
+ const version = packageJson?.version;
113
+ return version ? `${FLAGSMITH_USER_AGENT}/${version}` : FLAGSMITH_UNKNOWN_VERSION;
114
+ } catch {
115
+ return FLAGSMITH_UNKNOWN_VERSION;
116
+ }
117
+ }
@@ -3,11 +3,9 @@ import { EnvironmentModel } from '../../../flagsmith-engine/environments/models.
3
3
  import { buildEnvironmentModel } from '../../../flagsmith-engine/environments/util.js';
4
4
  import { IdentityModel } from '../../../flagsmith-engine/identities/models.js';
5
5
  import { buildIdentityModel } from '../../../flagsmith-engine/identities/util.js';
6
- import * as testData from '../engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json'
6
+ import * as testData from '../engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json';
7
7
 
8
- function extractTestCases(
9
- data: any
10
- ): {
8
+ function extractTestCases(data: any): {
11
9
  environment: EnvironmentModel;
12
10
  identity: IdentityModel;
13
11
  response: any;
@@ -12,7 +12,6 @@ import {
12
12
  environmentWithSegmentOverride,
13
13
  feature1,
14
14
  getEnvironmentFeatureStateForFeature,
15
- getEnvironmentFeatureStateForFeatureByName,
16
15
  identity,
17
16
  identityInSegment,
18
17
  segmentConditionProperty,
@@ -73,11 +72,7 @@ test('test_identity_get_all_feature_states_with_traits_hideDisabledFlags', () =>
73
72
  const env = environmentWithSegmentOverride();
74
73
  env.project.hideDisabledFlags = true;
75
74
 
76
- const featureStates = getIdentityFeatureStates(
77
- env,
78
- identityInSegment(),
79
- [trait_models]
80
- );
75
+ const featureStates = getIdentityFeatureStates(env, identityInSegment(), [trait_models]);
81
76
  expect(featureStates.length).toBe(0);
82
77
  });
83
78
 
@@ -66,11 +66,11 @@ test('test_feature_state_get_value_mv_values', () => {
66
66
  const mvFeatureState = new FeatureStateModel(myFeature, true, 1);
67
67
  mvFeatureState.multivariateFeatureStateValues = [
68
68
  mvFeatureStateValue1,
69
- mvFeatureStateValue2,
69
+ mvFeatureStateValue2
70
70
  ];
71
71
 
72
72
  mvFeatureState.setValue(mvFeatureControlValue);
73
73
 
74
- expect(mvFeatureState.getValue("test")).toBe(mvFeatureValue2);
74
+ expect(mvFeatureState.getValue('test')).toBe(mvFeatureValue2);
75
75
  }
76
76
  });
@@ -47,7 +47,7 @@ test('test_build_identity_model_from_dictionary_uses_identity_feature_list_for_i
47
47
  id: 1,
48
48
  identifier: 'test-identity',
49
49
  environment_api_key: 'api-key',
50
- created_date: '2021-08-22T06:25:23.406995Z',
50
+ created_date: '2021-08-22T06:25:23.406995Z'
51
51
  };
52
52
 
53
53
  const identityModel = buildIdentityModel(identity_dict);