flagsmith-nodejs 5.1.1 → 6.0.1
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/build/cjs/sdk/analytics.d.ts +1 -1
- package/build/cjs/sdk/analytics.js +1 -1
- package/build/cjs/sdk/index.d.ts +44 -42
- package/build/cjs/sdk/index.js +150 -152
- package/build/cjs/sdk/models.d.ts +7 -5
- package/build/cjs/sdk/polling_manager.d.ts +3 -1
- package/build/cjs/sdk/polling_manager.js +9 -2
- package/build/cjs/sdk/types.d.ts +73 -5
- package/build/cjs/sdk/utils.d.ts +24 -1
- package/build/cjs/sdk/utils.js +50 -18
- package/build/esm/sdk/analytics.d.ts +1 -1
- package/build/esm/sdk/analytics.js +1 -1
- package/build/esm/sdk/index.d.ts +44 -42
- package/build/esm/sdk/index.js +151 -153
- package/build/esm/sdk/models.d.ts +7 -5
- package/build/esm/sdk/polling_manager.d.ts +3 -1
- package/build/esm/sdk/polling_manager.js +9 -2
- package/build/esm/sdk/types.d.ts +73 -5
- package/build/esm/sdk/utils.d.ts +24 -1
- package/build/esm/sdk/utils.js +48 -17
- package/package.json +2 -2
- package/sdk/analytics.ts +5 -5
- package/sdk/index.ts +173 -176
- package/sdk/models.ts +9 -12
- package/sdk/polling_manager.ts +9 -2
- package/sdk/types.ts +74 -3
- package/sdk/utils.ts +50 -16
- package/tests/sdk/analytics.test.ts +0 -4
- package/tests/sdk/flagsmith-cache.test.ts +5 -17
- package/tests/sdk/flagsmith-environment-flags.test.ts +1 -29
- package/tests/sdk/flagsmith-identity-flags.test.ts +20 -15
- package/tests/sdk/flagsmith.test.ts +88 -83
- package/tests/sdk/polling.test.ts +0 -4
- package/tests/sdk/utils.ts +28 -6
- package/vitest.config.ts +1 -0
- /package/{.prettierrc.js → .prettierrc.cjs} +0 -0
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import Flagsmith from '../../sdk/index.js';
|
|
2
2
|
import { EnvironmentDataPollingManager } from '../../sdk/polling_manager.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
environmentJSON,
|
|
5
|
+
environmentModel,
|
|
6
|
+
flagsmith,
|
|
7
|
+
fetch,
|
|
8
|
+
offlineEnvironmentJSON,
|
|
9
|
+
badFetch
|
|
10
|
+
} from './utils.js';
|
|
4
11
|
import { DefaultFlag, Flags } from '../../sdk/models.js';
|
|
5
12
|
import { delay } from '../../sdk/utils.js';
|
|
6
13
|
import { EnvironmentModel } from '../../flagsmith-engine/environments/models.js';
|
|
@@ -8,12 +15,7 @@ import { BaseOfflineHandler } from '../../sdk/offline_handlers.js';
|
|
|
8
15
|
import { Agent } from 'undici';
|
|
9
16
|
|
|
10
17
|
vi.mock('../../sdk/polling_manager');
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
vi.clearAllMocks();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
18
|
test('test_flagsmith_starts_polling_manager_on_init_if_enabled', () => {
|
|
16
|
-
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
17
19
|
new Flagsmith({
|
|
18
20
|
environmentKey: 'ser.key',
|
|
19
21
|
enableLocalEvaluation: true
|
|
@@ -22,30 +24,26 @@ test('test_flagsmith_starts_polling_manager_on_init_if_enabled', () => {
|
|
|
22
24
|
});
|
|
23
25
|
|
|
24
26
|
test('test_flagsmith_local_evaluation_key_required', () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
})
|
|
31
|
-
expect(console.error).toBeCalled();
|
|
27
|
+
expect(() => {
|
|
28
|
+
new Flagsmith({
|
|
29
|
+
environmentKey: 'bad.key',
|
|
30
|
+
enableLocalEvaluation: true
|
|
31
|
+
});
|
|
32
|
+
}).toThrow('Using local evaluation requires a server-side environment key')
|
|
32
33
|
});
|
|
33
34
|
|
|
34
35
|
test('test_update_environment_sets_environment', async () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expect(flg.environment).toBeDefined();
|
|
39
|
-
|
|
36
|
+
const flg = flagsmith({
|
|
37
|
+
environmentKey: 'ser.key',
|
|
38
|
+
});
|
|
40
39
|
const model = environmentModel(JSON.parse(environmentJSON));
|
|
41
|
-
|
|
42
|
-
expect(flg.environment).toStrictEqual(model);
|
|
40
|
+
expect(await flg.getEnvironment()).toStrictEqual(model);
|
|
43
41
|
});
|
|
44
42
|
|
|
45
43
|
test('test_set_agent_options', async () => {
|
|
46
44
|
const agent = new Agent({})
|
|
47
45
|
|
|
48
|
-
fetch.
|
|
46
|
+
fetch.mockImplementationOnce((url, options) => {
|
|
49
47
|
//@ts-ignore I give up
|
|
50
48
|
if (options.dispatcher !== agent) {
|
|
51
49
|
throw new Error("Agent has not been set on retry fetch")
|
|
@@ -58,11 +56,9 @@ test('test_set_agent_options', async () => {
|
|
|
58
56
|
});
|
|
59
57
|
|
|
60
58
|
await flg.updateEnvironment();
|
|
61
|
-
expect(flg.environment).toBeDefined();
|
|
62
59
|
});
|
|
63
60
|
|
|
64
61
|
test('test_get_identity_segments', async () => {
|
|
65
|
-
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
66
62
|
const flg = flagsmith({
|
|
67
63
|
environmentKey: 'ser.key',
|
|
68
64
|
enableLocalEvaluation: true
|
|
@@ -75,7 +71,6 @@ test('test_get_identity_segments', async () => {
|
|
|
75
71
|
|
|
76
72
|
|
|
77
73
|
test('test_get_identity_segments_empty_without_local_eval', async () => {
|
|
78
|
-
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
79
74
|
const flg = new Flagsmith({
|
|
80
75
|
environmentKey: 'ser.key',
|
|
81
76
|
enableLocalEvaluation: false
|
|
@@ -85,8 +80,6 @@ test('test_get_identity_segments_empty_without_local_eval', async () => {
|
|
|
85
80
|
});
|
|
86
81
|
|
|
87
82
|
test('test_update_environment_uses_req_when_inited', async () => {
|
|
88
|
-
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
89
|
-
|
|
90
83
|
const flg = flagsmith({
|
|
91
84
|
environmentKey: 'ser.key',
|
|
92
85
|
enableLocalEvaluation: true,
|
|
@@ -100,7 +93,6 @@ test('test_update_environment_uses_req_when_inited', async () => {
|
|
|
100
93
|
});
|
|
101
94
|
|
|
102
95
|
test('test_isFeatureEnabled_environment', async () => {
|
|
103
|
-
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
104
96
|
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
105
97
|
|
|
106
98
|
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
@@ -118,9 +110,7 @@ test('test_isFeatureEnabled_environment', async () => {
|
|
|
118
110
|
});
|
|
119
111
|
|
|
120
112
|
test('test_fetch_recovers_after_single_API_error', async () => {
|
|
121
|
-
fetch
|
|
122
|
-
.mockRejectedValue('Error during fetching the API response')
|
|
123
|
-
.mockResolvedValue(new Response(flagsJSON));
|
|
113
|
+
fetch.mockRejectedValueOnce('Error during fetching the API response')
|
|
124
114
|
const flg = flagsmith({
|
|
125
115
|
environmentKey: 'key',
|
|
126
116
|
});
|
|
@@ -132,36 +122,38 @@ test('test_fetch_recovers_after_single_API_error', async () => {
|
|
|
132
122
|
expect(flag.value).toBe('some-value');
|
|
133
123
|
});
|
|
134
124
|
|
|
135
|
-
test(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
125
|
+
test.each([
|
|
126
|
+
[false, 'key'],
|
|
127
|
+
[true, 'ser.key']
|
|
128
|
+
])(
|
|
129
|
+
'default flag handler is used when API is unavailable (local evaluation = %s)',
|
|
130
|
+
async (enableLocalEvaluation, environmentKey) => {
|
|
131
|
+
const flg = flagsmith({
|
|
132
|
+
enableLocalEvaluation,
|
|
133
|
+
environmentKey,
|
|
134
|
+
defaultFlagHandler: () => new DefaultFlag('some-default-value', true),
|
|
135
|
+
fetch: badFetch,
|
|
136
|
+
});
|
|
137
|
+
const flags = await flg.getEnvironmentFlags();
|
|
138
|
+
const flag = flags.getFlag('some_feature');
|
|
139
|
+
expect(flag.isDefault).toBe(true);
|
|
140
|
+
expect(flag.enabled).toBe(true);
|
|
141
|
+
expect(flag.value).toBe('some-default-value');
|
|
142
|
+
}
|
|
143
|
+
);
|
|
153
144
|
|
|
154
145
|
test('default flag handler used when timeout occurs', async () => {
|
|
155
146
|
fetch.mockImplementation(async (...args) => {
|
|
156
|
-
|
|
157
|
-
|
|
147
|
+
const forever = new Promise(() => {})
|
|
148
|
+
await forever
|
|
149
|
+
throw new Error('waited forever')
|
|
158
150
|
});
|
|
159
151
|
|
|
160
152
|
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
161
153
|
|
|
162
|
-
const defaultFlagHandler = (
|
|
154
|
+
const defaultFlagHandler = () => defaultFlag;
|
|
163
155
|
|
|
164
|
-
const flg =
|
|
156
|
+
const flg = flagsmith({
|
|
165
157
|
environmentKey: 'key',
|
|
166
158
|
defaultFlagHandler: defaultFlagHandler,
|
|
167
159
|
requestTimeoutSeconds: 0.0001,
|
|
@@ -184,10 +176,9 @@ test('request timeout uses default if not provided', async () => {
|
|
|
184
176
|
})
|
|
185
177
|
|
|
186
178
|
test('test_throws_when_no_identityFlags_returned_due_to_error', async () => {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const flg = new Flagsmith({
|
|
179
|
+
const flg = flagsmith({
|
|
190
180
|
environmentKey: 'key',
|
|
181
|
+
fetch: badFetch,
|
|
191
182
|
});
|
|
192
183
|
|
|
193
184
|
await expect(async () => await flg.getIdentityFlags('identifier'))
|
|
@@ -196,34 +187,32 @@ test('test_throws_when_no_identityFlags_returned_due_to_error', async () => {
|
|
|
196
187
|
});
|
|
197
188
|
|
|
198
189
|
test('test onEnvironmentChange is called when provided', async () => {
|
|
199
|
-
const callback =
|
|
200
|
-
callback: (e: Error | null, result: EnvironmentModel) => { }
|
|
201
|
-
};
|
|
202
|
-
const callbackSpy = vi.spyOn(callback, 'callback');
|
|
190
|
+
const callback = vi.fn()
|
|
203
191
|
|
|
204
192
|
const flg = new Flagsmith({
|
|
205
193
|
environmentKey: 'ser.key',
|
|
206
194
|
enableLocalEvaluation: true,
|
|
207
|
-
onEnvironmentChange: callback
|
|
195
|
+
onEnvironmentChange: callback,
|
|
208
196
|
});
|
|
209
197
|
|
|
210
|
-
|
|
198
|
+
fetch.mockRejectedValueOnce(new Error('API error'));
|
|
199
|
+
await flg.updateEnvironment().catch(() => {
|
|
200
|
+
// Expected rejection
|
|
201
|
+
});
|
|
211
202
|
|
|
212
|
-
expect(
|
|
203
|
+
expect(callback).toBeCalled();
|
|
213
204
|
});
|
|
214
205
|
|
|
215
206
|
test('test onEnvironmentChange is called after error', async () => {
|
|
216
|
-
const callback = vi.fn(
|
|
217
|
-
|
|
207
|
+
const callback = vi.fn();
|
|
218
208
|
const flg = new Flagsmith({
|
|
219
209
|
environmentKey: 'ser.key',
|
|
220
210
|
enableLocalEvaluation: true,
|
|
221
211
|
onEnvironmentChange: callback,
|
|
212
|
+
fetch: badFetch,
|
|
222
213
|
});
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
expect(callback).toBeCalled();
|
|
214
|
+
await flg.updateEnvironment();
|
|
215
|
+
expect(callback).toHaveBeenCalled();
|
|
227
216
|
});
|
|
228
217
|
|
|
229
218
|
test('getIdentityFlags throws error if identifier is empty string', async () => {
|
|
@@ -234,15 +223,15 @@ test('getIdentityFlags throws error if identifier is empty string', async () =>
|
|
|
234
223
|
await expect(flg.getIdentityFlags('')).rejects.toThrow('`identifier` argument is missing or invalid.');
|
|
235
224
|
})
|
|
236
225
|
|
|
237
|
-
|
|
238
|
-
test('getIdentitySegments throws error if identifier is empty string', () => {
|
|
226
|
+
test('getIdentitySegments throws error if identifier is empty string', async () => {
|
|
239
227
|
const flg = flagsmith({
|
|
240
228
|
environmentKey: 'key',
|
|
241
229
|
});
|
|
242
230
|
|
|
243
|
-
expect(
|
|
244
|
-
|
|
245
|
-
|
|
231
|
+
await expect(flg.getIdentitySegments('')).rejects.toThrow(
|
|
232
|
+
'`identifier` argument is missing or invalid.'
|
|
233
|
+
);
|
|
234
|
+
});
|
|
246
235
|
|
|
247
236
|
test('offline_mode', async () => {
|
|
248
237
|
// Given
|
|
@@ -286,6 +275,7 @@ test('test_flagsmith_uses_offline_handler_if_set_and_no_api_response', async ()
|
|
|
286
275
|
environmentKey: 'some-key',
|
|
287
276
|
apiUrl: api_url,
|
|
288
277
|
offlineHandler: mock_offline_handler,
|
|
278
|
+
offlineMode: true
|
|
289
279
|
});
|
|
290
280
|
|
|
291
281
|
vi.spyOn(flg, 'getEnvironmentFlags');
|
|
@@ -305,10 +295,10 @@ test('test_flagsmith_uses_offline_handler_if_set_and_no_api_response', async ()
|
|
|
305
295
|
|
|
306
296
|
// When
|
|
307
297
|
const environmentFlags: Flags = await flg.getEnvironmentFlags();
|
|
298
|
+
expect(mock_offline_handler.getEnvironment).toHaveBeenCalledTimes(1);
|
|
308
299
|
const identityFlags: Flags = await flg.getIdentityFlags('identity', {});
|
|
309
300
|
|
|
310
301
|
// Then
|
|
311
|
-
expect(mock_offline_handler.getEnvironment).toHaveBeenCalledTimes(1);
|
|
312
302
|
expect(flg.getEnvironmentFlags).toHaveBeenCalled();
|
|
313
303
|
expect(flg.getIdentityFlags).toHaveBeenCalled();
|
|
314
304
|
|
|
@@ -336,22 +326,37 @@ test('cannot use both default handler and offline handler', () => {
|
|
|
336
326
|
|
|
337
327
|
test('cannot create Flagsmith client in remote evaluation without API key', () => {
|
|
338
328
|
// When and Then
|
|
339
|
-
expect(() => new Flagsmith()).toThrowError('ValueError: environmentKey is required.');
|
|
329
|
+
expect(() => new Flagsmith({ environmentKey: '' })).toThrowError('ValueError: environmentKey is required.');
|
|
340
330
|
});
|
|
341
331
|
|
|
342
332
|
|
|
343
|
-
function sleep(ms: number) {
|
|
344
|
-
return new Promise((resolve) => {
|
|
345
|
-
setTimeout(resolve, ms);
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
333
|
test('test_localEvaluation_true__identity_overrides_evaluated', async () => {
|
|
349
|
-
|
|
334
|
+
const flg = flagsmith({
|
|
335
|
+
environmentKey: 'ser.key',
|
|
336
|
+
enableLocalEvaluation: true
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
await flg.updateEnvironment()
|
|
340
|
+
const flags = await flg.getIdentityFlags('overridden-id');
|
|
341
|
+
expect(flags.getFeatureValue('some_feature')).toEqual('some-overridden-value');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('getIdentityFlags succeeds if initial fetch failed then succeeded', async () => {
|
|
345
|
+
const defaultFlagHandler = vi.fn(() => new DefaultFlag('mock-default-value', true));
|
|
346
|
+
|
|
347
|
+
fetch.mockRejectedValue(new Error('Initial API error'));
|
|
350
348
|
const flg = flagsmith({
|
|
351
349
|
environmentKey: 'ser.key',
|
|
352
350
|
enableLocalEvaluation: true,
|
|
351
|
+
defaultFlagHandler
|
|
353
352
|
});
|
|
354
353
|
|
|
355
|
-
const
|
|
356
|
-
expect(
|
|
354
|
+
const defaultFlags = await flg.getIdentityFlags('test-user');
|
|
355
|
+
expect(defaultFlags.isFeatureEnabled('mock-default-value')).toBe(true);
|
|
356
|
+
expect(defaultFlagHandler).toHaveBeenCalled();
|
|
357
|
+
|
|
358
|
+
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
359
|
+
await flg.getEnvironment();
|
|
360
|
+
const flags2 = await flg.getIdentityFlags('test-user');
|
|
361
|
+
expect(flags2.isFeatureEnabled('some_feature')).toBe(true);
|
|
357
362
|
});
|
|
@@ -3,10 +3,6 @@ import { EnvironmentDataPollingManager } from '../../sdk/polling_manager.js';
|
|
|
3
3
|
import { delay } from '../../sdk/utils.js';
|
|
4
4
|
vi.mock('../../sdk');
|
|
5
5
|
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
vi.clearAllMocks()
|
|
8
|
-
});
|
|
9
|
-
|
|
10
6
|
test('test_polling_manager_correctly_stops_if_never_started', async () => {
|
|
11
7
|
const flagsmith = new Flagsmith({
|
|
12
8
|
environmentKey: 'key'
|
package/tests/sdk/utils.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { buildEnvironmentModel } from '../../flagsmith-engine/environments/util.js';
|
|
3
3
|
import { AnalyticsProcessor } from '../../sdk/analytics.js';
|
|
4
|
-
import Flagsmith from '../../sdk/index.js';
|
|
5
|
-
import { FlagsmithCache } from '../../sdk/types.js';
|
|
4
|
+
import Flagsmith, {FlagsmithConfig} from '../../sdk/index.js';
|
|
5
|
+
import { Fetch, FlagsmithCache } from '../../sdk/types.js';
|
|
6
6
|
import { Flags } from '../../sdk/models.js';
|
|
7
7
|
|
|
8
8
|
const DATA_DIR = __dirname + '/data/';
|
|
@@ -19,13 +19,33 @@ export class TestCache implements FlagsmithCache {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export const fetch = vi.fn(
|
|
22
|
+
export const fetch = vi.fn((url: string, options?: RequestInit) => {
|
|
23
|
+
const headers = options?.headers as Record<string, string>;
|
|
24
|
+
if (!headers) throw new Error('missing request headers')
|
|
25
|
+
const env = headers['X-Environment-Key'];
|
|
26
|
+
if (!env) return Promise.resolve(new Response('missing x-environment-key header', { status: 404 }));
|
|
27
|
+
if (url.includes('/environment-document')) {
|
|
28
|
+
if (env.startsWith('ser.')) {
|
|
29
|
+
return Promise.resolve(new Response(environmentJSON, { status: 200 }))
|
|
30
|
+
}
|
|
31
|
+
return Promise.resolve(new Response('environment-document called without a server-side key', { status: 401 }))
|
|
32
|
+
}
|
|
33
|
+
if (url.includes("/flags")) {
|
|
34
|
+
return Promise.resolve(new Response(flagsJSON, { status: 200 }))
|
|
35
|
+
}
|
|
36
|
+
if (url.includes("/identities")) {
|
|
37
|
+
return Promise.resolve(new Response(identitiesJSON, { status: 200 }))
|
|
38
|
+
}
|
|
39
|
+
return Promise.resolve(new Response('unknown url ' + url, { status: 404 }))
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const badFetch: Fetch = () => { throw new Error('fetch failed')}
|
|
23
43
|
|
|
24
44
|
export function analyticsProcessor() {
|
|
25
45
|
return new AnalyticsProcessor({
|
|
26
46
|
environmentKey: 'test-key',
|
|
27
47
|
analyticsUrl: 'http://testUrl/analytics/flags/',
|
|
28
|
-
fetch,
|
|
48
|
+
fetch: (url, options) => fetch(url.toString(), options),
|
|
29
49
|
});
|
|
30
50
|
}
|
|
31
51
|
|
|
@@ -33,10 +53,12 @@ export function apiKey(): string {
|
|
|
33
53
|
return 'sometestfakekey';
|
|
34
54
|
}
|
|
35
55
|
|
|
36
|
-
export function flagsmith(params = {}) {
|
|
56
|
+
export function flagsmith(params: FlagsmithConfig = {}) {
|
|
37
57
|
return new Flagsmith({
|
|
38
58
|
environmentKey: apiKey(),
|
|
39
|
-
|
|
59
|
+
environmentRefreshIntervalSeconds: 0,
|
|
60
|
+
requestRetryDelayMilliseconds: 0,
|
|
61
|
+
fetch: (url, options) => fetch(url.toString(), options),
|
|
40
62
|
...params,
|
|
41
63
|
});
|
|
42
64
|
}
|
package/vitest.config.ts
CHANGED
|
File without changes
|