Skip to content

Commit 70836b7

Browse files
authored
Improved Switcher implementation, optimizaed for sync/async usage (#205)
* Improved Switcher implementation, optimizaed for sync/async usage * test: fixes timed-match suite * test: fixed TimedMatch test prep and Worker Thread initialization * test: fixed TimedMatch: added timeout error margin * test: fixed TimedMatch: added timeout error margin
1 parent 52a94c9 commit 70836b7

File tree

18 files changed

+406
-265
lines changed

18 files changed

+406
-265
lines changed

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,23 @@ const switcher = Client.getSwitcher();
9696
There are a few different ways to call the API.
9797
Here are some examples:
9898

99-
1. **No parameters**
100-
This is the simplest way to execute a criteria. It will return a boolean value indicating whether the feature is enabled or not.
99+
1. **Basic usage**
100+
Some of the ways you can check if a feature is enabled or not.
101101

102102
```js
103103
const switcher = Client.getSwitcher();
104-
await switcher.isItOn('FEATURE01');
105-
// or
106-
const { result, reason, metadata } = await switcher.detail().isItOn('FEATURE01');
104+
105+
// Local (synchronous) execution
106+
const isOnBool = switcher.isItOn('FEATURE01'); // true or false
107+
const isOnBool = switcher.isItOnBool('FEATURE01'); // true or false
108+
const isOnDetail = switcher.detail().isItOn('FEATURE01'); // { result: true, reason: 'Success', metadata: {} }
109+
const isOnDetail = switcher.isItOnDetail('FEATURE01'); // { result: true, reason: 'Success', metadata: {} }
110+
111+
// Remote (asynchronous) execution or hybrid (local/remote) execution
112+
const isOnBoolAsync = await switcher.isItOn('FEATURE01'); // Promise<boolean>
113+
const isOnBoolAsync = await switcher.isItOnBool('FEATURE01', true); // Promise<boolean>
114+
const isOnDetailAsync = await switcher.detail().isItOn('FEATURE01'); // Promise<SwitcherResult>
115+
const isOnDetailAsync = await switcher.isItOnDetail('FEATURE01', true); // Promise<SwitcherResult>
107116
```
108117

109118
2. **Strategy validation - preparing input**

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
"src/"
3232
],
3333
"devDependencies": {
34-
"@babel/eslint-parser": "^7.27.5",
34+
"@babel/eslint-parser": "^7.28.0",
3535
"@typescript-eslint/eslint-plugin": "^8.35.1",
3636
"@typescript-eslint/parser": "^8.35.1",
3737
"c8": "^10.1.3",
3838
"chai": "^5.2.0",
3939
"env-cmd": "^10.1.0",
40-
"eslint": "^9.30.0",
40+
"eslint": "^9.30.1",
4141
"mocha": "^11.7.1",
4242
"mocha-sonarqube-reporter": "^1.0.2",
4343
"sinon": "^21.0.0"

src/client.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,7 @@ export class Client {
140140
util.get(Client.#context.environment, DEFAULT_ENVIRONMENT)
141141
));
142142

143-
if (GlobalSnapshot.snapshot.data.domain.version == 0 &&
144-
(fetchRemote || !GlobalOptions.local)) {
143+
if (this.#isCheckSnapshotAvailable(fetchRemote)) {
145144
await Client.checkSnapshot();
146145
}
147146

@@ -152,6 +151,17 @@ export class Client {
152151
return GlobalSnapshot.snapshot?.data.domain.version || 0;
153152
}
154153

154+
/**
155+
* Checks if the snapshot is available to be checked.
156+
*
157+
* Snapshots with version 0 are required to be checked if either:
158+
* - fetchRemote is true, meaning it will fetch the latest snapshot from the API.
159+
* - GlobalOptions.local is false, meaning it will not use the local snapshot.
160+
*/
161+
static #isCheckSnapshotAvailable(fetchRemote) {
162+
return GlobalSnapshot.snapshot?.data.domain.version == 0 && (fetchRemote || !GlobalOptions.local);
163+
}
164+
155165
static watchSnapshot(callback = {}) {
156166
const { success = () => {}, reject = () => {} } = callback;
157167

src/lib/resolver.js

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,26 @@ import { SwitcherResult } from './result.js';
88
*
99
* @param {SnapshotData} data - The snapshot data containing domain and group information.
1010
* @param {SwitcherRequest} switcher - The switcher request to be evaluated.
11-
* @returns {Promise<SwitcherResult>} - The result of the switcher evaluation.
11+
* @returns {SwitcherResult} - The result of the switcher evaluation.
1212
*/
13-
async function resolveCriteria(data, switcher) {
13+
function resolveCriteria(data, switcher) {
1414
if (!data.domain.activated) {
1515
return SwitcherResult.disabled('Domain disabled');
1616
}
1717

1818
const { group } = data.domain;
19-
return await checkGroup(group, switcher);
19+
return checkGroup(group, switcher);
2020
}
2121

2222
/**
2323
* Checks if a switcher is valid within a specific group of the domain.
2424
*
2525
* @param {Group[]} groups - The list of groups to check against.
2626
* @param {SwitcherRequest} switcher - The switcher request to be evaluated.
27-
* @returns {Promise<SwitcherResult>} - The result of the switcher evaluation.
27+
* @returns {SwitcherResult} - The result of the switcher evaluation.
2828
* @throws {Error} - If the switcher key is not found in any group.
2929
*/
30-
async function checkGroup(groups, switcher) {
30+
function checkGroup(groups, switcher) {
3131
const key = util.get(switcher.key, '');
3232

3333
for (const group of groups) {
@@ -39,7 +39,7 @@ async function checkGroup(groups, switcher) {
3939
return SwitcherResult.disabled('Group disabled');
4040
}
4141

42-
return await checkConfig(configFound[0], switcher);
42+
return checkConfig(configFound[0], switcher);
4343
}
4444
}
4545

@@ -53,9 +53,9 @@ async function checkGroup(groups, switcher) {
5353
*
5454
* @param {Config} config Configuration to check
5555
* @param {SwitcherRequest} switcher - The switcher request to be evaluated.
56-
* @return {Promise<SwitcherResult>} - The result of the switcher evaluation.
56+
* @return {SwitcherResult} - The result of the switcher evaluation.
5757
*/
58-
async function checkConfig(config, switcher) {
58+
function checkConfig(config, switcher) {
5959
if (!config.activated) {
6060
return SwitcherResult.disabled('Config disabled');
6161
}
@@ -65,7 +65,7 @@ async function checkConfig(config, switcher) {
6565
}
6666

6767
if (config.strategies) {
68-
return await checkStrategy(config, switcher.input);
68+
return checkStrategy(config, switcher.input);
6969
}
7070

7171
return SwitcherResult.enabled();
@@ -76,9 +76,9 @@ async function checkConfig(config, switcher) {
7676
*
7777
* @param {Config} config - The configuration containing strategies.
7878
* @param {string[][]} [input] - The input data to be evaluated against the strategies.
79-
* @returns {Promise<SwitcherResult>} - The result of the strategy evaluation.
79+
* @returns {SwitcherResult} - The result of the strategy evaluation.
8080
*/
81-
async function checkStrategy(config, input) {
81+
function checkStrategy(config, input) {
8282
const { strategies } = config;
8383
const entry = getEntry(util.get(input, []));
8484

@@ -87,7 +87,7 @@ async function checkStrategy(config, input) {
8787
continue;
8888
}
8989

90-
const strategyResult = await checkStrategyConfig(strategyConfig, entry);
90+
const strategyResult = checkStrategyConfig(strategyConfig, entry);
9191
if (strategyResult) {
9292
return strategyResult;
9393
}
@@ -101,15 +101,15 @@ async function checkStrategy(config, input) {
101101
*
102102
* @param {Strategy} strategyConfig - The strategy configuration to be checked.
103103
* @param {Entry[]} [entry] - The entry data to be evaluated against the strategy.
104-
* @returns {Promise<SwitcherResult | undefined>} - The result of the strategy evaluation or undefined if valid.
104+
* @returns {SwitcherResult | undefined} - The result of the strategy evaluation or undefined if valid.
105105
*/
106-
async function checkStrategyConfig(strategyConfig, entry) {
106+
function checkStrategyConfig(strategyConfig, entry) {
107107
if (!entry?.length) {
108108
return SwitcherResult.disabled(`Strategy '${strategyConfig.strategy}' did not receive any input`);
109109
}
110110

111111
const strategyEntry = entry.filter((e) => e.strategy === strategyConfig.strategy);
112-
if (await isStrategyFulfilled(strategyEntry, strategyConfig)) {
112+
if (isStrategyFulfilled(strategyEntry, strategyConfig)) {
113113
return SwitcherResult.disabled(`Strategy '${strategyConfig.strategy}' does not agree`);
114114
}
115115

@@ -120,20 +120,19 @@ function hasRelayEnabled(config) {
120120
return config.relay?.activated;
121121
}
122122

123-
async function isStrategyFulfilled(strategyEntry, strategyConfig) {
124-
return strategyEntry.length == 0 ||
125-
!(await processOperation(strategyConfig, strategyEntry[0].input));
123+
function isStrategyFulfilled(strategyEntry, strategyConfig) {
124+
return strategyEntry.length == 0 || !processOperation(strategyConfig, strategyEntry[0].input);
126125
}
127126

128127
/**
129128
* Checks the criteria for a switcher request against the local snapshot.
130129
*
131130
* @param {Snapshot | undefined} snapshot - The snapshot containing the data to check against.
132131
* @param {SwitcherRequest} switcher - The switcher request to be evaluated.
133-
* @returns {Promise<SwitcherResult>} - The result of the switcher evaluation.
132+
* @returns {SwitcherResult} - The result of the switcher evaluation.
134133
* @throws {Error} - If the snapshot is not loaded.
135134
*/
136-
export default async function checkCriteriaLocal(snapshot, switcher) {
135+
export default function checkCriteriaLocal(snapshot, switcher) {
137136
if (!snapshot) {
138137
throw new Error('Snapshot not loaded. Try to use \'Client.loadSnapshot()\'');
139138
}

src/lib/snapshot.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const OperationsType = Object.freeze({
8686
HAS_ALL: 'HAS_ALL'
8787
});
8888

89-
const processOperation = async (strategyConfig, input) => {
89+
const processOperation = (strategyConfig, input) => {
9090
const { strategy, operation, values } = strategyConfig;
9191

9292
switch(strategy) {
@@ -207,16 +207,16 @@ function processDATE(operation, input, values) {
207207
}
208208
}
209209

210-
async function processREGEX(operation, input, values) {
210+
function processREGEX(operation, input, values) {
211211
switch(operation) {
212212
case OperationsType.EXIST:
213-
return await TimedMatch.tryMatch(values, input);
213+
return TimedMatch.tryMatch(values, input);
214214
case OperationsType.NOT_EXIST:
215-
return !(await processREGEX(OperationsType.EXIST, input, values));
215+
return !processREGEX(OperationsType.EXIST, input, values);
216216
case OperationsType.EQUAL:
217-
return await TimedMatch.tryMatch([`\\b${values[0]}\\b`], input);
217+
return TimedMatch.tryMatch([`\\b${values[0]}\\b`], input);
218218
case OperationsType.NOT_EQUAL:
219-
return !(await TimedMatch.tryMatch([`\\b${values[0]}\\b`], input));
219+
return !TimedMatch.tryMatch([`\\b${values[0]}\\b`], input);
220220
}
221221
}
222222

0 commit comments

Comments
 (0)