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/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @flagsmith/flagsmith-back-end
package/README.md CHANGED
@@ -1,5 +1,3 @@
1
- <img width="100%" src="https://github.com/Flagsmith/flagsmith/raw/main/static-files/hero.png"/>
2
-
3
1
  # Flagsmith NodeJS Client
4
2
 
5
3
  [![npm version](https://badge.fury.io/js/flagsmith-nodejs.svg)](https://badge.fury.io/js/flagsmith-nodejs)
@@ -21,10 +19,11 @@ If you encounter a bug or feature request we would like to hear about it. Before
21
19
 
22
20
  ## Testing
23
21
 
24
- To run the local tests you need to run following command beforehand:
22
+ To run the local tests you need to run following commands beforehand:
25
23
 
26
24
  ```bash
27
25
  git submodule add git@github.com:Flagsmith/engine-test-data.git tests/engine/engine-tests/engine-test-data/
26
+ git submodule update --init --recursive
28
27
  ```
29
28
 
30
29
  ## Get in touch
@@ -8,9 +8,9 @@ function buildFeatureModel(featuresModelJSON) {
8
8
  exports.buildFeatureModel = buildFeatureModel;
9
9
  function buildFeatureStateModel(featuresStateModelJSON) {
10
10
  const featureStateModel = new models_js_1.FeatureStateModel(buildFeatureModel(featuresStateModelJSON.feature), featuresStateModelJSON.enabled, featuresStateModelJSON.django_id, featuresStateModelJSON.feature_state_value, featuresStateModelJSON.featurestate_uuid);
11
- featureStateModel.featureSegment = featuresStateModelJSON.feature_segment ?
12
- buildFeatureSegment(featuresStateModelJSON.feature_segment) :
13
- undefined;
11
+ featureStateModel.featureSegment = featuresStateModelJSON.feature_segment
12
+ ? buildFeatureSegment(featuresStateModelJSON.feature_segment)
13
+ : undefined;
14
14
  const multivariateFeatureStateValues = featuresStateModelJSON.multivariate_feature_state_values
15
15
  ? featuresStateModelJSON.multivariate_feature_state_values.map((fsv) => {
16
16
  const featureOption = new models_js_1.MultivariateFeatureOptionModel(fsv.multivariate_feature_option.value, fsv.multivariate_feature_option.id);
@@ -3,7 +3,7 @@ import { FeatureStateModel } from './features/models.js';
3
3
  import { IdentityModel } from './identities/models.js';
4
4
  import { TraitModel } from './identities/traits/models.js';
5
5
  export { EnvironmentModel } from './environments/models.js';
6
- export { FeatureStateModel } from './features/models.js';
6
+ export { FeatureModel, FeatureStateModel } from './features/models.js';
7
7
  export { IdentityModel } from './identities/models.js';
8
8
  export { TraitModel } from './identities/traits/models.js';
9
9
  export { SegmentModel } from './segments/models.js';
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getEnvironmentFeatureStates = exports.getEnvironmentFeatureState = exports.getIdentityFeatureStates = exports.getIdentityFeatureState = exports.OrganisationModel = exports.SegmentModel = exports.TraitModel = exports.IdentityModel = exports.FeatureStateModel = exports.EnvironmentModel = void 0;
3
+ exports.getEnvironmentFeatureStates = exports.getEnvironmentFeatureState = exports.getIdentityFeatureStates = exports.getIdentityFeatureState = exports.OrganisationModel = exports.SegmentModel = exports.TraitModel = exports.IdentityModel = exports.FeatureStateModel = exports.FeatureModel = exports.EnvironmentModel = void 0;
4
4
  const evaluators_js_1 = require("./segments/evaluators.js");
5
5
  const errors_js_1 = require("./utils/errors.js");
6
6
  var models_js_1 = require("./environments/models.js");
7
7
  Object.defineProperty(exports, "EnvironmentModel", { enumerable: true, get: function () { return models_js_1.EnvironmentModel; } });
8
8
  var models_js_2 = require("./features/models.js");
9
+ Object.defineProperty(exports, "FeatureModel", { enumerable: true, get: function () { return models_js_2.FeatureModel; } });
9
10
  Object.defineProperty(exports, "FeatureStateModel", { enumerable: true, get: function () { return models_js_2.FeatureStateModel; } });
10
11
  var models_js_3 = require("./identities/models.js");
11
12
  Object.defineProperty(exports, "IdentityModel", { enumerable: true, get: function () { return models_js_3.IdentityModel; } });
@@ -16,7 +16,7 @@ exports.matchingFunctions = {
16
16
  [constants_js_1.CONDITION_OPERATORS.LESS_THAN]: (thisValue, otherValue) => thisValue > otherValue,
17
17
  [constants_js_1.CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => thisValue >= otherValue,
18
18
  [constants_js_1.CONDITION_OPERATORS.NOT_EQUAL]: (thisValue, otherValue) => thisValue != otherValue,
19
- [constants_js_1.CONDITION_OPERATORS.CONTAINS]: (thisValue, otherValue) => !!otherValue && otherValue.includes(thisValue),
19
+ [constants_js_1.CONDITION_OPERATORS.CONTAINS]: (thisValue, otherValue) => !!otherValue && otherValue.includes(thisValue)
20
20
  };
21
21
  exports.semverMatchingFunction = {
22
22
  ...exports.matchingFunctions,
@@ -24,9 +24,9 @@ exports.semverMatchingFunction = {
24
24
  [constants_js_1.CONDITION_OPERATORS.GREATER_THAN]: (thisValue, otherValue) => semver.gt(otherValue, thisValue),
25
25
  [constants_js_1.CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE]: (thisValue, otherValue) => semver.gte(otherValue, thisValue),
26
26
  [constants_js_1.CONDITION_OPERATORS.LESS_THAN]: (thisValue, otherValue) => semver.gt(thisValue, otherValue),
27
- [constants_js_1.CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => semver.gte(thisValue, otherValue),
27
+ [constants_js_1.CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => semver.gte(thisValue, otherValue)
28
28
  };
29
- const getMatchingFunctions = (semver) => (semver ? exports.semverMatchingFunction : exports.matchingFunctions);
29
+ const getMatchingFunctions = (semver) => semver ? exports.semverMatchingFunction : exports.matchingFunctions;
30
30
  exports.getMatchingFunctions = getMatchingFunctions;
31
31
  class SegmentConditionModel {
32
32
  EXCEPTION_OPERATOR_METHODS = {
@@ -46,9 +46,9 @@ class SegmentConditionModel {
46
46
  matchesTraitValue(traitValue) {
47
47
  const evaluators = {
48
48
  evaluateNotContains: (traitValue) => {
49
- return typeof traitValue == "string" &&
49
+ return (typeof traitValue == 'string' &&
50
50
  !!this.value &&
51
- !traitValue.includes(this.value?.toString());
51
+ !traitValue.includes(this.value?.toString()));
52
52
  },
53
53
  evaluateRegex: (traitValue) => {
54
54
  return !!this.value && !!traitValue?.toString().match(new RegExp(this.value));
@@ -57,13 +57,13 @@ class SegmentConditionModel {
57
57
  if (isNaN(parseFloat(traitValue)) || !this.value) {
58
58
  return false;
59
59
  }
60
- const parts = (this.value).split("|");
60
+ const parts = this.value.split('|');
61
61
  const [divisor, reminder] = [parseFloat(parts[0]), parseFloat(parts[1])];
62
62
  return traitValue % divisor === reminder;
63
63
  },
64
64
  evaluateIn: (traitValue) => {
65
65
  return this.value?.split(',').includes(traitValue.toString());
66
- },
66
+ }
67
67
  };
68
68
  // TODO: move this logic to the evaluator module
69
69
  if (this.EXCEPTION_OPERATOR_METHODS[this.operator]) {
@@ -17,7 +17,7 @@ function getHashedPercentateForObjIds(objectIds, iterations = 1) {
17
17
  let toHash = makeRepeated(objectIds, iterations).join(',');
18
18
  const hashedValue = md5(toHash);
19
19
  const hashedInt = BigInt('0x' + hashedValue);
20
- const value = (Number((hashedInt % 9999n)) / 9998.0) * 100;
20
+ const value = (Number(hashedInt % 9999n) / 9998.0) * 100;
21
21
  // we ignore this for it's nearly impossible use case to catch
22
22
  /* istanbul ignore next */
23
23
  if (value === 100) {
@@ -1,4 +1,4 @@
1
- export { AnalyticsProcessor, AnalyticsProcessorOptions, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, FlagsmithCache, DefaultFlag, Flags, Flagsmith, } from './sdk/index.js';
2
- export { BaseOfflineHandler, LocalFileHandler, } from './sdk/offline_handlers.js';
3
- export { FlagsmithConfig } from './sdk/types.js';
4
- export { EnvironmentModel, FeatureStateModel, IdentityModel, TraitModel, SegmentModel, OrganisationModel } from './flagsmith-engine/index.js';
1
+ export { AnalyticsProcessor, AnalyticsProcessorOptions, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, FlagsmithCache, BaseFlag, DefaultFlag, Flags, Flagsmith } from './sdk/index.js';
2
+ export { BaseOfflineHandler, LocalFileHandler } from './sdk/offline_handlers.js';
3
+ export { FlagsmithConfig, FlagsmithValue, TraitConfig } from './sdk/types.js';
4
+ export { EnvironmentModel, FeatureModel, FeatureStateModel, IdentityModel, TraitModel, SegmentModel, OrganisationModel } from './flagsmith-engine/index.js';
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OrganisationModel = exports.SegmentModel = exports.TraitModel = exports.IdentityModel = exports.FeatureStateModel = exports.EnvironmentModel = exports.LocalFileHandler = exports.BaseOfflineHandler = exports.Flagsmith = exports.Flags = exports.DefaultFlag = exports.EnvironmentDataPollingManager = exports.FlagsmithClientError = exports.FlagsmithAPIError = exports.AnalyticsProcessor = void 0;
3
+ exports.OrganisationModel = exports.SegmentModel = exports.TraitModel = exports.IdentityModel = exports.FeatureStateModel = exports.FeatureModel = exports.EnvironmentModel = exports.LocalFileHandler = exports.BaseOfflineHandler = exports.Flagsmith = exports.Flags = exports.DefaultFlag = exports.BaseFlag = exports.EnvironmentDataPollingManager = exports.FlagsmithClientError = exports.FlagsmithAPIError = exports.AnalyticsProcessor = void 0;
4
4
  var index_js_1 = require("./sdk/index.js");
5
5
  Object.defineProperty(exports, "AnalyticsProcessor", { enumerable: true, get: function () { return index_js_1.AnalyticsProcessor; } });
6
6
  Object.defineProperty(exports, "FlagsmithAPIError", { enumerable: true, get: function () { return index_js_1.FlagsmithAPIError; } });
7
7
  Object.defineProperty(exports, "FlagsmithClientError", { enumerable: true, get: function () { return index_js_1.FlagsmithClientError; } });
8
8
  Object.defineProperty(exports, "EnvironmentDataPollingManager", { enumerable: true, get: function () { return index_js_1.EnvironmentDataPollingManager; } });
9
+ Object.defineProperty(exports, "BaseFlag", { enumerable: true, get: function () { return index_js_1.BaseFlag; } });
9
10
  Object.defineProperty(exports, "DefaultFlag", { enumerable: true, get: function () { return index_js_1.DefaultFlag; } });
10
11
  Object.defineProperty(exports, "Flags", { enumerable: true, get: function () { return index_js_1.Flags; } });
11
12
  Object.defineProperty(exports, "Flagsmith", { enumerable: true, get: function () { return index_js_1.Flagsmith; } });
@@ -14,6 +15,7 @@ Object.defineProperty(exports, "BaseOfflineHandler", { enumerable: true, get: fu
14
15
  Object.defineProperty(exports, "LocalFileHandler", { enumerable: true, get: function () { return offline_handlers_js_1.LocalFileHandler; } });
15
16
  var index_js_2 = require("./flagsmith-engine/index.js");
16
17
  Object.defineProperty(exports, "EnvironmentModel", { enumerable: true, get: function () { return index_js_2.EnvironmentModel; } });
18
+ Object.defineProperty(exports, "FeatureModel", { enumerable: true, get: function () { return index_js_2.FeatureModel; } });
17
19
  Object.defineProperty(exports, "FeatureStateModel", { enumerable: true, get: function () { return index_js_2.FeatureStateModel; } });
18
20
  Object.defineProperty(exports, "IdentityModel", { enumerable: true, get: function () { return index_js_2.IdentityModel; } });
19
21
  Object.defineProperty(exports, "TraitModel", { enumerable: true, get: function () { return index_js_2.TraitModel; } });
@@ -1,5 +1,5 @@
1
1
  import { Logger } from 'pino';
2
- import { Fetch } from "./types.js";
2
+ import { Fetch } from './types.js';
3
3
  export declare const ANALYTICS_ENDPOINT = "./analytics/flags/";
4
4
  export interface AnalyticsProcessorOptions {
5
5
  /** URL of the Flagsmith analytics events API endpoint
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AnalyticsProcessor = exports.ANALYTICS_ENDPOINT = void 0;
4
4
  const pino_1 = require("pino");
5
+ const utils_js_1 = require("./utils.js");
5
6
  exports.ANALYTICS_ENDPOINT = './analytics/flags/';
6
7
  /** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
7
8
  const ANALYTICS_TIMER = 10;
@@ -47,7 +48,8 @@ class AnalyticsProcessor {
47
48
  signal: AbortSignal.timeout(this.requestTimeoutMs),
48
49
  headers: {
49
50
  'Content-Type': 'application/json',
50
- 'X-Environment-Key': this.environmentKey
51
+ 'X-Environment-Key': this.environmentKey,
52
+ 'User-Agent': (0, utils_js_1.getUserAgent)()
51
53
  }
52
54
  });
53
55
  await this.currentFlush;
@@ -5,10 +5,10 @@ import { BaseOfflineHandler } from './offline_handlers.js';
5
5
  import { DefaultFlag, Flags } from './models.js';
6
6
  import { EnvironmentDataPollingManager } from './polling_manager.js';
7
7
  import { SegmentModel } from '../flagsmith-engine/index.js';
8
- import { FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
8
+ import { FlagsmithConfig, FlagsmithTraitValue, TraitConfig } from './types.js';
9
9
  export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js';
10
10
  export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
11
- export { DefaultFlag, Flags } from './models.js';
11
+ export { BaseFlag, DefaultFlag, Flags } from './models.js';
12
12
  export { EnvironmentDataPollingManager } from './polling_manager.js';
13
13
  export { FlagsmithCache, FlagsmithConfig } from './types.js';
14
14
  /**
@@ -34,7 +34,7 @@ export { FlagsmithCache, FlagsmithConfig } from './types.js';
34
34
  * const bannerVariation: string = identityFlags.getFeatureValue('banner_flag')
35
35
  *
36
36
  * @see FlagsmithConfig
37
- */
37
+ */
38
38
  export declare class Flagsmith {
39
39
  environmentKey?: string;
40
40
  apiUrl?: string;
@@ -84,12 +84,12 @@ export declare class Flagsmith {
84
84
  *
85
85
  * @param {string} identifier a unique identifier for the identity in the current
86
86
  environment, e.g. email address, username, uuid
87
- * @param {{[key:string]:any | ITraitConfig}} traits? a dictionary of traits to add / update on the identity in
87
+ * @param {{[key:string]:any | TraitConfig}} traits? a dictionary of traits to add / update on the identity in
88
88
  Flagsmith, e.g. {"num_orders": 10} or {age: {value: 30, transient: true}}
89
89
  * @returns Flags object holding all the flags for the given identity.
90
90
  */
91
91
  getIdentityFlags(identifier: string, traits?: {
92
- [key: string]: FlagsmithTraitValue | ITraitConfig;
92
+ [key: string]: FlagsmithTraitValue | TraitConfig;
93
93
  }, transient?: boolean): Promise<Flags>;
94
94
  /**
95
95
  * Get the segments for the current environment for a given identity. Will also
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Flagsmith = exports.EnvironmentDataPollingManager = exports.Flags = exports.DefaultFlag = exports.FlagsmithClientError = exports.FlagsmithAPIError = exports.AnalyticsProcessor = void 0;
3
+ exports.Flagsmith = exports.EnvironmentDataPollingManager = exports.Flags = exports.DefaultFlag = exports.BaseFlag = exports.FlagsmithClientError = exports.FlagsmithAPIError = exports.AnalyticsProcessor = void 0;
4
4
  const index_js_1 = require("../flagsmith-engine/index.js");
5
5
  const util_js_1 = require("../flagsmith-engine/environments/util.js");
6
6
  const index_js_2 = require("../flagsmith-engine/index.js");
@@ -18,6 +18,7 @@ var errors_js_2 = require("./errors.js");
18
18
  Object.defineProperty(exports, "FlagsmithAPIError", { enumerable: true, get: function () { return errors_js_2.FlagsmithAPIError; } });
19
19
  Object.defineProperty(exports, "FlagsmithClientError", { enumerable: true, get: function () { return errors_js_2.FlagsmithClientError; } });
20
20
  var models_js_2 = require("./models.js");
21
+ Object.defineProperty(exports, "BaseFlag", { enumerable: true, get: function () { return models_js_2.BaseFlag; } });
21
22
  Object.defineProperty(exports, "DefaultFlag", { enumerable: true, get: function () { return models_js_2.DefaultFlag; } });
22
23
  Object.defineProperty(exports, "Flags", { enumerable: true, get: function () { return models_js_2.Flags; } });
23
24
  var polling_manager_js_2 = require("./polling_manager.js");
@@ -47,7 +48,7 @@ const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
47
48
  * const bannerVariation: string = identityFlags.getFeatureValue('banner_flag')
48
49
  *
49
50
  * @see FlagsmithConfig
50
- */
51
+ */
51
52
  class Flagsmith {
52
53
  environmentKey = undefined;
53
54
  apiUrl = undefined;
@@ -116,7 +117,8 @@ class Flagsmith {
116
117
  }
117
118
  const apiUrl = data.apiUrl || DEFAULT_API_URL;
118
119
  this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
119
- this.analyticsUrl = this.analyticsUrl || new URL(analytics_js_1.ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href;
120
+ this.analyticsUrl =
121
+ this.analyticsUrl || new URL(analytics_js_1.ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href;
120
122
  this.environmentFlagsUrl = `${this.apiUrl}flags/`;
121
123
  this.identitiesUrl = `${this.apiUrl}identities/`;
122
124
  this.environmentUrl = `${this.apiUrl}environment-document/`;
@@ -134,7 +136,7 @@ class Flagsmith {
134
136
  environmentKey: this.environmentKey,
135
137
  analyticsUrl: this.analyticsUrl,
136
138
  requestTimeoutMs: this.requestTimeoutMs,
137
- logger: this.logger,
139
+ logger: this.logger
138
140
  });
139
141
  }
140
142
  }
@@ -173,7 +175,7 @@ class Flagsmith {
173
175
  *
174
176
  * @param {string} identifier a unique identifier for the identity in the current
175
177
  environment, e.g. email address, username, uuid
176
- * @param {{[key:string]:any | ITraitConfig}} traits? a dictionary of traits to add / update on the identity in
178
+ * @param {{[key:string]:any | TraitConfig}} traits? a dictionary of traits to add / update on the identity in
177
179
  Flagsmith, e.g. {"num_orders": 10} or {age: {value: 30, transient: true}}
178
180
  * @returns Flags object holding all the flags for the given identity.
179
181
  */
@@ -277,6 +279,7 @@ class Flagsmith {
277
279
  if (this.environmentKey) {
278
280
  headers['X-Environment-Key'] = this.environmentKey;
279
281
  }
282
+ headers['User-Agent'] = (0, utils_js_1.getUserAgent)();
280
283
  if (this.customHeaders) {
281
284
  for (const [k, v] of Object.entries(this.customHeaders)) {
282
285
  headers[k] = v;
@@ -291,7 +294,7 @@ class Flagsmith {
291
294
  if (data.status !== 200) {
292
295
  throw new errors_js_1.FlagsmithAPIError(`Invalid request made to Flagsmith API. Response status code: ${data.status}`);
293
296
  }
294
- return data.json();
297
+ return { response: data, data: await data.json() };
295
298
  }
296
299
  /**
297
300
  * This promise ensures that the environment is retrieved before attempting to locally evaluate.
@@ -318,8 +321,43 @@ class Flagsmith {
318
321
  if (!this.environmentUrl) {
319
322
  throw new Error('`apiUrl` argument is missing or invalid.');
320
323
  }
321
- const environment_data = await this.getJSONResponse(this.environmentUrl, 'GET');
322
- return (0, util_js_1.buildEnvironmentModel)(environment_data);
324
+ const startTime = Date.now();
325
+ const documents = [];
326
+ let url = this.environmentUrl;
327
+ let loggedWarning = false;
328
+ while (true) {
329
+ try {
330
+ if (!loggedWarning) {
331
+ const elapsedMs = Date.now() - startTime;
332
+ if (elapsedMs > this.environmentRefreshIntervalSeconds * 1000) {
333
+ this.logger.warn(`Environment document retrieval exceeded the polling interval of ${this.environmentRefreshIntervalSeconds} seconds.`);
334
+ loggedWarning = true;
335
+ }
336
+ }
337
+ const { response, data } = await this.getJSONResponse(url, 'GET');
338
+ documents.push(data);
339
+ const linkHeader = response.headers.get('link');
340
+ if (linkHeader) {
341
+ const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
342
+ if (nextMatch) {
343
+ const relativeUrl = decodeURIComponent(nextMatch[1]);
344
+ url = new URL(relativeUrl, this.apiUrl).href;
345
+ continue;
346
+ }
347
+ }
348
+ break;
349
+ }
350
+ catch (error) {
351
+ throw error;
352
+ }
353
+ }
354
+ // Compile the document
355
+ const compiledDocument = documents[0];
356
+ for (let i = 1; i < documents.length; i++) {
357
+ compiledDocument.identity_overrides = compiledDocument.identity_overrides || [];
358
+ compiledDocument.identity_overrides.push(...(documents[i].identity_overrides || []));
359
+ }
360
+ return (0, util_js_1.buildEnvironmentModel)(compiledDocument);
323
361
  }
324
362
  async getEnvironmentFlagsFromDocument() {
325
363
  const environment = await this.getEnvironment();
@@ -355,7 +393,7 @@ class Flagsmith {
355
393
  if (!this.environmentFlagsUrl) {
356
394
  throw new Error('`apiUrl` argument is missing or invalid.');
357
395
  }
358
- const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
396
+ const { data: apiFlags } = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
359
397
  const flags = models_js_1.Flags.fromAPIFlags({
360
398
  apiFlags: apiFlags,
361
399
  analyticsProcessor: this.analyticsProcessor,
@@ -371,7 +409,7 @@ class Flagsmith {
371
409
  throw new Error('`apiUrl` argument is missing or invalid.');
372
410
  }
373
411
  const data = (0, utils_js_1.generateIdentitiesData)(identifier, traits, transient);
374
- const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
412
+ const { data: jsonResponse } = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
375
413
  const flags = models_js_1.Flags.fromAPIFlags({
376
414
  apiFlags: jsonResponse['flags'],
377
415
  analyticsProcessor: this.analyticsProcessor,
@@ -1,17 +1,42 @@
1
1
  import { FeatureStateModel } from '../flagsmith-engine/features/models.js';
2
2
  import { AnalyticsProcessor } from './analytics.js';
3
3
  type FlagValue = string | number | boolean | undefined;
4
+ /**
5
+ * A Flagsmith feature. It has an enabled/disabled state, and an optional {@link FlagValue}.
6
+ */
4
7
  export declare class BaseFlag {
8
+ /**
9
+ * Indicates whether this feature is enabled.
10
+ */
5
11
  enabled: boolean;
12
+ /**
13
+ * An optional {@link FlagValue} for this feature.
14
+ */
6
15
  value: FlagValue;
16
+ /**
17
+ * If true, the state for this feature was determined by a default flag handler. See {@link DefaultFlag}.
18
+ */
7
19
  isDefault: boolean;
8
20
  constructor(value: FlagValue, enabled: boolean, isDefault: boolean);
9
21
  }
22
+ /**
23
+ * A {@link BaseFlag} returned by a default flag handler when flag evaluation fails.
24
+ * @see FlagsmithConfig#defaultFlagHandler
25
+ */
10
26
  export declare class DefaultFlag extends BaseFlag {
11
27
  constructor(value: FlagValue, enabled: boolean);
12
28
  }
29
+ /**
30
+ * A Flagsmith feature retrieved from a successful flag evaluation.
31
+ */
13
32
  export declare class Flag extends BaseFlag {
33
+ /**
34
+ * An identifier for this feature, unique in a single Flagsmith installation.
35
+ */
14
36
  featureId: number;
37
+ /**
38
+ * The programmatic name for this feature, unique per Flagsmith project.
39
+ */
15
40
  featureName: string;
16
41
  constructor(params: {
17
42
  value: FlagValue;
@@ -1,9 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Flags = exports.Flag = exports.DefaultFlag = exports.BaseFlag = void 0;
4
+ /**
5
+ * A Flagsmith feature. It has an enabled/disabled state, and an optional {@link FlagValue}.
6
+ */
4
7
  class BaseFlag {
8
+ /**
9
+ * Indicates whether this feature is enabled.
10
+ */
5
11
  enabled;
12
+ /**
13
+ * An optional {@link FlagValue} for this feature.
14
+ */
6
15
  value;
16
+ /**
17
+ * If true, the state for this feature was determined by a default flag handler. See {@link DefaultFlag}.
18
+ */
7
19
  isDefault;
8
20
  constructor(value, enabled, isDefault) {
9
21
  this.value = value;
@@ -12,14 +24,27 @@ class BaseFlag {
12
24
  }
13
25
  }
14
26
  exports.BaseFlag = BaseFlag;
27
+ /**
28
+ * A {@link BaseFlag} returned by a default flag handler when flag evaluation fails.
29
+ * @see FlagsmithConfig#defaultFlagHandler
30
+ */
15
31
  class DefaultFlag extends BaseFlag {
16
32
  constructor(value, enabled) {
17
33
  super(value, enabled, true);
18
34
  }
19
35
  }
20
36
  exports.DefaultFlag = DefaultFlag;
37
+ /**
38
+ * A Flagsmith feature retrieved from a successful flag evaluation.
39
+ */
21
40
  class Flag extends BaseFlag {
41
+ /**
42
+ * An identifier for this feature, unique in a single Flagsmith installation.
43
+ */
22
44
  featureId;
45
+ /**
46
+ * The programmatic name for this feature, unique per Flagsmith project.
47
+ */
23
48
  featureName;
24
49
  constructor(params) {
25
50
  super(params.value, params.enabled, !!params.isDefault);
@@ -3,7 +3,10 @@ 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
- export type IFlagsmithValue<T = string | number | boolean | null> = T;
6
+ /**
7
+ * A concrete type for the possible values of a feature.
8
+ */
9
+ export type FlagsmithValue<T = string | number | boolean | null> = T;
7
10
  /**
8
11
  * Stores and retrieves {@link Flags} from a cache.
9
12
  */
@@ -89,7 +92,7 @@ export interface FlagsmithConfig {
89
92
  * const defaultHandler = () => new DefaultFlag(undefined, false)
90
93
  *
91
94
  * // Enable only VIP flags by default
92
- * const vipDefaultHandler = (key: string) => new Default(undefined, key.startsWith('vip_'))
95
+ * const vipDefaultHandler = (key: string) => new DefaultFlag(undefined, key.startsWith('vip_'))
93
96
  */
94
97
  defaultFlagHandler?: (flagKey: string) => DefaultFlag;
95
98
  cache?: FlagsmithCache;
@@ -109,8 +112,15 @@ export interface FlagsmithConfig {
109
112
  */
110
113
  offlineHandler?: BaseOfflineHandler;
111
114
  }
112
- export interface ITraitConfig {
115
+ /**
116
+ * Represents the configuration for a trait in Flagsmith.
117
+ *
118
+ * @property value The {@link FlagsmithTraitValue} for this trait.
119
+ * @property [transient] Indicates whether the trait should be persisted when used in a remote flag evaluation context.
120
+ * Defaults to false.
121
+ */
122
+ export interface TraitConfig {
113
123
  value: FlagsmithTraitValue;
114
124
  transient?: boolean;
115
125
  }
116
- export declare type FlagsmithTraitValue = IFlagsmithValue;
126
+ export declare type FlagsmithTraitValue = FlagsmithValue;
@@ -1,9 +1,9 @@
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
  type Traits = {
4
- [key: string]: ITraitConfig | FlagsmithTraitValue;
4
+ [key: string]: TraitConfig | FlagsmithTraitValue;
5
5
  };
6
- export declare function isTraitConfig(traitValue: ITraitConfig | FlagsmithTraitValue): traitValue is ITraitConfig;
6
+ export declare function isTraitConfig(traitValue: TraitConfig | FlagsmithTraitValue): traitValue is TraitConfig;
7
7
  export declare function generateIdentitiesData(identifier: string, traits: Traits, transient: boolean): {
8
8
  identifier: string;
9
9
  traits: ({
@@ -56,4 +56,5 @@ export declare class Deferred<T> {
56
56
  resolve(value: T | PromiseLike<T>): void;
57
57
  reject(reason?: unknown): void;
58
58
  }
59
+ export declare function getUserAgent(): string;
59
60
  export {};
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Deferred = exports.retryFetch = exports.delay = exports.generateIdentitiesData = exports.isTraitConfig = void 0;
3
+ exports.getUserAgent = exports.Deferred = exports.retryFetch = exports.delay = exports.generateIdentitiesData = exports.isTraitConfig = void 0;
4
+ const FLAGSMITH_USER_AGENT = 'flagsmith-nodejs-sdk';
5
+ const FLAGSMITH_UNKNOWN_VERSION = 'unknown';
4
6
  function isTraitConfig(traitValue) {
5
7
  return !!traitValue && typeof traitValue == 'object' && traitValue.value !== undefined;
6
8
  }
@@ -11,13 +13,13 @@ function generateIdentitiesData(identifier, traits, transient) {
11
13
  return {
12
14
  trait_key: key,
13
15
  trait_value: value?.value,
14
- transient: value?.transient,
16
+ transient: value?.transient
15
17
  };
16
18
  }
17
19
  else {
18
20
  return {
19
21
  trait_key: key,
20
- trait_value: value,
22
+ trait_value: value
21
23
  };
22
24
  }
23
25
  });
@@ -93,3 +95,14 @@ class Deferred {
93
95
  }
94
96
  }
95
97
  exports.Deferred = Deferred;
98
+ function getUserAgent() {
99
+ try {
100
+ const packageJson = require('../package.json');
101
+ const version = packageJson?.version;
102
+ return version ? `${FLAGSMITH_USER_AGENT}/${version}` : FLAGSMITH_UNKNOWN_VERSION;
103
+ }
104
+ catch {
105
+ return FLAGSMITH_UNKNOWN_VERSION;
106
+ }
107
+ }
108
+ exports.getUserAgent = getUserAgent;
@@ -1,4 +1,4 @@
1
- import { randomUUID as uuidv4 } from "node:crypto";
1
+ import { randomUUID as uuidv4 } from 'node:crypto';
2
2
  import { getHashedPercentateForObjIds } from '../utils/hashing/index.js';
3
3
  export class FeatureModel {
4
4
  id;
@@ -4,9 +4,9 @@ export function buildFeatureModel(featuresModelJSON) {
4
4
  }
5
5
  export function buildFeatureStateModel(featuresStateModelJSON) {
6
6
  const featureStateModel = new FeatureStateModel(buildFeatureModel(featuresStateModelJSON.feature), featuresStateModelJSON.enabled, featuresStateModelJSON.django_id, featuresStateModelJSON.feature_state_value, featuresStateModelJSON.featurestate_uuid);
7
- featureStateModel.featureSegment = featuresStateModelJSON.feature_segment ?
8
- buildFeatureSegment(featuresStateModelJSON.feature_segment) :
9
- undefined;
7
+ featureStateModel.featureSegment = featuresStateModelJSON.feature_segment
8
+ ? buildFeatureSegment(featuresStateModelJSON.feature_segment)
9
+ : undefined;
10
10
  const multivariateFeatureStateValues = featuresStateModelJSON.multivariate_feature_state_values
11
11
  ? featuresStateModelJSON.multivariate_feature_state_values.map((fsv) => {
12
12
  const featureOption = new MultivariateFeatureOptionModel(fsv.multivariate_feature_option.value, fsv.multivariate_feature_option.id);
@@ -3,7 +3,7 @@ import { FeatureStateModel } from './features/models.js';
3
3
  import { IdentityModel } from './identities/models.js';
4
4
  import { TraitModel } from './identities/traits/models.js';
5
5
  export { EnvironmentModel } from './environments/models.js';
6
- export { FeatureStateModel } from './features/models.js';
6
+ export { FeatureModel, FeatureStateModel } from './features/models.js';
7
7
  export { IdentityModel } from './identities/models.js';
8
8
  export { TraitModel } from './identities/traits/models.js';
9
9
  export { SegmentModel } from './segments/models.js';
@@ -1,7 +1,7 @@
1
1
  import { getIdentitySegments } from './segments/evaluators.js';
2
2
  import { FeatureStateNotFound } from './utils/errors.js';
3
3
  export { EnvironmentModel } from './environments/models.js';
4
- export { FeatureStateModel } from './features/models.js';
4
+ export { FeatureModel, FeatureStateModel } from './features/models.js';
5
5
  export { IdentityModel } from './identities/models.js';
6
6
  export { TraitModel } from './identities/traits/models.js';
7
7
  export { SegmentModel } from './segments/models.js';
@@ -11,7 +11,7 @@ export const matchingFunctions = {
11
11
  [CONDITION_OPERATORS.LESS_THAN]: (thisValue, otherValue) => thisValue > otherValue,
12
12
  [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => thisValue >= otherValue,
13
13
  [CONDITION_OPERATORS.NOT_EQUAL]: (thisValue, otherValue) => thisValue != otherValue,
14
- [CONDITION_OPERATORS.CONTAINS]: (thisValue, otherValue) => !!otherValue && otherValue.includes(thisValue),
14
+ [CONDITION_OPERATORS.CONTAINS]: (thisValue, otherValue) => !!otherValue && otherValue.includes(thisValue)
15
15
  };
16
16
  export const semverMatchingFunction = {
17
17
  ...matchingFunctions,
@@ -19,9 +19,9 @@ export const semverMatchingFunction = {
19
19
  [CONDITION_OPERATORS.GREATER_THAN]: (thisValue, otherValue) => semver.gt(otherValue, thisValue),
20
20
  [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE]: (thisValue, otherValue) => semver.gte(otherValue, thisValue),
21
21
  [CONDITION_OPERATORS.LESS_THAN]: (thisValue, otherValue) => semver.gt(thisValue, otherValue),
22
- [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => semver.gte(thisValue, otherValue),
22
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => semver.gte(thisValue, otherValue)
23
23
  };
24
- export const getMatchingFunctions = (semver) => (semver ? semverMatchingFunction : matchingFunctions);
24
+ export const getMatchingFunctions = (semver) => semver ? semverMatchingFunction : matchingFunctions;
25
25
  export class SegmentConditionModel {
26
26
  EXCEPTION_OPERATOR_METHODS = {
27
27
  [NOT_CONTAINS]: 'evaluateNotContains',
@@ -40,9 +40,9 @@ export class SegmentConditionModel {
40
40
  matchesTraitValue(traitValue) {
41
41
  const evaluators = {
42
42
  evaluateNotContains: (traitValue) => {
43
- return typeof traitValue == "string" &&
43
+ return (typeof traitValue == 'string' &&
44
44
  !!this.value &&
45
- !traitValue.includes(this.value?.toString());
45
+ !traitValue.includes(this.value?.toString()));
46
46
  },
47
47
  evaluateRegex: (traitValue) => {
48
48
  return !!this.value && !!traitValue?.toString().match(new RegExp(this.value));
@@ -51,13 +51,13 @@ export class SegmentConditionModel {
51
51
  if (isNaN(parseFloat(traitValue)) || !this.value) {
52
52
  return false;
53
53
  }
54
- const parts = (this.value).split("|");
54
+ const parts = this.value.split('|');
55
55
  const [divisor, reminder] = [parseFloat(parts[0]), parseFloat(parts[1])];
56
56
  return traitValue % divisor === reminder;
57
57
  },
58
58
  evaluateIn: (traitValue) => {
59
59
  return this.value?.split(',').includes(traitValue.toString());
60
- },
60
+ }
61
61
  };
62
62
  // TODO: move this logic to the evaluator module
63
63
  if (this.EXCEPTION_OPERATOR_METHODS[this.operator]) {