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
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
***

<div align="center">
<b>Switcher Client SDK</b><br>
<b>Switcher Client JS SDK</b><br>
A JavaScript SDK for Switcher API
</div>

Expand Down Expand Up @@ -81,10 +81,10 @@ Client.buildContext({
});

// 2. Get a switcher instance
const switcher = Client.getSwitcher();
const switcher = Client.getSwitcher('FEATURE01');

// 3. Check if a feature is enabled
const isFeatureEnabled = await switcher.isItOn('FEATURE01');
const isFeatureEnabled = await switcher.isItOn();
console.log('Feature enabled:', isFeatureEnabled);
```

Expand Down Expand Up @@ -177,19 +177,22 @@ Client.buildContext({
Multiple ways to check if a feature is enabled:

```js
// Non-persisted switcher instance
const switcher = Client.getSwitcher();
// Persisted switcher instance
const switcher = Client.getSwitcher('FEATURE01');

// 🚀 Synchronous (local mode only)
const isEnabled = switcher.isItOn('FEATURE01'); // Returns: boolean
const isEnabledBool = switcher.isItOnBool('FEATURE01'); // Returns: boolean
const detailResult = switcher.detail().isItOn('FEATURE01'); // Returns: { result, reason, metadata }
const detailDirect = switcher.isItOnDetail('FEATURE01'); // Returns: { result, reason, metadata }
const isEnabled = switcher.isItOn(); // Returns: boolean
const isEnabledBool = switcher.isItOnBool(); // Returns: boolean
const detailResult = switcher.detail().isItOn(); // Returns: { result, reason, metadata }
const detailDirect = switcher.isItOnDetail(); // Returns: { result, reason, metadata }

// 🌐 Asynchronous (remote/hybrid mode)
const isEnabledAsync = await switcher.isItOn('FEATURE01'); // Returns: Promise<boolean>
const isEnabledBoolAsync = await switcher.isItOnBool('FEATURE01', true); // Returns: Promise<boolean>
const detailResultAsync = await switcher.detail().isItOn('FEATURE01'); // Returns: Promise<SwitcherResult>
const detailDirectAsync = await switcher.isItOnDetail('FEATURE01', true); // Returns: Promise<SwitcherResult>
const isEnabledAsync = await switcher.isItOn(); // Returns: Promise<boolean>
const isEnabledBoolAsync = await switcher.isItOnBool(true); // Returns: Promise<boolean>
const detailResultAsync = await switcher.detail().isItOn(); // Returns: Promise<SwitcherResult>
const detailDirectAsync = await switcher.isItOnDetail(true); // Returns: Promise<SwitcherResult>
```

### Strategy Validation
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
],
"devDependencies": {
"@babel/eslint-parser": "^7.28.5",
"@typescript-eslint/eslint-plugin": "^8.50.1",
"@typescript-eslint/parser": "^8.50.1",
"@typescript-eslint/eslint-plugin": "^8.52.0",
"@typescript-eslint/parser": "^8.52.0",
"c8": "^10.1.3",
"chai": "^6.2.2",
"env-cmd": "^11.0.0",
Expand Down
4 changes: 3 additions & 1 deletion src/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export class Client {
static buildContext(context: SwitcherContext, options?: SwitcherOptions): void;

/**
* Creates a new instance of Switcher
* Creates a new instance of Switcher.
*
* Provide a key if you want to persist the instance.
*/
static getSwitcher(key?: string): Switcher;

Expand Down
16 changes: 15 additions & 1 deletion src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { SnapshotWatcher } from './lib/snapshotWatcher.js';

export class Client {
static #snapshotWatcher = new SnapshotWatcher();
static #switchers = new Map();
static #testEnabled;
static #context;

Expand Down Expand Up @@ -103,8 +104,21 @@ export class Client {
}

static getSwitcher(key) {
return new Switcher(util.get(key, ''))
const keyValue = util.get(key, '');
const persistedSwitcher = this.#switchers.get(keyValue);

if (persistedSwitcher) {
return persistedSwitcher;
}

const switcher = new Switcher(keyValue)
.restrictRelay(GlobalOptions.restrictRelay);

if (keyValue) {
this.#switchers.set(keyValue, switcher);
}

return switcher;
}

static async checkSnapshot() {
Expand Down
54 changes: 32 additions & 22 deletions src/switcher.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,65 +16,75 @@ export type SwitcherExecutionResult = Promise<boolean | SwitcherResult> | boolea
* const { result, reason, metadata } = switcher.isItOnDetail();
*
* // Force asynchronous execution
* const isOnAsync = await switcher.isItOnBool('MY_SWITCHER', true);
* const detailAsync = await switcher.isItOnDetail('MY_SWITCHER', true);
* const isOnAsync = await switcher.isItOnBool(true);
* const detailAsync = await switcher.isItOnDetail(true);
* ```
*/
export class Switcher {

/**
* Execute criteria with boolean result (synchronous version)
* Execute criteria with boolean result (synchronous, uses persisted key)
*
* @param key - switcher key
* @param forceAsync - when true, forces async execution
* @returns boolean value
* @returns boolean result
*/
isItOnBool(key: string, forceAsync?: false): boolean;
isItOnBool(): boolean;

/**
* Execute criteria with boolean result (asynchronous version)
* Execute criteria with boolean result (synchronous)
*
* @param key - switcher key
* @returns boolean result
*/
isItOnBool(key: string): boolean;

/**
* Execute criteria with boolean result (asynchronous, uses persisted key)
*
* @param forceAsync - when true, forces async execution
* @returns Promise<boolean> value
* @returns Promise<boolean> result
*/
isItOnBool(key: string, forceAsync?: true): Promise<boolean>;
isItOnBool(forceAsync: true): Promise<boolean>;

/**
* Execute criteria with boolean result
* Execute criteria with boolean result (asynchronous)
*
* @param key - switcher key
* @param forceAsync - when true, forces async execution
* @returns boolean value or Promise<boolean> based on execution mode
* @returns Promise<boolean> result
*/
isItOnBool(key: string, forceAsync?: boolean): Promise<boolean> | boolean;
isItOnBool(key: string, forceAsync: true): Promise<boolean>;

/**
* Execute criteria with detail information (synchronous version)
* Execute criteria with detail information (synchronous)
*
* @param key - switcher key
* @param forceAsync - when true, forces async execution
* @returns SwitcherResult object
*/
isItOnDetail(key: string, forceAsync?: false): SwitcherResult;
isItOnDetail(): SwitcherResult;

/**
* Execute criteria with detail information (asynchronous version)
* Execute criteria with detail information (synchronous)
*
* @param key - switcher key
* @returns SwitcherResult object
*/
isItOnDetail(key: string): SwitcherResult;

/**
* Execute criteria with detail information (asynchronous, uses persisted key)
*
* @param forceAsync - when true, forces async execution
* @returns Promise<SwitcherResult> object
*/
isItOnDetail(key: string, forceAsync?: true): Promise<SwitcherResult>;
isItOnDetail(forceAsync: true): Promise<SwitcherResult>;

/**
* Execute criteria with detail information
* Execute criteria with detail information (asynchronous)
*
* @param key - switcher key
* @param forceAsync - when true, forces async execution
* @returns SwitcherResult or Promise<SwitcherResult> based on execution mode
* @returns Promise<SwitcherResult> object
*/
isItOnDetail(key: string, forceAsync?: boolean): Promise<SwitcherResult> | SwitcherResult;
isItOnDetail(key: string, forceAsync: true): Promise<SwitcherResult>;

/**
* Execute criteria
Expand Down
28 changes: 20 additions & 8 deletions src/switcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,36 @@ export class Switcher extends SwitcherRequest {
}
}

isItOnBool(key, forceAsync = false) {
isItOnBool(arg1, arg2) {
this.detail(false);

if (forceAsync) {
return Promise.resolve(this.isItOn(key));
// Handle case where first argument is forceAsync boolean
if (typeof arg1 === 'boolean') {
arg2 = arg1;
arg1 = undefined;
}

return this.isItOn(key);
if (arg2) {
return Promise.resolve(this.isItOn(arg1));
}

return this.isItOn(arg1);
}

isItOnDetail(key, forceAsync = false) {
isItOnDetail(arg1, arg2) {
this.detail(true);

if (forceAsync) {
return Promise.resolve(this.isItOn(key));
// Handle case where first argument is forceAsync boolean
if (typeof arg1 === 'boolean') {
arg2 = arg1;
arg1 = undefined;
}

if (arg2) {
return Promise.resolve(this.isItOn(arg1));
}

return this.isItOn(key);
return this.isItOn(arg1);
}

isItOn(key) {
Expand Down
40 changes: 21 additions & 19 deletions tests/playground/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async function setupSwitcher(local) {
}

/**
* This code snippet is a minimal example of how to configure and use Switcher4Deno locally.
* This code snippet is a minimal example of how to configure and use switcher-client-js locally.
* No remote API account is required.
*
* Snapshot is loaded from file at tests/playground/snapshot/local.json
Expand Down Expand Up @@ -57,11 +57,11 @@ const _testSimpleAPICall = async (local) => {
.then(() => console.log('Switcher checked'))
.catch(error => console.log(error));

const switcher = Client.getSwitcher();
const switcher = Client.getSwitcher(SWITCHER_KEY);

setInterval(async () => {
const time = Date.now();
const result = await switcher.detail().isItOn(SWITCHER_KEY);
const result = await switcher.detail().isItOn();
console.log(`- ${Date.now() - time} ms - ${JSON.stringify(result)}`);
}, 1000);
};
Expand All @@ -73,14 +73,15 @@ const _testThrottledAPICall = async () => {
await Client.checkSwitchers([SWITCHER_KEY]);
Client.subscribeNotifyError((error) => console.log(error));

const switcher = Client.getSwitcher();
switcher.throttle(1000);


setInterval(async () => {
const time = Date.now();

const switcher = Client.getSwitcher(SWITCHER_KEY);
switcher.throttle(5000);
const result = await switcher
.detail()
.isItOn(SWITCHER_KEY);
.isItOn();

console.log(`- ${Date.now() - time} ms - ${JSON.stringify(result)}`);
}, 1000);
Expand All @@ -94,31 +95,32 @@ const _testSnapshotUpdate = async () => {
console.log('checkSnapshot:', await Client.checkSnapshot());
};

// Requires remote API
const _testAsyncCall = async () => {
setupSwitcher(true);
const switcher = Client.getSwitcher();
setupSwitcher(false);
const switcher = Client.getSwitcher(SWITCHER_KEY);

console.log('Sync:', await switcher.isItOn(SWITCHER_KEY));
console.log('Sync:', await switcher.isItOn());

switcher.isItOn(SWITCHER_KEY)
switcher.isItOn()
.then(res => console.log('Promise result:', res))
.catch(error => console.log(error));
};

// Does not require remote API
const _testBypasser = async () => {
setupSwitcher(true);
const switcher = Client.getSwitcher();
const switcher = Client.getSwitcher(SWITCHER_KEY);

let result = await switcher.isItOn(SWITCHER_KEY);
let result = await switcher.isItOn();
console.log(result);

Client.assume(SWITCHER_KEY).true();
result = await switcher.isItOn(SWITCHER_KEY);
result = await switcher.isItOn();
console.log(result);

Client.forget(SWITCHER_KEY);
result = await switcher.isItOn(SWITCHER_KEY);
result = await switcher.isItOn();
console.log(result);

Client.unloadSnapshot();
Expand Down Expand Up @@ -158,13 +160,13 @@ const _testWatchSnapshotContextOptions = async () => {

await Client.loadSnapshot();

const switcher = Client.getSwitcher();
const switcher = Client.getSwitcher(SWITCHER_KEY);

setInterval(async () => {
const time = Date.now();
const result = await switcher
.detail()
.isItOn(SWITCHER_KEY);
.isItOn();

console.log(`- ${Date.now() - time} ms - ${JSON.stringify(result)}`);
}, 1000);
Expand All @@ -176,7 +178,7 @@ const _testSnapshotAutoUpdate = async () => {
{ local: true, logger: true });

await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: true });
const switcher = Client.getSwitcher();
const switcher = Client.getSwitcher(SWITCHER_KEY);

Client.scheduleSnapshotAutoUpdate(1, {
success: (updated) => console.log('In-memory snapshot updated', updated),
Expand All @@ -185,7 +187,7 @@ const _testSnapshotAutoUpdate = async () => {

setInterval(async () => {
const time = Date.now();
await switcher.checkValue('user_1').isItOn(SWITCHER_KEY);
await switcher.checkValue('user_1').isItOn();
console.clear();
console.log(JSON.stringify(Client.getLogger(SWITCHER_KEY)), `executed in ${Date.now() - time}ms`);
}, 2000);
Expand Down
13 changes: 7 additions & 6 deletions tests/switcher-client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ describe('E2E test - Client local #1:', function () {
await switcher
.checkValue('Japan')
.checkNetwork('10.0.0.3')
.prepare();
.prepare('FF2FOR2020');

assert.isTrue(await switcher.isItOn('FF2FOR2020') === true);
assert.isTrue(switcher.isItOnBool('FF2FOR2020') === true);
assert.isTrue(await switcher.isItOnBool('FF2FOR2020', true) === true);
assert.isTrue(switcher.isItOnDetail('FF2FOR2020').result === true);
assert.isTrue((await switcher.isItOnDetail('FF2FOR2020', true)).result === true);
assert.isTrue(switcher.isItOn() === true);
assert.isTrue(await switcher.isItOn() === true);
assert.isTrue(switcher.isItOnBool() === true);
assert.isTrue(await switcher.isItOnBool(true) === true);
assert.isTrue(switcher.isItOnDetail().result === true);
assert.isTrue((await switcher.isItOnDetail(true)).result === true);
});

it('should get execution from logger', async function () {
Expand Down
Loading