Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,23 @@ const switcher = Client.getSwitcher();
There are a few different ways to call the API.
Here are some examples:

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

```js
const switcher = Client.getSwitcher();
await switcher.isItOn('FEATURE01');
// or
const { result, reason, metadata } = await switcher.detail().isItOn('FEATURE01');

// Local (synchronous) execution
const isOnBool = switcher.isItOn('FEATURE01'); // true or false
const isOnBool = switcher.isItOnBool('FEATURE01'); // true or false
const isOnDetail = switcher.detail().isItOn('FEATURE01'); // { result: true, reason: 'Success', metadata: {} }
const isOnDetail = switcher.isItOnDetail('FEATURE01'); // { result: true, reason: 'Success', metadata: {} }

// Remote (asynchronous) execution or hybrid (local/remote) execution
const isOnBoolAsync = await switcher.isItOn('FEATURE01'); // Promise<boolean>
const isOnBoolAsync = await switcher.isItOnBool('FEATURE01', true); // Promise<boolean>
const isOnDetailAsync = await switcher.detail().isItOn('FEATURE01'); // Promise<SwitcherResult>
const isOnDetailAsync = await switcher.isItOnDetail('FEATURE01', true); // Promise<SwitcherResult>
```

2. **Strategy validation - preparing input**
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
"src/"
],
"devDependencies": {
"@babel/eslint-parser": "^7.27.5",
"@babel/eslint-parser": "^7.28.0",
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.35.1",
"c8": "^10.1.3",
"chai": "^5.2.0",
"env-cmd": "^10.1.0",
"eslint": "^9.30.0",
"eslint": "^9.30.1",
"mocha": "^11.7.1",
"mocha-sonarqube-reporter": "^1.0.2",
"sinon": "^21.0.0"
Expand Down
14 changes: 12 additions & 2 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,7 @@ export class Client {
util.get(Client.#context.environment, DEFAULT_ENVIRONMENT)
));

if (GlobalSnapshot.snapshot.data.domain.version == 0 &&
(fetchRemote || !GlobalOptions.local)) {
if (this.#isCheckSnapshotAvailable(fetchRemote)) {
await Client.checkSnapshot();
}

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

/**
* Checks if the snapshot is available to be checked.
*
* Snapshots with version 0 are required to be checked if either:
* - fetchRemote is true, meaning it will fetch the latest snapshot from the API.
* - GlobalOptions.local is false, meaning it will not use the local snapshot.
*/
static #isCheckSnapshotAvailable(fetchRemote) {
return GlobalSnapshot.snapshot?.data.domain.version == 0 && (fetchRemote || !GlobalOptions.local);
}

static watchSnapshot(callback = {}) {
const { success = () => {}, reject = () => {} } = callback;

Expand Down
39 changes: 19 additions & 20 deletions src/lib/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ import { SwitcherResult } from './result.js';
*
* @param {SnapshotData} data - The snapshot data containing domain and group information.
* @param {SwitcherRequest} switcher - The switcher request to be evaluated.
* @returns {Promise<SwitcherResult>} - The result of the switcher evaluation.
* @returns {SwitcherResult} - The result of the switcher evaluation.
*/
async function resolveCriteria(data, switcher) {
function resolveCriteria(data, switcher) {
if (!data.domain.activated) {
return SwitcherResult.disabled('Domain disabled');
}

const { group } = data.domain;
return await checkGroup(group, switcher);
return checkGroup(group, switcher);
}

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

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

return await checkConfig(configFound[0], switcher);
return checkConfig(configFound[0], switcher);
}
}

Expand All @@ -53,9 +53,9 @@ async function checkGroup(groups, switcher) {
*
* @param {Config} config Configuration to check
* @param {SwitcherRequest} switcher - The switcher request to be evaluated.
* @return {Promise<SwitcherResult>} - The result of the switcher evaluation.
* @return {SwitcherResult} - The result of the switcher evaluation.
*/
async function checkConfig(config, switcher) {
function checkConfig(config, switcher) {
if (!config.activated) {
return SwitcherResult.disabled('Config disabled');
}
Expand All @@ -65,7 +65,7 @@ async function checkConfig(config, switcher) {
}

if (config.strategies) {
return await checkStrategy(config, switcher.input);
return checkStrategy(config, switcher.input);
}

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

Expand All @@ -87,7 +87,7 @@ async function checkStrategy(config, input) {
continue;
}

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

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

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

async function isStrategyFulfilled(strategyEntry, strategyConfig) {
return strategyEntry.length == 0 ||
!(await processOperation(strategyConfig, strategyEntry[0].input));
function isStrategyFulfilled(strategyEntry, strategyConfig) {
return strategyEntry.length == 0 || !processOperation(strategyConfig, strategyEntry[0].input);
}

/**
* Checks the criteria for a switcher request against the local snapshot.
*
* @param {Snapshot | undefined} snapshot - The snapshot containing the data to check against.
* @param {SwitcherRequest} switcher - The switcher request to be evaluated.
* @returns {Promise<SwitcherResult>} - The result of the switcher evaluation.
* @returns {SwitcherResult} - The result of the switcher evaluation.
* @throws {Error} - If the snapshot is not loaded.
*/
export default async function checkCriteriaLocal(snapshot, switcher) {
export default function checkCriteriaLocal(snapshot, switcher) {
if (!snapshot) {
throw new Error('Snapshot not loaded. Try to use \'Client.loadSnapshot()\'');
}
Expand Down
12 changes: 6 additions & 6 deletions src/lib/snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const OperationsType = Object.freeze({
HAS_ALL: 'HAS_ALL'
});

const processOperation = async (strategyConfig, input) => {
const processOperation = (strategyConfig, input) => {
const { strategy, operation, values } = strategyConfig;

switch(strategy) {
Expand Down Expand Up @@ -207,16 +207,16 @@ function processDATE(operation, input, values) {
}
}

async function processREGEX(operation, input, values) {
function processREGEX(operation, input, values) {
switch(operation) {
case OperationsType.EXIST:
return await TimedMatch.tryMatch(values, input);
return TimedMatch.tryMatch(values, input);
case OperationsType.NOT_EXIST:
return !(await processREGEX(OperationsType.EXIST, input, values));
return !processREGEX(OperationsType.EXIST, input, values);
case OperationsType.EQUAL:
return await TimedMatch.tryMatch([`\\b${values[0]}\\b`], input);
return TimedMatch.tryMatch([`\\b${values[0]}\\b`], input);
case OperationsType.NOT_EQUAL:
return !(await TimedMatch.tryMatch([`\\b${values[0]}\\b`], input));
return !TimedMatch.tryMatch([`\\b${values[0]}\\b`], input);
}
}

Expand Down
Loading