flagsmith-nodejs 6.0.0 → 6.1.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.
- package/.github/workflows/publish.yml +17 -17
- package/.github/workflows/pull_request.yaml +33 -33
- package/.husky/pre-commit +0 -0
- package/.prettierignore +2 -1
- package/README.md +2 -1
- package/build/cjs/flagsmith-engine/features/util.js +3 -3
- package/build/cjs/flagsmith-engine/index.d.ts +1 -1
- package/build/cjs/flagsmith-engine/index.js +2 -1
- package/build/cjs/flagsmith-engine/segments/models.js +7 -7
- package/build/cjs/flagsmith-engine/utils/hashing/index.js +1 -1
- package/build/cjs/index.d.ts +4 -4
- package/build/cjs/index.js +3 -1
- package/build/cjs/sdk/analytics.d.ts +1 -1
- package/build/cjs/sdk/index.d.ts +5 -5
- package/build/cjs/sdk/index.js +7 -5
- package/build/cjs/sdk/models.d.ts +32 -5
- package/build/cjs/sdk/models.js +25 -0
- package/build/cjs/sdk/types.d.ts +14 -4
- package/build/cjs/sdk/utils.d.ts +4 -4
- package/build/cjs/sdk/utils.js +2 -2
- package/build/esm/flagsmith-engine/features/models.js +1 -1
- package/build/esm/flagsmith-engine/features/util.js +3 -3
- package/build/esm/flagsmith-engine/index.d.ts +1 -1
- package/build/esm/flagsmith-engine/index.js +1 -1
- package/build/esm/flagsmith-engine/segments/models.js +7 -7
- package/build/esm/flagsmith-engine/utils/hashing/index.js +2 -2
- package/build/esm/flagsmith-engine/utils/index.js +1 -1
- package/build/esm/index.d.ts +4 -4
- package/build/esm/index.js +3 -3
- package/build/esm/sdk/analytics.d.ts +1 -1
- package/build/esm/sdk/index.d.ts +5 -5
- package/build/esm/sdk/index.js +6 -5
- package/build/esm/sdk/models.d.ts +32 -5
- package/build/esm/sdk/models.js +25 -0
- package/build/esm/sdk/types.d.ts +14 -4
- package/build/esm/sdk/utils.d.ts +4 -4
- package/build/esm/sdk/utils.js +2 -2
- package/flagsmith-engine/environments/util.ts +2 -2
- package/flagsmith-engine/features/models.ts +1 -1
- package/flagsmith-engine/features/util.ts +14 -14
- package/flagsmith-engine/identities/models.ts +1 -1
- package/flagsmith-engine/index.ts +1 -1
- package/flagsmith-engine/segments/evaluators.ts +2 -3
- package/flagsmith-engine/segments/models.ts +25 -15
- package/flagsmith-engine/utils/hashing/index.ts +3 -3
- package/flagsmith-engine/utils/index.ts +4 -2
- package/index.ts +19 -22
- package/package.json +2 -2
- package/sdk/analytics.ts +7 -5
- package/sdk/index.ts +38 -21
- package/sdk/models.ts +34 -12
- package/sdk/offline_handlers.ts +1 -1
- package/sdk/types.ts +17 -8
- package/sdk/utils.ts +8 -8
- package/tests/engine/e2e/engine.test.ts +2 -4
- package/tests/engine/unit/engine.test.ts +1 -5
- package/tests/engine/unit/features/models.test.ts +2 -2
- package/tests/engine/unit/identities/identities_builders.test.ts +1 -1
- package/tests/engine/unit/segments/segment_evaluators.test.ts +52 -23
- package/tests/engine/unit/segments/segments_model.test.ts +35 -37
- package/tests/engine/unit/utils/utils.test.ts +28 -30
- package/tests/sdk/analytics.test.ts +25 -26
- package/tests/sdk/flagsmith-cache.test.ts +84 -76
- package/tests/sdk/flagsmith-environment-flags.test.ts +93 -93
- package/tests/sdk/flagsmith-identity-flags.test.ts +146 -149
- package/tests/sdk/flagsmith.test.ts +40 -42
- package/tests/sdk/offline-handlers.test.ts +32 -32
- package/tests/sdk/polling.test.ts +0 -1
- package/tests/sdk/utils.ts +26 -18
- package/vitest.config.ts +10 -15
|
@@ -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) =>
|
|
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 ==
|
|
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 =
|
|
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]) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash } from
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
2
|
const md5 = (data) => createHash('md5').update(data).digest('hex');
|
|
3
3
|
const makeRepeated = (arr, repeats) => Array.from({ length: repeats }, () => arr).flat();
|
|
4
4
|
// https://stackoverflow.com/questions/12532871/how-to-convert-a-very-large-hex-number-to-decimal-in-javascript
|
|
@@ -14,7 +14,7 @@ export function getHashedPercentateForObjIds(objectIds, iterations = 1) {
|
|
|
14
14
|
let toHash = makeRepeated(objectIds, iterations).join(',');
|
|
15
15
|
const hashedValue = md5(toHash);
|
|
16
16
|
const hashedInt = BigInt('0x' + hashedValue);
|
|
17
|
-
const value = (Number(
|
|
17
|
+
const value = (Number(hashedInt % 9999n) / 9998.0) * 100;
|
|
18
18
|
// we ignore this for it's nearly impossible use case to catch
|
|
19
19
|
/* istanbul ignore next */
|
|
20
20
|
if (value === 100) {
|
package/build/esm/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AnalyticsProcessor, AnalyticsProcessorOptions, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, FlagsmithCache, DefaultFlag, Flags, Flagsmith
|
|
2
|
-
export { BaseOfflineHandler, LocalFileHandler
|
|
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';
|
package/build/esm/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { AnalyticsProcessor, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, DefaultFlag, Flags, Flagsmith
|
|
2
|
-
export { BaseOfflineHandler, LocalFileHandler
|
|
3
|
-
export { EnvironmentModel, FeatureStateModel, IdentityModel, TraitModel, SegmentModel, OrganisationModel } from './flagsmith-engine/index.js';
|
|
1
|
+
export { AnalyticsProcessor, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, BaseFlag, DefaultFlag, Flags, Flagsmith } from './sdk/index.js';
|
|
2
|
+
export { BaseOfflineHandler, LocalFileHandler } from './sdk/offline_handlers.js';
|
|
3
|
+
export { EnvironmentModel, FeatureModel, FeatureStateModel, IdentityModel, TraitModel, SegmentModel, OrganisationModel } from './flagsmith-engine/index.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger } from 'pino';
|
|
2
|
-
import { Fetch } from
|
|
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
|
package/build/esm/sdk/index.d.ts
CHANGED
|
@@ -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,
|
|
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 |
|
|
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 |
|
|
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
|
package/build/esm/sdk/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js'
|
|
|
11
11
|
import { pino } from 'pino';
|
|
12
12
|
export { AnalyticsProcessor } from './analytics.js';
|
|
13
13
|
export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
|
|
14
|
-
export { DefaultFlag, Flags } from './models.js';
|
|
14
|
+
export { BaseFlag, DefaultFlag, Flags } from './models.js';
|
|
15
15
|
export { EnvironmentDataPollingManager } from './polling_manager.js';
|
|
16
16
|
const DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/';
|
|
17
17
|
const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
|
|
@@ -38,7 +38,7 @@ const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
|
|
|
38
38
|
* const bannerVariation: string = identityFlags.getFeatureValue('banner_flag')
|
|
39
39
|
*
|
|
40
40
|
* @see FlagsmithConfig
|
|
41
|
-
*/
|
|
41
|
+
*/
|
|
42
42
|
export class Flagsmith {
|
|
43
43
|
environmentKey = undefined;
|
|
44
44
|
apiUrl = undefined;
|
|
@@ -107,7 +107,8 @@ export class Flagsmith {
|
|
|
107
107
|
}
|
|
108
108
|
const apiUrl = data.apiUrl || DEFAULT_API_URL;
|
|
109
109
|
this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
|
|
110
|
-
this.analyticsUrl =
|
|
110
|
+
this.analyticsUrl =
|
|
111
|
+
this.analyticsUrl || new URL(ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href;
|
|
111
112
|
this.environmentFlagsUrl = `${this.apiUrl}flags/`;
|
|
112
113
|
this.identitiesUrl = `${this.apiUrl}identities/`;
|
|
113
114
|
this.environmentUrl = `${this.apiUrl}environment-document/`;
|
|
@@ -125,7 +126,7 @@ export class Flagsmith {
|
|
|
125
126
|
environmentKey: this.environmentKey,
|
|
126
127
|
analyticsUrl: this.analyticsUrl,
|
|
127
128
|
requestTimeoutMs: this.requestTimeoutMs,
|
|
128
|
-
logger: this.logger
|
|
129
|
+
logger: this.logger
|
|
129
130
|
});
|
|
130
131
|
}
|
|
131
132
|
}
|
|
@@ -164,7 +165,7 @@ export class Flagsmith {
|
|
|
164
165
|
*
|
|
165
166
|
* @param {string} identifier a unique identifier for the identity in the current
|
|
166
167
|
environment, e.g. email address, username, uuid
|
|
167
|
-
* @param {{[key:string]:any |
|
|
168
|
+
* @param {{[key:string]:any | TraitConfig}} traits? a dictionary of traits to add / update on the identity in
|
|
168
169
|
Flagsmith, e.g. {"num_orders": 10} or {age: {value: 30, transient: true}}
|
|
169
170
|
* @returns Flags object holding all the flags for the given identity.
|
|
170
171
|
*/
|
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
import { FeatureStateModel } from '../flagsmith-engine/features/models.js';
|
|
2
2
|
import { AnalyticsProcessor } from './analytics.js';
|
|
3
|
+
type FlagValue = string | number | boolean | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* A Flagsmith feature. It has an enabled/disabled state, and an optional {@link FlagValue}.
|
|
6
|
+
*/
|
|
3
7
|
export declare class BaseFlag {
|
|
8
|
+
/**
|
|
9
|
+
* Indicates whether this feature is enabled.
|
|
10
|
+
*/
|
|
4
11
|
enabled: boolean;
|
|
5
|
-
|
|
12
|
+
/**
|
|
13
|
+
* An optional {@link FlagValue} for this feature.
|
|
14
|
+
*/
|
|
15
|
+
value: FlagValue;
|
|
16
|
+
/**
|
|
17
|
+
* If true, the state for this feature was determined by a default flag handler. See {@link DefaultFlag}.
|
|
18
|
+
*/
|
|
6
19
|
isDefault: boolean;
|
|
7
|
-
constructor(value:
|
|
20
|
+
constructor(value: FlagValue, enabled: boolean, isDefault: boolean);
|
|
8
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* A {@link BaseFlag} returned by a default flag handler when flag evaluation fails.
|
|
24
|
+
* @see FlagsmithConfig#defaultFlagHandler
|
|
25
|
+
*/
|
|
9
26
|
export declare class DefaultFlag extends BaseFlag {
|
|
10
|
-
constructor(value:
|
|
27
|
+
constructor(value: FlagValue, enabled: boolean);
|
|
11
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* A Flagsmith feature retrieved from a successful flag evaluation.
|
|
31
|
+
*/
|
|
12
32
|
export declare class Flag extends BaseFlag {
|
|
33
|
+
/**
|
|
34
|
+
* An identifier for this feature, unique in a single Flagsmith installation.
|
|
35
|
+
*/
|
|
13
36
|
featureId: number;
|
|
37
|
+
/**
|
|
38
|
+
* The programmatic name for this feature, unique per Flagsmith project.
|
|
39
|
+
*/
|
|
14
40
|
featureName: string;
|
|
15
41
|
constructor(params: {
|
|
16
|
-
value:
|
|
42
|
+
value: FlagValue;
|
|
17
43
|
enabled: boolean;
|
|
18
44
|
isDefault?: boolean;
|
|
19
45
|
featureId: number;
|
|
@@ -50,6 +76,7 @@ export declare class Flags {
|
|
|
50
76
|
}): Flags;
|
|
51
77
|
allFlags(): Flag[];
|
|
52
78
|
getFlag(featureName: string): BaseFlag;
|
|
53
|
-
getFeatureValue(featureName: string):
|
|
79
|
+
getFeatureValue(featureName: string): FlagValue;
|
|
54
80
|
isFeatureEnabled(featureName: string): boolean;
|
|
55
81
|
}
|
|
82
|
+
export {};
|
package/build/esm/sdk/models.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Flagsmith feature. It has an enabled/disabled state, and an optional {@link FlagValue}.
|
|
3
|
+
*/
|
|
1
4
|
export class BaseFlag {
|
|
5
|
+
/**
|
|
6
|
+
* Indicates whether this feature is enabled.
|
|
7
|
+
*/
|
|
2
8
|
enabled;
|
|
9
|
+
/**
|
|
10
|
+
* An optional {@link FlagValue} for this feature.
|
|
11
|
+
*/
|
|
3
12
|
value;
|
|
13
|
+
/**
|
|
14
|
+
* If true, the state for this feature was determined by a default flag handler. See {@link DefaultFlag}.
|
|
15
|
+
*/
|
|
4
16
|
isDefault;
|
|
5
17
|
constructor(value, enabled, isDefault) {
|
|
6
18
|
this.value = value;
|
|
@@ -8,13 +20,26 @@ export class BaseFlag {
|
|
|
8
20
|
this.isDefault = isDefault;
|
|
9
21
|
}
|
|
10
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* A {@link BaseFlag} returned by a default flag handler when flag evaluation fails.
|
|
25
|
+
* @see FlagsmithConfig#defaultFlagHandler
|
|
26
|
+
*/
|
|
11
27
|
export class DefaultFlag extends BaseFlag {
|
|
12
28
|
constructor(value, enabled) {
|
|
13
29
|
super(value, enabled, true);
|
|
14
30
|
}
|
|
15
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* A Flagsmith feature retrieved from a successful flag evaluation.
|
|
34
|
+
*/
|
|
16
35
|
export class Flag extends BaseFlag {
|
|
36
|
+
/**
|
|
37
|
+
* An identifier for this feature, unique in a single Flagsmith installation.
|
|
38
|
+
*/
|
|
17
39
|
featureId;
|
|
40
|
+
/**
|
|
41
|
+
* The programmatic name for this feature, unique per Flagsmith project.
|
|
42
|
+
*/
|
|
18
43
|
featureName;
|
|
19
44
|
constructor(params) {
|
|
20
45
|
super(params.value, params.enabled, !!params.isDefault);
|
package/build/esm/sdk/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
126
|
+
export declare type FlagsmithTraitValue = FlagsmithValue;
|
package/build/esm/sdk/utils.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Fetch, FlagsmithTraitValue,
|
|
2
|
-
import { Dispatcher } from
|
|
1
|
+
import { Fetch, FlagsmithTraitValue, TraitConfig } from './types.js';
|
|
2
|
+
import { Dispatcher } from 'undici-types';
|
|
3
3
|
type Traits = {
|
|
4
|
-
[key: string]:
|
|
4
|
+
[key: string]: TraitConfig | FlagsmithTraitValue;
|
|
5
5
|
};
|
|
6
|
-
export declare function isTraitConfig(traitValue:
|
|
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: ({
|
package/build/esm/sdk/utils.js
CHANGED
|
@@ -7,13 +7,13 @@ export function generateIdentitiesData(identifier, traits, transient) {
|
|
|
7
7
|
return {
|
|
8
8
|
trait_key: key,
|
|
9
9
|
trait_value: value?.value,
|
|
10
|
-
transient: value?.transient
|
|
10
|
+
transient: value?.transient
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
else {
|
|
14
14
|
return {
|
|
15
15
|
trait_key: key,
|
|
16
|
-
trait_value: value
|
|
16
|
+
trait_value: value
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
});
|
|
@@ -15,8 +15,8 @@ export function buildEnvironmentModel(environmentJSON: any) {
|
|
|
15
15
|
);
|
|
16
16
|
environmentModel.featureStates = featureStates;
|
|
17
17
|
if (!!environmentJSON.identity_overrides) {
|
|
18
|
-
environmentModel.identityOverrides = environmentJSON.identity_overrides.map(
|
|
19
|
-
buildIdentityModel(identityData)
|
|
18
|
+
environmentModel.identityOverrides = environmentJSON.identity_overrides.map(
|
|
19
|
+
(identityData: any) => buildIdentityModel(identityData)
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
22
|
return environmentModel;
|
|
@@ -19,23 +19,23 @@ export function buildFeatureStateModel(featuresStateModelJSON: any): FeatureStat
|
|
|
19
19
|
featuresStateModelJSON.featurestate_uuid
|
|
20
20
|
);
|
|
21
21
|
|
|
22
|
-
featureStateModel.featureSegment = featuresStateModelJSON.feature_segment
|
|
23
|
-
buildFeatureSegment(featuresStateModelJSON.feature_segment)
|
|
24
|
-
undefined;
|
|
22
|
+
featureStateModel.featureSegment = featuresStateModelJSON.feature_segment
|
|
23
|
+
? buildFeatureSegment(featuresStateModelJSON.feature_segment)
|
|
24
|
+
: undefined;
|
|
25
25
|
|
|
26
26
|
const multivariateFeatureStateValues = featuresStateModelJSON.multivariate_feature_state_values
|
|
27
27
|
? featuresStateModelJSON.multivariate_feature_state_values.map((fsv: any) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
const featureOption = new MultivariateFeatureOptionModel(
|
|
29
|
+
fsv.multivariate_feature_option.value,
|
|
30
|
+
fsv.multivariate_feature_option.id
|
|
31
|
+
);
|
|
32
|
+
return new MultivariateFeatureStateValueModel(
|
|
33
|
+
featureOption,
|
|
34
|
+
fsv.percentage_allocation,
|
|
35
|
+
fsv.id,
|
|
36
|
+
fsv.mv_fs_value_uuid
|
|
37
|
+
);
|
|
38
|
+
})
|
|
39
39
|
: [];
|
|
40
40
|
|
|
41
41
|
featureStateModel.multivariateFeatureStateValues = multivariateFeatureStateValues;
|
|
@@ -19,7 +19,7 @@ export class IdentityModel {
|
|
|
19
19
|
environmentApiKey: string,
|
|
20
20
|
identifier: string,
|
|
21
21
|
identityUuid?: string,
|
|
22
|
-
djangoID?: number
|
|
22
|
+
djangoID?: number
|
|
23
23
|
) {
|
|
24
24
|
this.identityUuid = identityUuid || uuidv4();
|
|
25
25
|
this.createdDate = Date.parse(created_date) || Date.now();
|
|
@@ -7,7 +7,7 @@ import { SegmentModel } from './segments/models.js';
|
|
|
7
7
|
import { FeatureStateNotFound } from './utils/errors.js';
|
|
8
8
|
|
|
9
9
|
export { EnvironmentModel } from './environments/models.js';
|
|
10
|
-
export { FeatureStateModel } from './features/models.js';
|
|
10
|
+
export { FeatureModel, FeatureStateModel } from './features/models.js';
|
|
11
11
|
export { IdentityModel } from './identities/models.js';
|
|
12
12
|
export { TraitModel } from './identities/traits/models.js';
|
|
13
13
|
export { SegmentModel } from './segments/models.js';
|
|
@@ -67,11 +67,10 @@ export function traitsMatchSegmentCondition(
|
|
|
67
67
|
}
|
|
68
68
|
const traits = identityTraits.filter(t => t.traitKey === condition.property_);
|
|
69
69
|
const trait = traits.length > 0 ? traits[0] : undefined;
|
|
70
|
-
if (condition.operator === IS_SET
|
|
70
|
+
if (condition.operator === IS_SET) {
|
|
71
71
|
return !!trait;
|
|
72
|
-
} else if (condition.operator === IS_NOT_SET){
|
|
72
|
+
} else if (condition.operator === IS_NOT_SET) {
|
|
73
73
|
return trait == undefined;
|
|
74
74
|
}
|
|
75
75
|
return trait ? condition.matchesTraitValue(trait.traitValue) : false;
|
|
76
|
-
|
|
77
76
|
}
|
|
@@ -27,21 +27,25 @@ export const matchingFunctions = {
|
|
|
27
27
|
thisValue >= otherValue,
|
|
28
28
|
[CONDITION_OPERATORS.NOT_EQUAL]: (thisValue: any, otherValue: any) => thisValue != otherValue,
|
|
29
29
|
[CONDITION_OPERATORS.CONTAINS]: (thisValue: any, otherValue: any) =>
|
|
30
|
-
!!otherValue && otherValue.includes(thisValue)
|
|
30
|
+
!!otherValue && otherValue.includes(thisValue)
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export const semverMatchingFunction = {
|
|
34
34
|
...matchingFunctions,
|
|
35
|
-
[CONDITION_OPERATORS.EQUAL]: (thisValue: any, otherValue: any) =>
|
|
36
|
-
|
|
35
|
+
[CONDITION_OPERATORS.EQUAL]: (thisValue: any, otherValue: any) =>
|
|
36
|
+
semver.eq(thisValue, otherValue),
|
|
37
|
+
[CONDITION_OPERATORS.GREATER_THAN]: (thisValue: any, otherValue: any) =>
|
|
38
|
+
semver.gt(otherValue, thisValue),
|
|
37
39
|
[CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE]: (thisValue: any, otherValue: any) =>
|
|
38
40
|
semver.gte(otherValue, thisValue),
|
|
39
|
-
[CONDITION_OPERATORS.LESS_THAN]: (thisValue: any, otherValue: any) =>
|
|
41
|
+
[CONDITION_OPERATORS.LESS_THAN]: (thisValue: any, otherValue: any) =>
|
|
42
|
+
semver.gt(thisValue, otherValue),
|
|
40
43
|
[CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue: any, otherValue: any) =>
|
|
41
|
-
semver.gte(thisValue, otherValue)
|
|
42
|
-
}
|
|
44
|
+
semver.gte(thisValue, otherValue)
|
|
45
|
+
};
|
|
43
46
|
|
|
44
|
-
export const getMatchingFunctions = (semver: boolean) =>
|
|
47
|
+
export const getMatchingFunctions = (semver: boolean) =>
|
|
48
|
+
semver ? semverMatchingFunction : matchingFunctions;
|
|
45
49
|
|
|
46
50
|
export class SegmentConditionModel {
|
|
47
51
|
EXCEPTION_OPERATOR_METHODS: { [key: string]: string } = {
|
|
@@ -55,7 +59,11 @@ export class SegmentConditionModel {
|
|
|
55
59
|
value: string | null | undefined;
|
|
56
60
|
property_: string | null | undefined;
|
|
57
61
|
|
|
58
|
-
constructor(
|
|
62
|
+
constructor(
|
|
63
|
+
operator: string,
|
|
64
|
+
value?: string | null | undefined,
|
|
65
|
+
property?: string | null | undefined
|
|
66
|
+
) {
|
|
59
67
|
this.operator = operator;
|
|
60
68
|
this.value = value;
|
|
61
69
|
this.property_ = property;
|
|
@@ -64,24 +72,26 @@ export class SegmentConditionModel {
|
|
|
64
72
|
matchesTraitValue(traitValue: any) {
|
|
65
73
|
const evaluators: { [key: string]: CallableFunction } = {
|
|
66
74
|
evaluateNotContains: (traitValue: any) => {
|
|
67
|
-
return
|
|
75
|
+
return (
|
|
76
|
+
typeof traitValue == 'string' &&
|
|
68
77
|
!!this.value &&
|
|
69
|
-
!traitValue.includes(this.value?.toString())
|
|
78
|
+
!traitValue.includes(this.value?.toString())
|
|
79
|
+
);
|
|
70
80
|
},
|
|
71
81
|
evaluateRegex: (traitValue: any) => {
|
|
72
82
|
return !!this.value && !!traitValue?.toString().match(new RegExp(this.value));
|
|
73
83
|
},
|
|
74
84
|
evaluateModulo: (traitValue: any) => {
|
|
75
85
|
if (isNaN(parseFloat(traitValue)) || !this.value) {
|
|
76
|
-
return false
|
|
86
|
+
return false;
|
|
77
87
|
}
|
|
78
|
-
const parts =
|
|
88
|
+
const parts = this.value.split('|');
|
|
79
89
|
const [divisor, reminder] = [parseFloat(parts[0]), parseFloat(parts[1])];
|
|
80
|
-
return traitValue % divisor === reminder
|
|
90
|
+
return traitValue % divisor === reminder;
|
|
81
91
|
},
|
|
82
92
|
evaluateIn: (traitValue: any) => {
|
|
83
|
-
return this.value?.split(',').includes(traitValue.toString())
|
|
84
|
-
}
|
|
93
|
+
return this.value?.split(',').includes(traitValue.toString());
|
|
94
|
+
}
|
|
85
95
|
};
|
|
86
96
|
|
|
87
97
|
// TODO: move this logic to the evaluator module
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {BinaryLike, createHash} from
|
|
1
|
+
import { BinaryLike, createHash } from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
const md5 = (data: BinaryLike) => createHash('md5').update(data).digest('hex')
|
|
3
|
+
const md5 = (data: BinaryLike) => createHash('md5').update(data).digest('hex');
|
|
4
4
|
|
|
5
5
|
const makeRepeated = (arr: Array<any>, repeats: number) =>
|
|
6
6
|
Array.from({ length: repeats }, () => arr).flat();
|
|
@@ -18,7 +18,7 @@ export function getHashedPercentateForObjIds(objectIds: Array<any>, iterations =
|
|
|
18
18
|
let toHash = makeRepeated(objectIds, iterations).join(',');
|
|
19
19
|
const hashedValue = md5(toHash);
|
|
20
20
|
const hashedInt = BigInt('0x' + hashedValue);
|
|
21
|
-
const value = (Number(
|
|
21
|
+
const value = (Number(hashedInt % 9999n) / 9998.0) * 100;
|
|
22
22
|
|
|
23
23
|
// we ignore this for it's nearly impossible use case to catch
|
|
24
24
|
/* istanbul ignore next */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { removeSemverSuffix } from
|
|
1
|
+
import { removeSemverSuffix } from '../segments/util.js';
|
|
2
2
|
|
|
3
|
-
export function getCastingFunction(
|
|
3
|
+
export function getCastingFunction(
|
|
4
|
+
traitType: 'boolean' | 'string' | 'number' | 'semver' | any
|
|
5
|
+
): CallableFunction {
|
|
4
6
|
switch (traitType) {
|
|
5
7
|
case 'boolean':
|
|
6
8
|
return (x: any) => !['False', 'false'].includes(x);
|
package/index.ts
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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.
|
|
3
|
+
"version": "6.1.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",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@types/semver": "^7.3.9",
|
|
70
70
|
"@types/uuid": "^8.3.4",
|
|
71
71
|
"@vitest/coverage-v8": "^2.1.2",
|
|
72
|
-
"esbuild": "^0.
|
|
72
|
+
"esbuild": "^0.25.0",
|
|
73
73
|
"husky": "^7.0.4",
|
|
74
74
|
"prettier": "^2.2.1",
|
|
75
75
|
"typescript": "^4.9.5",
|