Skip to content

Commit 7e490f4

Browse files
authored
feat: Allow for synchronous inspectors. (#103)
1 parent 98a1733 commit 7e490f4

File tree

7 files changed

+85
-14
lines changed

7 files changed

+85
-14
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: 2
22
jobs:
33
build:
44
docker:
5-
- image: cimg/node:12.22
5+
- image: cimg/node:22.2.0
66
steps:
77
- checkout
88

src/InspectorManager.js

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ function InspectorManager(inspectors, logger) {
2222

2323
/**
2424
* Collection of inspectors keyed by type.
25+
*
26+
* Inspectors are async by default.
27+
*
2528
* @type {{[type: string]: object[]}}
2629
*/
2730
const inspectorsByType = {
@@ -30,14 +33,30 @@ function InspectorManager(inspectors, logger) {
3033
[InspectorTypes.flagDetailChanged]: [],
3134
[InspectorTypes.clientIdentityChanged]: [],
3235
};
36+
/**
37+
* Collection synchronous of inspectors keyed by type.
38+
*
39+
* @type {{[type: string]: object[]}}
40+
*/
41+
const synchronousInspectorsByType = {
42+
[InspectorTypes.flagUsed]: [],
43+
[InspectorTypes.flagDetailsChanged]: [],
44+
[InspectorTypes.flagDetailChanged]: [],
45+
[InspectorTypes.clientIdentityChanged]: [],
46+
};
3347

3448
const safeInspectors = inspectors && inspectors.map(inspector => SafeInspector(inspector, logger));
3549

3650
safeInspectors &&
3751
safeInspectors.forEach(safeInspector => {
3852
// Only add inspectors of supported types.
39-
if (Object.prototype.hasOwnProperty.call(inspectorsByType, safeInspector.type)) {
53+
if (Object.prototype.hasOwnProperty.call(inspectorsByType, safeInspector.type) && !safeInspector.synchronous) {
4054
inspectorsByType[safeInspector.type].push(safeInspector);
55+
} else if (
56+
Object.prototype.hasOwnProperty.call(synchronousInspectorsByType, safeInspector.type) &&
57+
safeInspector.synchronous
58+
) {
59+
synchronousInspectorsByType[safeInspector.type].push(safeInspector);
4160
} else {
4261
logger.warn(messages.invalidInspector(safeInspector.type, safeInspector.name));
4362
}
@@ -49,7 +68,9 @@ function InspectorManager(inspectors, logger) {
4968
* @param {string} type The type of the inspector to check.
5069
* @returns True if there are any inspectors of that type registered.
5170
*/
52-
manager.hasListeners = type => inspectorsByType[type] && inspectorsByType[type].length;
71+
manager.hasListeners = type =>
72+
(inspectorsByType[type] && inspectorsByType[type].length) ||
73+
(synchronousInspectorsByType[type] && synchronousInspectorsByType[type].length);
5374

5475
/**
5576
* Notify registered inspectors of a flag being used.
@@ -61,9 +82,13 @@ function InspectorManager(inspectors, logger) {
6182
* @param {Object} context The LDContext for the flag.
6283
*/
6384
manager.onFlagUsed = (flagKey, detail, context) => {
64-
if (inspectorsByType[InspectorTypes.flagUsed].length) {
85+
const type = InspectorTypes.flagUsed;
86+
if (synchronousInspectorsByType[type].length) {
87+
synchronousInspectorsByType[type].forEach(inspector => inspector.method(flagKey, detail, context));
88+
}
89+
if (inspectorsByType[type].length) {
6590
onNextTick(() => {
66-
inspectorsByType[InspectorTypes.flagUsed].forEach(inspector => inspector.method(flagKey, detail, context));
91+
inspectorsByType[type].forEach(inspector => inspector.method(flagKey, detail, context));
6792
});
6893
}
6994
};
@@ -76,9 +101,13 @@ function InspectorManager(inspectors, logger) {
76101
* @param {Record<string, Object>} flags The current flags as a Record<string, LDEvaluationDetail>.
77102
*/
78103
manager.onFlags = flags => {
79-
if (inspectorsByType[InspectorTypes.flagDetailsChanged].length) {
104+
const type = InspectorTypes.flagDetailsChanged;
105+
if (synchronousInspectorsByType[type].length) {
106+
synchronousInspectorsByType[type].forEach(inspector => inspector.method(flags));
107+
}
108+
if (inspectorsByType[type].length) {
80109
onNextTick(() => {
81-
inspectorsByType[InspectorTypes.flagDetailsChanged].forEach(inspector => inspector.method(flags));
110+
inspectorsByType[type].forEach(inspector => inspector.method(flags));
82111
});
83112
}
84113
};
@@ -92,9 +121,13 @@ function InspectorManager(inspectors, logger) {
92121
* @param {Object} flag An `LDEvaluationDetail` for the flag.
93122
*/
94123
manager.onFlagChanged = (flagKey, flag) => {
95-
if (inspectorsByType[InspectorTypes.flagDetailChanged].length) {
124+
const type = InspectorTypes.flagDetailChanged;
125+
if (synchronousInspectorsByType[type].length) {
126+
synchronousInspectorsByType[type].forEach(inspector => inspector.method(flagKey, flag));
127+
}
128+
if (inspectorsByType[type].length) {
96129
onNextTick(() => {
97-
inspectorsByType[InspectorTypes.flagDetailChanged].forEach(inspector => inspector.method(flagKey, flag));
130+
inspectorsByType[type].forEach(inspector => inspector.method(flagKey, flag));
98131
});
99132
}
100133
};
@@ -107,9 +140,13 @@ function InspectorManager(inspectors, logger) {
107140
* @param {Object} context The `LDContext` which is now identified.
108141
*/
109142
manager.onIdentityChanged = context => {
110-
if (inspectorsByType[InspectorTypes.clientIdentityChanged].length) {
143+
const type = InspectorTypes.clientIdentityChanged;
144+
if (synchronousInspectorsByType[type].length) {
145+
synchronousInspectorsByType[type].forEach(inspector => inspector.method(context));
146+
}
147+
if (inspectorsByType[type].length) {
111148
onNextTick(() => {
112-
inspectorsByType[InspectorTypes.clientIdentityChanged].forEach(inspector => inspector.method(context));
149+
inspectorsByType[type].forEach(inspector => inspector.method(context));
113150
});
114151
}
115152
};

src/SafeInspector.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ function SafeInspector(inspector, logger) {
99
const wrapper = {
1010
type: inspector.type,
1111
name: inspector.name,
12+
synchronous: inspector.synchronous,
1213
};
1314

1415
wrapper.method = (...args) => {

src/__tests__/InspectorManager-test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('given an inspector manager with no registered inspectors', () => {
2828
});
2929
});
3030

31-
describe('given an inspector with callbacks of every type', () => {
31+
describe.each([true, false])('given an inspector with callbacks of every type: synchronous: %p', synchronous => {
3232
/**
3333
* @type {AsyncQueue}
3434
*/
@@ -39,6 +39,7 @@ describe('given an inspector with callbacks of every type', () => {
3939
{
4040
type: 'flag-used',
4141
name: 'my-flag-used-inspector',
42+
synchronous,
4243
method: (flagKey, flagDetail, context) => {
4344
eventQueue.add({ type: 'flag-used', flagKey, flagDetail, context });
4445
},
@@ -47,13 +48,15 @@ describe('given an inspector with callbacks of every type', () => {
4748
{
4849
type: 'flag-used',
4950
name: 'my-other-flag-used-inspector',
51+
synchronous,
5052
method: (flagKey, flagDetail, context) => {
5153
eventQueue.add({ type: 'flag-used', flagKey, flagDetail, context });
5254
},
5355
},
5456
{
5557
type: 'flag-details-changed',
5658
name: 'my-flag-details-inspector',
59+
synchronous,
5760
method: details => {
5861
eventQueue.add({
5962
type: 'flag-details-changed',
@@ -64,6 +67,7 @@ describe('given an inspector with callbacks of every type', () => {
6467
{
6568
type: 'flag-detail-changed',
6669
name: 'my-flag-detail-inspector',
70+
synchronous,
6771
method: (flagKey, flagDetail) => {
6872
eventQueue.add({
6973
type: 'flag-detail-changed',
@@ -75,6 +79,7 @@ describe('given an inspector with callbacks of every type', () => {
7579
{
7680
type: 'client-identity-changed',
7781
name: 'my-identity-inspector',
82+
synchronous,
7883
method: context => {
7984
eventQueue.add({
8085
type: 'client-identity-changed',
@@ -85,6 +90,7 @@ describe('given an inspector with callbacks of every type', () => {
8590
// Invalid inspector shouldn't have an effect.
8691
{
8792
type: 'potato',
93+
synchronous,
8894
name: 'my-potato-inspector',
8995
method: () => {},
9096
},

src/__tests__/LDClient-inspectors-test.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,28 @@ const stubPlatform = require('./stubPlatform');
55
const envName = 'UNKNOWN_ENVIRONMENT_ID';
66
const context = { key: 'context-key' };
77

8-
describe('given a streaming client with registered inspectors', () => {
8+
describe.each([true, false])('given a streaming client with registered inspectors, synchronous: %p', synchronous => {
99
const eventQueue = new AsyncQueue();
1010

1111
const inspectors = [
1212
{
1313
type: 'flag-used',
14+
synchronous,
1415
method: (flagKey, flagDetail, context) => {
1516
eventQueue.add({ type: 'flag-used', flagKey, flagDetail, context });
1617
},
1718
},
1819
// 'flag-used registered twice.
1920
{
2021
type: 'flag-used',
22+
synchronous,
2123
method: (flagKey, flagDetail, context) => {
2224
eventQueue.add({ type: 'flag-used', flagKey, flagDetail, context });
2325
},
2426
},
2527
{
2628
type: 'flag-details-changed',
29+
synchronous,
2730
method: details => {
2831
eventQueue.add({
2932
type: 'flag-details-changed',
@@ -33,6 +36,7 @@ describe('given a streaming client with registered inspectors', () => {
3336
},
3437
{
3538
type: 'flag-detail-changed',
39+
synchronous,
3640
method: (flagKey, flagDetail) => {
3741
eventQueue.add({
3842
type: 'flag-detail-changed',
@@ -43,6 +47,7 @@ describe('given a streaming client with registered inspectors', () => {
4347
},
4448
{
4549
type: 'client-identity-changed',
50+
synchronous,
4651
method: context => {
4752
eventQueue.add({
4853
type: 'client-identity-changed',

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,8 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
462462
} else {
463463
mods[data.key] = { current: newDetail };
464464
}
465-
handleFlagChanges(mods); // don't wait for this Promise to be resolved
466465
notifyInspectionFlagChanged(data, newFlag);
466+
handleFlagChanges(mods); // don't wait for this Promise to be resolved
467467
} else {
468468
logger.debug(messages.debugStreamPatchIgnored(data.key));
469469
}

typings.d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,13 @@ declare module 'launchdarkly-js-sdk-common' {
10131013
*/
10141014
name: string;
10151015

1016+
/**
1017+
* If `true`, then the inspector will be ran synchronously with evaluation.
1018+
* Synchronous inspectors execute inline with evaluation and care should be taken to ensure
1019+
* they have minimal performance overhead.
1020+
*/
1021+
synchronous?: boolean,
1022+
10161023
/**
10171024
* This method is called when a flag is accessed via a variation method, or it can be called based on actions in
10181025
* wrapper SDKs which have different methods of tracking when a flag was accessed. It is not called when a call is made
@@ -1040,6 +1047,11 @@ declare module 'launchdarkly-js-sdk-common' {
10401047
*/
10411048
name: string;
10421049

1050+
/**
1051+
* If `true`, then the inspector will be ran synchronously with flag updates.
1052+
*/
1053+
synchronous?: boolean,
1054+
10431055
/**
10441056
* This method is called when the flags in the store are replaced with new flags. It will contain all flags
10451057
* regardless of if they have been evaluated.
@@ -1065,6 +1077,11 @@ declare module 'launchdarkly-js-sdk-common' {
10651077
*/
10661078
name: string;
10671079

1080+
/**
1081+
* If `true`, then the inspector will be ran synchronously with flag updates.
1082+
*/
1083+
synchronous?: boolean,
1084+
10681085
/**
10691086
* This method is called when a flag is updated. It will not be called
10701087
* when all flags are updated.
@@ -1088,6 +1105,11 @@ declare module 'launchdarkly-js-sdk-common' {
10881105
*/
10891106
name: string;
10901107

1108+
/**
1109+
* If `true`, then the inspector will be ran synchronously with identification.
1110+
*/
1111+
synchronous?: boolean,
1112+
10911113
/**
10921114
* This method will be called when an identify operation completes.
10931115
*/

0 commit comments

Comments
 (0)