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
package/sdk/analytics.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { pino, Logger } from 'pino';
|
|
2
|
-
import { Fetch } from
|
|
3
|
-
import { FlagsmithConfig } from
|
|
2
|
+
import { Fetch } from './types.js';
|
|
3
|
+
import { FlagsmithConfig } from './types.js';
|
|
4
4
|
|
|
5
5
|
export const ANALYTICS_ENDPOINT = './analytics/flags/';
|
|
6
6
|
|
|
7
7
|
/** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
|
|
8
8
|
const ANALYTICS_TIMER = 10;
|
|
9
9
|
|
|
10
|
-
const DEFAULT_REQUEST_TIMEOUT_MS = 3000
|
|
10
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 3000;
|
|
11
11
|
|
|
12
12
|
export interface AnalyticsProcessorOptions {
|
|
13
13
|
/** URL of the Flagsmith analytics events API endpoint
|
|
@@ -20,7 +20,7 @@ export interface AnalyticsProcessorOptions {
|
|
|
20
20
|
requestTimeoutMs?: number;
|
|
21
21
|
logger?: Logger;
|
|
22
22
|
/** Custom {@link fetch} implementation to use for API requests. **/
|
|
23
|
-
fetch?: Fetch
|
|
23
|
+
fetch?: Fetch;
|
|
24
24
|
|
|
25
25
|
/** @deprecated Use {@link analyticsUrl} instead. **/
|
|
26
26
|
baseApiUrl?: string;
|
|
@@ -76,7 +76,9 @@ export class AnalyticsProcessor {
|
|
|
76
76
|
} catch (error) {
|
|
77
77
|
// We don't want failing to write analytics to cause any exceptions in the main
|
|
78
78
|
// thread so we just swallow them here.
|
|
79
|
-
this.logger.warn(
|
|
79
|
+
this.logger.warn(
|
|
80
|
+
'Failed to post analytics to Flagsmith API. Not clearing data, will retry.'
|
|
81
|
+
);
|
|
80
82
|
return;
|
|
81
83
|
} finally {
|
|
82
84
|
this.currentFlush = undefined;
|
package/sdk/index.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { Dispatcher } from 'undici-types';
|
|
2
|
-
import {
|
|
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
|
|
|
@@ -14,13 +17,19 @@ import { EnvironmentDataPollingManager } from './polling_manager.js';
|
|
|
14
17
|
import { Deferred, generateIdentitiesData, 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 {
|
|
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 =
|
|
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(
|
|
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(
|
|
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 |
|
|
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 |
|
|
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(
|
|
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);
|
|
@@ -334,7 +352,7 @@ export class Flagsmith {
|
|
|
334
352
|
this.retries,
|
|
335
353
|
this.requestTimeoutMs,
|
|
336
354
|
this.requestRetryDelayMilliseconds,
|
|
337
|
-
this.customFetch
|
|
355
|
+
this.customFetch
|
|
338
356
|
);
|
|
339
357
|
|
|
340
358
|
if (data.status !== 200) {
|
|
@@ -438,7 +456,7 @@ export class Flagsmith {
|
|
|
438
456
|
|
|
439
457
|
private async getIdentityFlagsFromApi(
|
|
440
458
|
identifier: string,
|
|
441
|
-
traits: { [key: string]: FlagsmithTraitValue |
|
|
459
|
+
traits: { [key: string]: FlagsmithTraitValue | TraitConfig },
|
|
442
460
|
transient: boolean = false
|
|
443
461
|
) {
|
|
444
462
|
if (!this.identitiesUrl) {
|
|
@@ -463,8 +481,7 @@ export class Flagsmith {
|
|
|
463
481
|
traits: { key: string; value: any }[]
|
|
464
482
|
) {
|
|
465
483
|
const traitModels = traits.map(trait => new TraitModel(trait.key, trait.value));
|
|
466
|
-
let identityWithOverrides =
|
|
467
|
-
this.identitiesWithOverridesByIdentifier?.get(identifier);
|
|
484
|
+
let identityWithOverrides = this.identitiesWithOverridesByIdentifier?.get(identifier);
|
|
468
485
|
if (identityWithOverrides) {
|
|
469
486
|
identityWithOverrides.updateTraits(traitModels);
|
|
470
487
|
return identityWithOverrides;
|
package/sdk/models.ts
CHANGED
|
@@ -1,34 +1,57 @@
|
|
|
1
1
|
import { FeatureStateModel } from '../flagsmith-engine/features/models.js';
|
|
2
2
|
import { AnalyticsProcessor } from './analytics.js';
|
|
3
3
|
|
|
4
|
+
type FlagValue = string | number | boolean | undefined;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Flagsmith feature. It has an enabled/disabled state, and an optional {@link FlagValue}.
|
|
8
|
+
*/
|
|
4
9
|
export class BaseFlag {
|
|
10
|
+
/**
|
|
11
|
+
* Indicates whether this feature is enabled.
|
|
12
|
+
*/
|
|
5
13
|
enabled: boolean;
|
|
6
|
-
|
|
14
|
+
/**
|
|
15
|
+
* An optional {@link FlagValue} for this feature.
|
|
16
|
+
*/
|
|
17
|
+
value: FlagValue;
|
|
18
|
+
/**
|
|
19
|
+
* If true, the state for this feature was determined by a default flag handler. See {@link DefaultFlag}.
|
|
20
|
+
*/
|
|
7
21
|
isDefault: boolean;
|
|
8
22
|
|
|
9
|
-
constructor(
|
|
10
|
-
value: string | number | boolean | undefined,
|
|
11
|
-
enabled: boolean,
|
|
12
|
-
isDefault: boolean
|
|
13
|
-
) {
|
|
23
|
+
constructor(value: FlagValue, enabled: boolean, isDefault: boolean) {
|
|
14
24
|
this.value = value;
|
|
15
25
|
this.enabled = enabled;
|
|
16
26
|
this.isDefault = isDefault;
|
|
17
27
|
}
|
|
18
28
|
}
|
|
19
29
|
|
|
30
|
+
/**
|
|
31
|
+
* A {@link BaseFlag} returned by a default flag handler when flag evaluation fails.
|
|
32
|
+
* @see FlagsmithConfig#defaultFlagHandler
|
|
33
|
+
*/
|
|
20
34
|
export class DefaultFlag extends BaseFlag {
|
|
21
|
-
constructor(value:
|
|
35
|
+
constructor(value: FlagValue, enabled: boolean) {
|
|
22
36
|
super(value, enabled, true);
|
|
23
37
|
}
|
|
24
38
|
}
|
|
25
39
|
|
|
40
|
+
/**
|
|
41
|
+
* A Flagsmith feature retrieved from a successful flag evaluation.
|
|
42
|
+
*/
|
|
26
43
|
export class Flag extends BaseFlag {
|
|
44
|
+
/**
|
|
45
|
+
* An identifier for this feature, unique in a single Flagsmith installation.
|
|
46
|
+
*/
|
|
27
47
|
featureId: number;
|
|
48
|
+
/**
|
|
49
|
+
* The programmatic name for this feature, unique per Flagsmith project.
|
|
50
|
+
*/
|
|
28
51
|
featureName: string;
|
|
29
52
|
|
|
30
53
|
constructor(params: {
|
|
31
|
-
value:
|
|
54
|
+
value: FlagValue;
|
|
32
55
|
enabled: boolean;
|
|
33
56
|
isDefault?: boolean;
|
|
34
57
|
featureId: number;
|
|
@@ -82,7 +105,7 @@ export class Flags {
|
|
|
82
105
|
defaultFlagHandler?: (f: string) => DefaultFlag;
|
|
83
106
|
identityID?: string | number;
|
|
84
107
|
}): Flags {
|
|
85
|
-
const flags: { [key: string]:
|
|
108
|
+
const flags: { [key: string]: Flag } = {};
|
|
86
109
|
for (const fs of data.featureStates) {
|
|
87
110
|
flags[fs.feature.name] = Flag.fromFeatureStateModel(fs, data.identityID);
|
|
88
111
|
}
|
|
@@ -98,7 +121,7 @@ export class Flags {
|
|
|
98
121
|
analyticsProcessor?: AnalyticsProcessor;
|
|
99
122
|
defaultFlagHandler?: (v: string) => DefaultFlag;
|
|
100
123
|
}): Flags {
|
|
101
|
-
const flags: { [key: string]:
|
|
124
|
+
const flags: { [key: string]: Flag } = {};
|
|
102
125
|
|
|
103
126
|
for (const flagData of data.apiFlags) {
|
|
104
127
|
flags[flagData['feature']['name']] = Flag.fromAPIFlag(flagData);
|
|
@@ -124,7 +147,6 @@ export class Flags {
|
|
|
124
147
|
}
|
|
125
148
|
|
|
126
149
|
return { enabled: false, isDefault: true, value: undefined };
|
|
127
|
-
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
if (this.analyticsProcessor && flag.featureId) {
|
|
@@ -134,7 +156,7 @@ export class Flags {
|
|
|
134
156
|
return flag;
|
|
135
157
|
}
|
|
136
158
|
|
|
137
|
-
getFeatureValue(featureName: string):
|
|
159
|
+
getFeatureValue(featureName: string): FlagValue {
|
|
138
160
|
return this.getFlag(featureName).value;
|
|
139
161
|
}
|
|
140
162
|
|
package/sdk/offline_handlers.ts
CHANGED
|
@@ -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()
|
|
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
|
|
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
|
-
|
|
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 =
|
|
134
|
+
export declare type FlagsmithTraitValue = FlagsmithValue;
|
package/sdk/utils.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {Fetch, FlagsmithTraitValue,
|
|
2
|
-
import {Dispatcher} from
|
|
1
|
+
import { Fetch, FlagsmithTraitValue, TraitConfig } from './types.js';
|
|
2
|
+
import { Dispatcher } from 'undici-types';
|
|
3
3
|
|
|
4
|
-
type Traits = { [key: string]:
|
|
4
|
+
type Traits = { [key: string]: TraitConfig | FlagsmithTraitValue };
|
|
5
5
|
|
|
6
6
|
export function isTraitConfig(
|
|
7
|
-
traitValue:
|
|
8
|
-
): traitValue is
|
|
7
|
+
traitValue: TraitConfig | FlagsmithTraitValue
|
|
8
|
+
): traitValue is TraitConfig {
|
|
9
9
|
return !!traitValue && typeof traitValue == 'object' && traitValue.value !== undefined;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -15,12 +15,12 @@ export function generateIdentitiesData(identifier: string, traits: Traits, trans
|
|
|
15
15
|
return {
|
|
16
16
|
trait_key: key,
|
|
17
17
|
trait_value: value?.value,
|
|
18
|
-
transient: value?.transient
|
|
18
|
+
transient: value?.transient
|
|
19
19
|
};
|
|
20
20
|
} else {
|
|
21
21
|
return {
|
|
22
22
|
trait_key: key,
|
|
23
|
-
trait_value: value
|
|
23
|
+
trait_value: value
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
});
|
|
@@ -47,7 +47,7 @@ export const retryFetch = (
|
|
|
47
47
|
retries: number = 3,
|
|
48
48
|
timeoutMs: number = 10, // set an overall timeout for this function
|
|
49
49
|
retryDelayMs: number = 1000,
|
|
50
|
-
customFetch: Fetch
|
|
50
|
+
customFetch: Fetch
|
|
51
51
|
): Promise<Response> => {
|
|
52
52
|
const retryWrapper = async (n: number): Promise<Response> => {
|
|
53
53
|
try {
|
|
@@ -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;
|
|
@@ -73,11 +73,7 @@ test('test_identity_get_all_feature_states_with_traits_hideDisabledFlags', () =>
|
|
|
73
73
|
const env = environmentWithSegmentOverride();
|
|
74
74
|
env.project.hideDisabledFlags = true;
|
|
75
75
|
|
|
76
|
-
const featureStates = getIdentityFeatureStates(
|
|
77
|
-
env,
|
|
78
|
-
identityInSegment(),
|
|
79
|
-
[trait_models]
|
|
80
|
-
);
|
|
76
|
+
const featureStates = getIdentityFeatureStates(env, identityInSegment(), [trait_models]);
|
|
81
77
|
expect(featureStates.length).toBe(0);
|
|
82
78
|
});
|
|
83
79
|
|
|
@@ -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(
|
|
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);
|
|
@@ -1,44 +1,70 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ALL_RULE,
|
|
3
3
|
CONDITION_OPERATORS,
|
|
4
|
-
PERCENTAGE_SPLIT
|
|
4
|
+
PERCENTAGE_SPLIT
|
|
5
5
|
} from '../../../../flagsmith-engine/segments/constants.js';
|
|
6
|
-
import {SegmentConditionModel} from '../../../../flagsmith-engine/segments/models.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import { SegmentConditionModel } from '../../../../flagsmith-engine/segments/models.js';
|
|
7
|
+
import {
|
|
8
|
+
traitsMatchSegmentCondition,
|
|
9
|
+
evaluateIdentityInSegment
|
|
10
|
+
} from '../../../../flagsmith-engine/segments/evaluators.js';
|
|
11
|
+
import { TraitModel, IdentityModel } from '../../../../flagsmith-engine/index.js';
|
|
12
|
+
import { environment } from '../utils.js';
|
|
10
13
|
import { buildSegmentModel } from '../../../../flagsmith-engine/segments/util.js';
|
|
11
14
|
import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
|
|
12
15
|
|
|
13
|
-
|
|
14
16
|
// todo: work out how to implement this in a test function or before hook
|
|
15
17
|
vi.mock('../../../../flagsmith-engine/utils/hashing', () => ({
|
|
16
18
|
getHashedPercentateForObjIds: vi.fn(() => 1)
|
|
17
19
|
}));
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
[
|
|
21
|
+
let traitExistenceTestCases: [
|
|
22
|
+
string,
|
|
23
|
+
string | null | undefined,
|
|
24
|
+
string | null | undefined,
|
|
25
|
+
TraitModel[],
|
|
26
|
+
boolean
|
|
27
|
+
][] = [
|
|
28
|
+
[CONDITION_OPERATORS.IS_SET, 'foo', null, [], false],
|
|
29
|
+
[CONDITION_OPERATORS.IS_SET, 'foo', undefined, [new TraitModel('foo', 'bar')], true],
|
|
30
|
+
[
|
|
31
|
+
CONDITION_OPERATORS.IS_SET,
|
|
32
|
+
'foo',
|
|
33
|
+
undefined,
|
|
34
|
+
[new TraitModel('foo', 'bar'), new TraitModel('fooBaz', 'baz')],
|
|
35
|
+
true
|
|
36
|
+
],
|
|
24
37
|
[CONDITION_OPERATORS.IS_NOT_SET, 'foo', undefined, [], true],
|
|
25
|
-
[CONDITION_OPERATORS.IS_NOT_SET, 'foo', null, [new TraitModel('foo','bar')], false],
|
|
26
|
-
[
|
|
38
|
+
[CONDITION_OPERATORS.IS_NOT_SET, 'foo', null, [new TraitModel('foo', 'bar')], false],
|
|
39
|
+
[
|
|
40
|
+
CONDITION_OPERATORS.IS_NOT_SET,
|
|
41
|
+
'foo',
|
|
42
|
+
null,
|
|
43
|
+
[new TraitModel('foo', 'bar'), new TraitModel('fooBaz', 'baz')],
|
|
44
|
+
false
|
|
45
|
+
]
|
|
27
46
|
];
|
|
28
47
|
|
|
29
48
|
test('test_traits_match_segment_condition_for_trait_existence_operators', () => {
|
|
30
49
|
for (const testCase of traitExistenceTestCases) {
|
|
31
|
-
const [operator, conditionProperty, conditionValue, traits, expectedResult] = testCase
|
|
32
|
-
let segmentModel = new SegmentConditionModel(operator, conditionValue, conditionProperty)
|
|
33
|
-
expect(
|
|
34
|
-
|
|
35
|
-
)
|
|
50
|
+
const [operator, conditionProperty, conditionValue, traits, expectedResult] = testCase;
|
|
51
|
+
let segmentModel = new SegmentConditionModel(operator, conditionValue, conditionProperty);
|
|
52
|
+
expect(traitsMatchSegmentCondition(traits, segmentModel, 'any', 'any')).toBe(
|
|
53
|
+
expectedResult
|
|
54
|
+
);
|
|
36
55
|
}
|
|
37
56
|
});
|
|
38
57
|
|
|
39
|
-
|
|
40
58
|
test('evaluateIdentityInSegment uses django ID for hashed percentage when present', () => {
|
|
41
|
-
var identityModel = new IdentityModel(
|
|
59
|
+
var identityModel = new IdentityModel(
|
|
60
|
+
Date.now().toString(),
|
|
61
|
+
[],
|
|
62
|
+
[],
|
|
63
|
+
environment().apiKey,
|
|
64
|
+
'identity_1',
|
|
65
|
+
undefined,
|
|
66
|
+
1
|
|
67
|
+
);
|
|
42
68
|
const segmentDefinition = {
|
|
43
69
|
id: 1,
|
|
44
70
|
name: 'percentage_split_segment',
|
|
@@ -49,7 +75,7 @@ test('evaluateIdentityInSegment uses django ID for hashed percentage when presen
|
|
|
49
75
|
{
|
|
50
76
|
operator: PERCENTAGE_SPLIT,
|
|
51
77
|
property_: null,
|
|
52
|
-
value:
|
|
78
|
+
value: '10'
|
|
53
79
|
}
|
|
54
80
|
],
|
|
55
81
|
rules: []
|
|
@@ -62,6 +88,9 @@ test('evaluateIdentityInSegment uses django ID for hashed percentage when presen
|
|
|
62
88
|
var result = evaluateIdentityInSegment(identityModel, segmentModel);
|
|
63
89
|
|
|
64
90
|
expect(result).toBe(true);
|
|
65
|
-
expect(getHashedPercentateForObjIds).toHaveBeenCalledTimes(1)
|
|
66
|
-
expect(getHashedPercentateForObjIds).toHaveBeenCalledWith([
|
|
91
|
+
expect(getHashedPercentateForObjIds).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(getHashedPercentateForObjIds).toHaveBeenCalledWith([
|
|
93
|
+
segmentModel.id,
|
|
94
|
+
identityModel.djangoID
|
|
95
|
+
]);
|
|
67
96
|
});
|