Skip to content

Commit 8b2f757

Browse files
feat: Add experimental debug override functionality. (#132)
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/master/CONTRIBUTING.md#submitting-pull-requests) - [ ] I have validated my changes against all supported platform versions **Describe the solution you've provided** This PR adds debug override capability to the LaunchDarkly SDK, allowing developers to override flag values during local development and testing without affecting production flag configurations. Key features: - Flag Override Interface: New `LDDebugOverride` interface with methods to set, remove, and manage flag overrides. - Plugin Integration: Extended `LDPlugin` interface with optional `registerDebug` method for plugins to access override functionality. - Runtime Override Management: Override values take precedence over remote flag values while preserving original flag evaluation logic. - Testing: Added test coverage for all override methods and edge cases. **Additional context** This feature is designed primarily for development and testing workflows where developers need to: - Test different flag variations without modifying remote configurations. - Debug applications with specific flag states. - Validate feature behavior across different flag combinations. The implementation maintains backward compatibility and follows the existing plugin architecture patterns. Override functionality is only available when explicitly enabled through plugins, ensuring it doesn't impact production performance or security. --------- Co-authored-by: Subhav Gautam <[email protected]>
1 parent 78f6f7e commit 8b2f757

File tree

8 files changed

+751
-42
lines changed

8 files changed

+751
-42
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
"launchdarkly-js-test-helpers": "1.1.0",
4545
"prettier": "1.19.1",
4646
"readline-sync": "^1.4.9",
47-
"typescript": "~5.4.5",
48-
"typedoc": "^0.25.13"
47+
"typedoc": "^0.25.13",
48+
"typescript": "~5.4.5"
4949
},
5050
"dependencies": {
5151
"base64-js": "^1.3.0",

src/FlagStore.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
const utils = require('./utils');
2+
3+
/**
4+
* FlagStore - Centralized flag store and access point for all feature flags
5+
*
6+
* This module manages two types of feature flags:
7+
* 1. Regular flags - Retrieved from LaunchDarkly servers or bootstrap data
8+
* 2. Override flags - Local overrides for debugging/testing
9+
*
10+
* When a flag is requested:
11+
* - If an override exists for that flag, the override value is returned
12+
* - Otherwise, the regular flag value is returned
13+
*/
14+
function FlagStore() {
15+
let flags = {};
16+
// The flag overrides are set lazily to allow bypassing property checks when no overrides are present.
17+
let flagOverrides;
18+
19+
/**
20+
* Gets a single flag by key, with overrides taking precedence over regular flags
21+
* @param {string} key The flag key to retrieve
22+
* @returns {Object|null} The flag object or null if not found
23+
*/
24+
function get(key) {
25+
// Check overrides first, then real flags
26+
if (flagOverrides && utils.objectHasOwnProperty(flagOverrides, key) && flagOverrides[key]) {
27+
return flagOverrides[key];
28+
}
29+
30+
if (flags && utils.objectHasOwnProperty(flags, key) && flags[key] && !flags[key].deleted) {
31+
return flags[key];
32+
}
33+
34+
return null;
35+
}
36+
37+
/**
38+
* Gets all flags with overrides applied
39+
* @returns {Object} Object containing all flags with any overrides applied
40+
*/
41+
function getFlagsWithOverrides() {
42+
const result = {};
43+
44+
// Add all flags first
45+
for (const key in flags) {
46+
const flag = get(key);
47+
if (flag) {
48+
result[key] = flag;
49+
}
50+
}
51+
52+
// Override with any flagOverrides (they take precedence)
53+
if (flagOverrides) {
54+
for (const key in flagOverrides) {
55+
const override = get(key);
56+
if (override) {
57+
result[key] = override;
58+
}
59+
}
60+
}
61+
62+
return result;
63+
}
64+
65+
/**
66+
* Replaces all flags with new flag data
67+
* @param {Object} newFlags - Object containing the new flag data
68+
*/
69+
function setFlags(newFlags) {
70+
flags = { ...newFlags };
71+
}
72+
73+
/**
74+
* Sets an override value for a specific flag
75+
* @param {string} key The flag key to override
76+
* @param {*} value The override value for the flag
77+
*/
78+
function setOverride(key, value) {
79+
if (!flagOverrides) {
80+
flagOverrides = {};
81+
}
82+
flagOverrides[key] = { value };
83+
}
84+
85+
/**
86+
* Removes an override for a specific flag
87+
* @param {string} key The flag key to remove the override for
88+
*/
89+
function removeOverride(key) {
90+
if (!flagOverrides || !flagOverrides[key]) {
91+
return; // No override to remove
92+
}
93+
94+
delete flagOverrides[key];
95+
96+
// If no more overrides, reset to undefined for performance
97+
if (Object.keys(flagOverrides).length === 0) {
98+
flagOverrides = undefined;
99+
}
100+
}
101+
102+
/**
103+
* Clears all flag overrides and returns the cleared overrides
104+
* @returns {Object} The overrides that were cleared, useful for tracking what was removed
105+
*/
106+
function clearAllOverrides() {
107+
if (!flagOverrides) {
108+
return {}; // No overrides to clear, return empty object for consistency
109+
}
110+
111+
const clearedOverrides = { ...flagOverrides };
112+
flagOverrides = undefined; // Reset to undefined
113+
return clearedOverrides;
114+
}
115+
116+
/**
117+
* Gets the internal flag state without overrides applied
118+
* @returns {Object} The internal flag data structure
119+
*/
120+
function getFlags() {
121+
return flags;
122+
}
123+
124+
/**
125+
* Gets the flag overrides data
126+
* @returns {Object} The flag overrides object, or empty object if no overrides exist
127+
*/
128+
function getFlagOverrides() {
129+
return flagOverrides || {};
130+
}
131+
132+
return {
133+
clearAllOverrides,
134+
get,
135+
getFlagOverrides,
136+
getFlags,
137+
getFlagsWithOverrides,
138+
removeOverride,
139+
setFlags,
140+
setOverride,
141+
};
142+
}
143+
144+
module.exports = FlagStore;

0 commit comments

Comments
 (0)