launchdarkly-js-sdk-common 5.3.0 → 5.4.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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/release-please-config.json +3 -1
- package/src/__tests__/LDClient-events-test.js +92 -0
- package/src/__tests__/LDClient-inspectors-test.js +106 -2
- package/src/index.js +21 -6
- package/typings.d.ts +1 -1
- package/.circleci/config.yml +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `launchdarkly-js-sdk-common` package will be documented in this file. Changes that affect the dependent SDKs such as `launchdarkly-js-client-sdk` should also be logged in those projects, in the next release that uses the updated version of this package. This project adheres to [Semantic Versioning](http://semver.org).
|
|
4
4
|
|
|
5
|
+
## [5.4.0](https://github.com/launchdarkly/js-sdk-common/compare/5.3.0...5.4.0) (2024-10-18)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* Add support for client-side prerequisite events. ([#112](https://github.com/launchdarkly/js-sdk-common/issues/112)) ([9d1708b](https://github.com/launchdarkly/js-sdk-common/commit/9d1708b212246c5650794af99f79cd6a95cfbcd1))
|
|
11
|
+
|
|
5
12
|
## [5.3.0](https://github.com/launchdarkly/js-sdk-common/compare/launchdarkly-js-sdk-common-v5.2.0...launchdarkly-js-sdk-common-v5.3.0) (2024-06-18)
|
|
6
13
|
|
|
7
14
|
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
import * as messages from '../messages';
|
|
2
3
|
|
|
3
4
|
import { withCloseable, sleepAsync } from 'launchdarkly-js-test-helpers';
|
|
@@ -253,6 +254,81 @@ describe('LDClient events', () => {
|
|
|
253
254
|
});
|
|
254
255
|
});
|
|
255
256
|
|
|
257
|
+
it('sends events for prerequisites', async () => {
|
|
258
|
+
const initData = makeBootstrap({
|
|
259
|
+
'is-prereq': {
|
|
260
|
+
value: true,
|
|
261
|
+
variation: 1,
|
|
262
|
+
reason: {
|
|
263
|
+
kind: 'FALLTHROUGH',
|
|
264
|
+
},
|
|
265
|
+
version: 1,
|
|
266
|
+
trackEvents: true,
|
|
267
|
+
trackReason: true,
|
|
268
|
+
},
|
|
269
|
+
'has-prereq-depth-1': {
|
|
270
|
+
value: true,
|
|
271
|
+
variation: 0,
|
|
272
|
+
prerequisites: ['is-prereq'],
|
|
273
|
+
reason: {
|
|
274
|
+
kind: 'FALLTHROUGH',
|
|
275
|
+
},
|
|
276
|
+
version: 4,
|
|
277
|
+
trackEvents: true,
|
|
278
|
+
trackReason: true,
|
|
279
|
+
},
|
|
280
|
+
'has-prereq-depth-2': {
|
|
281
|
+
value: true,
|
|
282
|
+
variation: 0,
|
|
283
|
+
prerequisites: ['has-prereq-depth-1'],
|
|
284
|
+
reason: {
|
|
285
|
+
kind: 'FALLTHROUGH',
|
|
286
|
+
},
|
|
287
|
+
version: 5,
|
|
288
|
+
trackEvents: true,
|
|
289
|
+
trackReason: true,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
await withClientAndEventProcessor(user, { bootstrap: initData }, async (client, ep) => {
|
|
293
|
+
await client.waitForInitialization(5);
|
|
294
|
+
client.variation('has-prereq-depth-2', false);
|
|
295
|
+
|
|
296
|
+
// An identify event and 3 feature events.
|
|
297
|
+
expect(ep.events.length).toEqual(4);
|
|
298
|
+
expectIdentifyEvent(ep.events[0], user);
|
|
299
|
+
expect(ep.events[1]).toMatchObject({
|
|
300
|
+
kind: 'feature',
|
|
301
|
+
key: 'is-prereq',
|
|
302
|
+
variation: 1,
|
|
303
|
+
value: true,
|
|
304
|
+
version: 1,
|
|
305
|
+
reason: {
|
|
306
|
+
kind: 'FALLTHROUGH',
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
expect(ep.events[2]).toMatchObject({
|
|
310
|
+
kind: 'feature',
|
|
311
|
+
key: 'has-prereq-depth-1',
|
|
312
|
+
variation: 0,
|
|
313
|
+
value: true,
|
|
314
|
+
version: 4,
|
|
315
|
+
reason: {
|
|
316
|
+
kind: 'FALLTHROUGH',
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
expect(ep.events[3]).toMatchObject({
|
|
320
|
+
kind: 'feature',
|
|
321
|
+
key: 'has-prereq-depth-2',
|
|
322
|
+
variation: 0,
|
|
323
|
+
value: true,
|
|
324
|
+
version: 5,
|
|
325
|
+
reason: {
|
|
326
|
+
kind: 'FALLTHROUGH',
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
256
332
|
it('sends a feature event on receiving a new flag value', async () => {
|
|
257
333
|
const oldFlags = { foo: { value: 'a', variation: 1, version: 2, flagVersion: 2000 } };
|
|
258
334
|
const newFlags = { foo: { value: 'b', variation: 2, version: 3, flagVersion: 2001 } };
|
|
@@ -327,6 +403,22 @@ describe('LDClient events', () => {
|
|
|
327
403
|
});
|
|
328
404
|
});
|
|
329
405
|
|
|
406
|
+
it('does not send duplicate events for prerequisites with all flags.', async () => {
|
|
407
|
+
const initData = makeBootstrap({
|
|
408
|
+
foo: { value: 'a', variation: 1, version: 2 },
|
|
409
|
+
bar: { value: 'b', variation: 1, version: 3, prerequisites: ['foo'] },
|
|
410
|
+
});
|
|
411
|
+
await withClientAndEventProcessor(user, { bootstrap: initData }, async (client, ep) => {
|
|
412
|
+
await client.waitForInitialization(5);
|
|
413
|
+
client.allFlags();
|
|
414
|
+
|
|
415
|
+
expect(ep.events.length).toEqual(3);
|
|
416
|
+
expectIdentifyEvent(ep.events[0], user);
|
|
417
|
+
expectFeatureEvent({ e: ep.events[1], key: 'foo', user, value: 'a', variation: 1, version: 2, defaultVal: null });
|
|
418
|
+
expectFeatureEvent({ e: ep.events[2], key: 'bar', user, value: 'b', variation: 1, version: 3, defaultVal: null });
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
330
422
|
it('does not send feature events for allFlags() if sendEventsOnlyForVariation is set', async () => {
|
|
331
423
|
const initData = makeBootstrap({
|
|
332
424
|
foo: { value: 'a', variation: 1, version: 2 },
|
|
@@ -5,6 +5,41 @@ const stubPlatform = require('./stubPlatform');
|
|
|
5
5
|
const envName = 'UNKNOWN_ENVIRONMENT_ID';
|
|
6
6
|
const context = { key: 'context-key' };
|
|
7
7
|
|
|
8
|
+
const flagPayload = {
|
|
9
|
+
'is-prereq': {
|
|
10
|
+
value: true,
|
|
11
|
+
variation: 1,
|
|
12
|
+
reason: {
|
|
13
|
+
kind: 'FALLTHROUGH',
|
|
14
|
+
},
|
|
15
|
+
version: 1,
|
|
16
|
+
trackEvents: true,
|
|
17
|
+
trackReason: true,
|
|
18
|
+
},
|
|
19
|
+
'has-prereq-depth-1': {
|
|
20
|
+
value: true,
|
|
21
|
+
variation: 0,
|
|
22
|
+
prerequisites: ['is-prereq'],
|
|
23
|
+
reason: {
|
|
24
|
+
kind: 'FALLTHROUGH',
|
|
25
|
+
},
|
|
26
|
+
version: 4,
|
|
27
|
+
trackEvents: true,
|
|
28
|
+
trackReason: true,
|
|
29
|
+
},
|
|
30
|
+
'has-prereq-depth-2': {
|
|
31
|
+
value: true,
|
|
32
|
+
variation: 0,
|
|
33
|
+
prerequisites: ['has-prereq-depth-1'],
|
|
34
|
+
reason: {
|
|
35
|
+
kind: 'FALLTHROUGH',
|
|
36
|
+
},
|
|
37
|
+
version: 5,
|
|
38
|
+
trackEvents: true,
|
|
39
|
+
trackReason: true,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
8
43
|
describe.each([true, false])('given a streaming client with registered inspectors, synchronous: %p', synchronous => {
|
|
9
44
|
const eventQueue = new AsyncQueue();
|
|
10
45
|
|
|
@@ -63,7 +98,7 @@ describe.each([true, false])('given a streaming client with registered inspector
|
|
|
63
98
|
beforeEach(async () => {
|
|
64
99
|
platform = stubPlatform.defaults();
|
|
65
100
|
const server = platform.testing.http.newServer();
|
|
66
|
-
server.byDefault(respondJson(
|
|
101
|
+
server.byDefault(respondJson(flagPayload));
|
|
67
102
|
const config = { streaming: true, baseUrl: server.url, inspectors, sendEvents: false };
|
|
68
103
|
client = platform.testing.makeClient(envName, context, config);
|
|
69
104
|
await client.waitUntilReady();
|
|
@@ -91,7 +126,29 @@ describe.each([true, false])('given a streaming client with registered inspector
|
|
|
91
126
|
const flagsEvent = await eventQueue.take();
|
|
92
127
|
expect(flagsEvent).toMatchObject({
|
|
93
128
|
type: 'flag-details-changed',
|
|
94
|
-
details: {
|
|
129
|
+
details: {
|
|
130
|
+
'is-prereq': {
|
|
131
|
+
value: true,
|
|
132
|
+
variationIndex: 1,
|
|
133
|
+
reason: {
|
|
134
|
+
kind: 'FALLTHROUGH',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
'has-prereq-depth-1': {
|
|
138
|
+
value: true,
|
|
139
|
+
variationIndex: 0,
|
|
140
|
+
reason: {
|
|
141
|
+
kind: 'FALLTHROUGH',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
'has-prereq-depth-2': {
|
|
145
|
+
value: true,
|
|
146
|
+
variationIndex: 0,
|
|
147
|
+
reason: {
|
|
148
|
+
kind: 'FALLTHROUGH',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
95
152
|
});
|
|
96
153
|
});
|
|
97
154
|
|
|
@@ -129,4 +186,51 @@ describe.each([true, false])('given a streaming client with registered inspector
|
|
|
129
186
|
flagDetail: { value: false },
|
|
130
187
|
});
|
|
131
188
|
});
|
|
189
|
+
|
|
190
|
+
it('emits an event when a flag is used', async () => {
|
|
191
|
+
// Take initial events.
|
|
192
|
+
eventQueue.take();
|
|
193
|
+
eventQueue.take();
|
|
194
|
+
|
|
195
|
+
await platform.testing.eventSourcesCreated.take();
|
|
196
|
+
client.variation('is-prereq', false);
|
|
197
|
+
const updateEvent = await eventQueue.take();
|
|
198
|
+
expect(updateEvent).toMatchObject({
|
|
199
|
+
type: 'flag-used',
|
|
200
|
+
flagKey: 'is-prereq',
|
|
201
|
+
flagDetail: { value: true },
|
|
202
|
+
});
|
|
203
|
+
// Two inspectors are handling this
|
|
204
|
+
const updateEvent2 = await eventQueue.take();
|
|
205
|
+
expect(updateEvent2).toMatchObject({
|
|
206
|
+
type: 'flag-used',
|
|
207
|
+
flagKey: 'is-prereq',
|
|
208
|
+
flagDetail: { value: true },
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('does not execute flag-used for prerequisites', async () => {
|
|
213
|
+
// Take initial events.
|
|
214
|
+
eventQueue.take();
|
|
215
|
+
eventQueue.take();
|
|
216
|
+
|
|
217
|
+
await platform.testing.eventSourcesCreated.take();
|
|
218
|
+
client.variation('has-prereq-depth-2', false);
|
|
219
|
+
// There would be many more than 2 events if prerequisites were inspected.
|
|
220
|
+
const updateEvent = await eventQueue.take();
|
|
221
|
+
expect(updateEvent).toMatchObject({
|
|
222
|
+
type: 'flag-used',
|
|
223
|
+
flagKey: 'has-prereq-depth-2',
|
|
224
|
+
flagDetail: { value: true },
|
|
225
|
+
});
|
|
226
|
+
// Two inspectors are handling this
|
|
227
|
+
const updateEvent2 = await eventQueue.take();
|
|
228
|
+
expect(updateEvent2).toMatchObject({
|
|
229
|
+
type: 'flag-used',
|
|
230
|
+
flagKey: 'has-prereq-depth-2',
|
|
231
|
+
flagDetail: { value: true },
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(eventQueue.length()).toEqual(0);
|
|
235
|
+
});
|
|
132
236
|
});
|
package/src/index.js
CHANGED
|
@@ -299,18 +299,19 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
function variation(key, defaultValue) {
|
|
302
|
-
return variationDetailInternal(key, defaultValue, true, false, false).value;
|
|
302
|
+
return variationDetailInternal(key, defaultValue, true, false, false, true).value;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
function variationDetail(key, defaultValue) {
|
|
306
|
-
return variationDetailInternal(key, defaultValue, true, true, false);
|
|
306
|
+
return variationDetailInternal(key, defaultValue, true, true, false, true);
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
function variationDetailInternal(key, defaultValue, sendEvent, includeReasonInEvent, isAllFlags) {
|
|
309
|
+
function variationDetailInternal(key, defaultValue, sendEvent, includeReasonInEvent, isAllFlags, notifyInspection) {
|
|
310
310
|
let detail;
|
|
311
|
+
let flag;
|
|
311
312
|
|
|
312
313
|
if (flags && utils.objectHasOwnProperty(flags, key) && flags[key] && !flags[key].deleted) {
|
|
313
|
-
|
|
314
|
+
flag = flags[key];
|
|
314
315
|
detail = getFlagDetail(flag);
|
|
315
316
|
if (flag.value === null || flag.value === undefined) {
|
|
316
317
|
detail.value = defaultValue;
|
|
@@ -320,11 +321,18 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
320
321
|
}
|
|
321
322
|
|
|
322
323
|
if (sendEvent) {
|
|
324
|
+
// For an all-flags evaluation, with events enabled, each flag will get an event, so we do not
|
|
325
|
+
// need to duplicate the prerequisites.
|
|
326
|
+
if (!isAllFlags) {
|
|
327
|
+
flag?.prerequisites?.forEach(key => {
|
|
328
|
+
variationDetailInternal(key, undefined, sendEvent, false, false, false);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
323
331
|
sendFlagEvent(key, detail, defaultValue, includeReasonInEvent);
|
|
324
332
|
}
|
|
325
333
|
|
|
326
334
|
// For the all flags case `onFlags` will be called instead.
|
|
327
|
-
if (!isAllFlags) {
|
|
335
|
+
if (!isAllFlags && notifyInspection) {
|
|
328
336
|
notifyInspectionFlagUsed(key, detail);
|
|
329
337
|
}
|
|
330
338
|
|
|
@@ -351,7 +359,14 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
351
359
|
|
|
352
360
|
for (const key in flags) {
|
|
353
361
|
if (utils.objectHasOwnProperty(flags, key) && !flags[key].deleted) {
|
|
354
|
-
results[key] = variationDetailInternal(
|
|
362
|
+
results[key] = variationDetailInternal(
|
|
363
|
+
key,
|
|
364
|
+
null,
|
|
365
|
+
!options.sendEventsOnlyForVariation,
|
|
366
|
+
false,
|
|
367
|
+
true,
|
|
368
|
+
false
|
|
369
|
+
).value;
|
|
355
370
|
}
|
|
356
371
|
}
|
|
357
372
|
|
package/typings.d.ts
CHANGED
|
@@ -552,7 +552,7 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
552
552
|
|
|
553
553
|
/**
|
|
554
554
|
* Describes the reason that a flag evaluation produced a particular value. This is
|
|
555
|
-
* part of the {@link LDEvaluationDetail} object returned by {@link LDClient.variationDetail
|
|
555
|
+
* part of the {@link LDEvaluationDetail} object returned by {@link LDClient.variationDetail}.
|
|
556
556
|
*/
|
|
557
557
|
export interface LDEvaluationReason {
|
|
558
558
|
/**
|
package/.circleci/config.yml
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
jobs:
|
|
3
|
-
build:
|
|
4
|
-
docker:
|
|
5
|
-
- image: cimg/node:22.2.0
|
|
6
|
-
steps:
|
|
7
|
-
- checkout
|
|
8
|
-
|
|
9
|
-
- run: npm install
|
|
10
|
-
- run: npm run lint:all
|
|
11
|
-
- run:
|
|
12
|
-
command: npm test
|
|
13
|
-
environment:
|
|
14
|
-
JEST_JUNIT_OUTPUT: "reports/junit/js-test-results.xml"
|
|
15
|
-
- run: npm run check-typescript
|
|
16
|
-
- run:
|
|
17
|
-
name: dependency audit
|
|
18
|
-
command: ./scripts/better-audit.sh
|
|
19
|
-
- store_test_results:
|
|
20
|
-
path: reports/junit/
|
|
21
|
-
- store_artifacts:
|
|
22
|
-
path: reports/junit/
|