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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "5.3.0"
2
+ ".": "5.4.0"
3
3
  }
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,6 +1,6 @@
1
1
  {
2
2
  "name": "launchdarkly-js-sdk-common",
3
- "version": "5.3.0",
3
+ "version": "5.4.0",
4
4
  "description": "LaunchDarkly SDK for JavaScript - common code",
5
5
  "author": "LaunchDarkly <team@launchdarkly.com>",
6
6
  "license": "Apache-2.0",
@@ -2,7 +2,9 @@
2
2
  "bootstrap-sha": "d49ca41718a593c071874950d301f2f00c71a371",
3
3
  "packages": {
4
4
  ".": {
5
- "release-type": "node"
5
+ "release-type": "node",
6
+ "include-v-in-tag": false,
7
+ "include-component-in-tag": false
6
8
  }
7
9
  }
8
10
  }
@@ -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
- const flag = flags[key];
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(key, null, !options.sendEventsOnlyForVariation, false, true).value;
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
  /**
@@ -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/