Skip to content

Commit

Permalink
Standardized callback for loadSnapshot and watchSnapshot (#189)
Browse files Browse the repository at this point in the history
* Standardized callback for loadSnapshot and watchSnapshot

* chore: fixed test async when success

* Fixed scheduleSnapshotAutoUpdate callback
  • Loading branch information
petruki authored May 12, 2024
1 parent 7405a75 commit cea8ab8
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 76 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ https://github.com/switcherapi/switcher-api
The context properties stores all information regarding connectivity.

```js
import { Switcher } from 'switcher-client';
import { Client } from 'switcher-client';

const apiKey = '[API_KEY]';
const environment = 'default';
Expand Down Expand Up @@ -200,9 +200,10 @@ Client.loadSnapshot();
Activate and monitor snapshot changes using this feature. Optionally, you can implement any action based on the callback response.

```js
Client.watchSnapshot(
() => console.log('In-memory snapshot updated'),
(err) => console.log(err));
Client.watchSnapshot({
success: () => console.log('In-memory snapshot updated'),
reject: (err) => console.log(err)
});
```

## Snapshot version check
Expand All @@ -217,5 +218,8 @@ You can also schedule a snapshot update using the method below.<br>
It allows you to run the Client SDK in local mode (zero latency) and still have the snapshot updated automatically.

```js
Client.scheduleSnapshotAutoUpdate(1 * 60 * 60 * 24); // 24 hours
Client.scheduleSnapshotAutoUpdate(3000, {
success: (updated) => console.log('Snapshot updated', updated),
reject: (err) => console.log(err)
});
```
29 changes: 22 additions & 7 deletions src/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Switcher } from './switcher';
*
* 1. Use Client.buildContext() to define the arguments to connect to the API.
* 2. Use Client.getSwitcher() to create a new instance of Switcher.
* 3. Use the instance created to call isItOn to query the API.
* 3. Use the instance created to call isItOn to execute criteria evaluation.
*/
export class Client {

Expand All @@ -22,11 +22,8 @@ export class Client {

/**
* Read snapshot and load it into memory
*
* @param watchSnapshot when true, it will watch for snapshot file modifications
* @param fetchRemote when true, it will initialize the snapshot from the API
*/
static loadSnapshot(watchSnapshot?: boolean, fetchRemote?: boolean): Promise<void>;
static loadSnapshot(options?: LoadSnapshotOptions): Promise<void>;

/**
* Verifies if the current snapshot file is updated.
Expand All @@ -42,8 +39,12 @@ export class Client {
* building context
*
* @param interval in ms
* @param success returns true if snapshot has been updated
*/
static scheduleSnapshotAutoUpdate(interval?: number, callback?: (updated: boolean, err: Error) => void): void;
static scheduleSnapshotAutoUpdate(interval?: number, callback?: {
success?: (updated: boolean) => void;
reject?: (err: Error) => void;
}): void;

/**
* Terminates Snapshot Auto Update
Expand All @@ -64,7 +65,10 @@ export class Client {
* @param success when snapshot has successfully updated
* @param error when any error has thrown when attempting to load snapshot
*/
static watchSnapshot(success?: () => void, error?: (err: any) => void): void;
static watchSnapshot(callback: {
success?: () => void | Promise<void>;
reject?: (err: Error) => void;
}): void;

/**
* Terminate watching snapshot files
Expand Down Expand Up @@ -160,6 +164,17 @@ export type SwitcherOptions = {
certPath?: string;
}

/**
* LoadSnapshotOptions defines the options to load a snapshot.
*
* @param watchSnapshot when true, it will watch for snapshot file modifications
* @param fetchRemote when true, it will initialize the snapshot from the API
*/
export type LoadSnapshotOptions = {
watchSnapshot?: boolean;
fetchRemote?: boolean;
}

declare class Key {

constructor(key: string);
Expand Down
25 changes: 17 additions & 8 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,27 +119,29 @@ export class Client {
return false;
}

static async loadSnapshot(watchSnapshot = false, fetchRemote = false) {
static async loadSnapshot(options = { fetchRemote: false, watchSnapshot: false }) {
Client.#snapshot = loadDomain(
util.get(Client.#options.snapshotLocation, ''),
util.get(Client.#context.environment, DEFAULT_ENVIRONMENT)
);

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

if (watchSnapshot) {
if (options.watchSnapshot) {
Client.watchSnapshot();
}

return Client.#snapshot?.data.domain.version || 0;
}

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

if (Client.testEnabled || !Client.#options.snapshotLocation?.length) {
return error(new Error('Watch Snapshot cannot be used in test mode or without a snapshot location'));
return reject(new Error('Watch Snapshot cannot be used in test mode or without a snapshot location'));
}

const snapshotFile = `${Client.#options.snapshotLocation}/${Client.#context.environment}.json`;
Expand All @@ -151,7 +153,7 @@ export class Client {
success();
}
} catch (e) {
error(e);
reject(e);
} finally {
lastUpdate = listener.ctime;
}
Expand All @@ -168,13 +170,20 @@ export class Client {
unwatchFile(snapshotFile);
}

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

if (interval) {
Client.#options.snapshotAutoUpdateInterval = interval;
}

if (Client.#options.snapshotAutoUpdateInterval && Client.#options.snapshotAutoUpdateInterval > 0) {
SnapshotAutoUpdater.schedule(Client.#options.snapshotAutoUpdateInterval, this.checkSnapshot, callback);
SnapshotAutoUpdater.schedule(
Client.#options.snapshotAutoUpdateInterval,
this.checkSnapshot,
success,
reject
);
}
}

Expand Down
35 changes: 20 additions & 15 deletions src/lib/bypasser/key.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,62 @@
/**
* Type definition for Switcher Keys which are used to mock results
* Key record used to store key response when bypassing criteria execution
*/
export default class Key {

#key;
#result;
#reason;
#metadata;

constructor(key) {
this._key = key;
this._result = undefined;
this._reason = undefined;
this._metadata = undefined;
this.#key = key;
this.#result = undefined;
this.#reason = undefined;
this.#metadata = undefined;
}

/**
* Force result to true
*/
true() {
this._result = true;
this._reason = 'Forced to true';
this.#result = true;
this.#reason = 'Forced to true';
return this;
}

/**
* Force result to false
*/
false() {
this._result = false;
this._reason = 'Forced to false';
this.#result = false;
this.#reason = 'Forced to false';
return this;
}

/**
* Define metadata for the response
*/
withMetadata(metadata) {
this._metadata = metadata;
this.#metadata = metadata;
return this;
}

/**
* Return selected switcher name
*/
getKey() {
return this._key;
return this.#key;
}

/**
* Return key response
*/
getResponse() {
return {
key: this._key,
result: this._result,
reason: this._reason,
metadata: this._metadata
key: this.#key,
result: this.#result,
reason: this.#reason,
metadata: this.#metadata
};
}
}
12 changes: 4 additions & 8 deletions src/lib/utils/snapshotAutoUpdater.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
export default class SnapshotAutoUpdater {
static _worker = undefined;

static schedule(interval, checkSnapshot, callback) {
static schedule(interval, checkSnapshot, success, reject) {
if (this._worker) {
this.terminate();
}

this._worker = setInterval(async () => {
try {
const updated = await checkSnapshot();
if (callback) {
callback(updated);
}
success(updated);
} catch (err) {
if (callback) {
this.terminate();
callback(null, err);
}
this.terminate();
reject(err);
}
}, interval * 1000);
}
Expand Down
22 changes: 12 additions & 10 deletions test/playground/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const snapshotLocation = './test/playground/snapshot/';
*/
async function setupSwitcher(local) {
Client.buildContext({ url, apiKey, domain, component, environment }, { local, logger: true });
await Client.loadSnapshot(false, local)
await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: local })
.then(version => console.log('Snapshot loaded - version:', version))
.catch(() => console.log('Failed to load Snapshot'));
}
Expand Down Expand Up @@ -131,28 +131,30 @@ const _testBypasser = async () => {
// Requires remote API
const _testWatchSnapshot = async () => {
Client.buildContext({ url, apiKey, domain, component, environment }, { snapshotLocation, local: true, logger: true });
await Client.loadSnapshot(false, true)
await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: true })
.then(() => console.log('Snapshot loaded'))
.catch(() => console.log('Failed to load Snapshot'));

const switcher = Client.getSwitcher();

Client.watchSnapshot(
async () => console.log('In-memory snapshot updated', await switcher.isItOn(SWITCHER_KEY)),
(err) => console.log(err));

Client.watchSnapshot({
success: async () => console.log('In-memory snapshot updated', await switcher.isItOn(SWITCHER_KEY)),
reject: (err) => console.log(err)
});
};

// Requires remote API
const _testSnapshotAutoUpdate = async () => {
Client.buildContext({ url, apiKey, domain, component, environment },
{ local: true, logger: true });

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

Client.scheduleSnapshotAutoUpdate(3,
(updated) => console.log('In-memory snapshot updated', updated),
(err) => console.log(err));
Client.scheduleSnapshotAutoUpdate(1, {
success: (updated) => console.log('In-memory snapshot updated', updated),
reject: (err) => console.log(err)
});

setInterval(async () => {
const time = Date.now();
Expand Down
22 changes: 9 additions & 13 deletions test/switcher-snapshot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('E2E test - Switcher local - Snapshot:', function () {
regexSafe: false
});

await Client.loadSnapshot(true);
await Client.loadSnapshot({ watchSnapshot: true });
assert.isTrue(await Client.checkSnapshot());

//restore state to avoid process leakage
Expand All @@ -79,7 +79,7 @@ describe('E2E test - Switcher local - Snapshot:', function () {
regexSafe: false
});

await Client.loadSnapshot(true);
await Client.loadSnapshot({ watchSnapshot: true });
assert.isTrue(await Client.checkSnapshot());
assert.isTrue(existsSync(`generated-snapshots/${environment}.json`));

Expand All @@ -102,7 +102,7 @@ describe('E2E test - Switcher local - Snapshot:', function () {
regexSafe: false
});

await Client.loadSnapshot(true, true);
await Client.loadSnapshot({ watchSnapshot: true, fetchRemote: true });
assert.isTrue(existsSync(`generated-snapshots/${environment}.json`));

//restore state to avoid process leakage
Expand Down Expand Up @@ -328,13 +328,11 @@ describe('E2E test - Snapshot AutoUpdater:', function () {
});

let snapshotUpdated = false;
Client.scheduleSnapshotAutoUpdate(1, (updated) => {
if (updated != undefined) {
snapshotUpdated = updated;
}
Client.scheduleSnapshotAutoUpdate(1, {
success: (updated) => snapshotUpdated = updated
});

await Client.loadSnapshot(false, true);
await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: true });

const switcher = Client.getSwitcher();
assert.isFalse(await switcher.isItOn('FF2FOR2030'));
Expand All @@ -361,13 +359,11 @@ describe('E2E test - Snapshot AutoUpdater:', function () {
});

let error;
Client.scheduleSnapshotAutoUpdate(1, (updated, err) => {
if (err != undefined) {
error = err;
}
Client.scheduleSnapshotAutoUpdate(1, {
reject: (err) => error = err
});

await Client.loadSnapshot(false, true);
await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: true });

//next call will fail
givenError(fetchStub, 3, { errno: 'ECONNREFUSED' });
Expand Down
Loading

0 comments on commit cea8ab8

Please sign in to comment.