Skip to content

Commit

Permalink
Default uninitialized EppoClient instance (#48)
Browse files Browse the repository at this point in the history
* initialize instance off the bat; set request configuration later

* make initialize flag public

* correct docs

* more doc fixes

* use newly published common sdk
  • Loading branch information
aarsilv authored Feb 5, 2024
1 parent f742feb commit e842de5
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 19 deletions.
11 changes: 11 additions & 0 deletions docs/js-client-sdk.eppojsclient.initialized.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@eppo/js-client-sdk](./js-client-sdk.md) &gt; [EppoJSClient](./js-client-sdk.eppojsclient.md) &gt; [initialized](./js-client-sdk.eppojsclient.initialized.md)

## EppoJSClient.initialized property

**Signature:**

```typescript
static initialized: boolean;
```
1 change: 1 addition & 0 deletions docs/js-client-sdk.eppojsclient.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export declare class EppoJSClient extends EppoClient
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [initialized](./js-client-sdk.eppojsclient.initialized.md) | <code>static</code> | boolean | |
| [instance](./js-client-sdk.eppojsclient.instance.md) | <code>static</code> | [EppoJSClient](./js-client-sdk.eppojsclient.md) | |
## Methods
Expand Down
2 changes: 1 addition & 1 deletion docs/js-client-sdk.iclientconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ export interface IClientConfig
| [pollAfterFailedInitialization?](./js-client-sdk.iclientconfig.pollafterfailedinitialization.md) | | boolean | _(Optional)_ Poll for new configurations even if the initial configuration request failed. (default: false) |
| [pollAfterSuccessfulInitialization?](./js-client-sdk.iclientconfig.pollaftersuccessfulinitialization.md) | | boolean | _(Optional)_ Poll for new configurations (every 30 seconds) after successfully requesting the initial configuration. (default: false) |
| [requestTimeoutMs?](./js-client-sdk.iclientconfig.requesttimeoutms.md) | | number | _(Optional)_ \* Timeout in milliseconds for the HTTPS request for the experiment configuration. (Default: 5000) |
| [throwOnFailedInitialization?](./js-client-sdk.iclientconfig.throwonfailedinitialization.md) | | boolean | _(Optional)_ Throw on error if unable to fetch an initial configuration during initialization. (default: true) |
| [throwOnFailedInitialization?](./js-client-sdk.iclientconfig.throwonfailedinitialization.md) | | boolean | _(Optional)_ Throw an error if unable to fetch an initial configuration during initialization. (default: true) |

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## IClientConfig.throwOnFailedInitialization property

Throw on error if unable to fetch an initial configuration during initialization. (default: true)
Throw an error if unable to fetch an initial configuration during initialization. (default: true)

**Signature:**

Expand Down
2 changes: 2 additions & 0 deletions js-client-sdk.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export class EppoJSClient extends EppoClient {
// (undocumented)
getStringAssignment(subjectKey: string, flagKey: string, subjectAttributes?: Record<string, any>, assignmentHooks?: IAssignmentHooks): string | null;
// (undocumented)
static initialized: boolean;
// (undocumented)
static instance: EppoJSClient;
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"xhr-mock": "^2.5.1"
},
"dependencies": {
"@eppo/js-client-sdk-common": "2.1.0",
"@eppo/js-client-sdk-common": "2.1.1",
"md5": "^2.3.0"
}
}
39 changes: 37 additions & 2 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { HttpClient } from '@eppo/js-client-sdk-common';
import { POLL_INTERVAL_MS, POLL_JITTER_PCT } from '@eppo/js-client-sdk-common/dist/constants';
import { IExperimentConfiguration } from '@eppo/js-client-sdk-common/dist/dto/experiment-configuration-dto';
import { EppoValue } from '@eppo/js-client-sdk-common/dist/eppo_value';
import * as md5 from 'md5';
import * as td from 'testdouble';
Expand All @@ -24,6 +25,7 @@ import { IAssignmentLogger, IEppoClient, getInstance, init } from './index';
describe('EppoJSClient E2E test', () => {
let globalClient: IEppoClient;
let mockLogger: IAssignmentLogger;
let returnRac = readMockRacResponse; // function so it can be overridden per-test

const apiKey = 'dummy';
const baseUrl = 'http://127.0.0.1:4000';
Expand Down Expand Up @@ -81,7 +83,7 @@ describe('EppoJSClient E2E test', () => {
beforeAll(async () => {
mock.setup();
mock.get(/randomized_assignment\/v3\/config*/, (_req, res) => {
const rac = readMockRacResponse();
const rac = returnRac();
return res.status(200).body(JSON.stringify(rac));
});
mockLogger = td.object<IAssignmentLogger>();
Expand All @@ -93,6 +95,7 @@ describe('EppoJSClient E2E test', () => {
});

afterEach(() => {
returnRac = readMockRacResponse;
globalClient.setLogger(mockLogger);
td.reset();
});
Expand Down Expand Up @@ -379,7 +382,7 @@ describe('EppoJSClient E2E test', () => {
flags: {
[hashedFlagKey]: mockExperimentConfig,
},
};
} as unknown as Record<string, IExperimentConfiguration>;

beforeAll(() => {
jest.useFakeTimers({
Expand Down Expand Up @@ -545,5 +548,37 @@ describe('EppoJSClient E2E test', () => {
// Assignments now working
expect(client.getStringAssignment('subject', flagKey)).toBe('control');
});

describe('With reloaded index module', () => {
// eslint-disable-next-line @typescript-eslint/ban-types
let init: Function;
// eslint-disable-next-line @typescript-eslint/ban-types
let getInstance: Function;
beforeEach(() => {
jest.isolateModules(() => {
// Isolate and re-require so that the static instance is reset to it's default state
// eslint-disable-next-line @typescript-eslint/no-var-requires
const reloadedModule = require('./index');
init = reloadedModule.init;
getInstance = reloadedModule.getInstance;
});
});

it('returns empty assignments pre-initialization by default', async () => {
returnRac = () => mockConfigResponse;
const client = getInstance();
expect(client.getStringAssignment('subject', flagKey)).toBeNull();
// don't await
init({
apiKey,
baseUrl,
assignmentLogger: mockLogger,
});
expect(client.getStringAssignment('subject', flagKey)).toBeNull();
// Advance time so a poll happened and check again
await jest.advanceTimersByTimeAsync(POLL_INTERVAL_MS);
expect(client.getStringAssignment('subject', flagKey)).toBe('control');
});
});
});
});
29 changes: 19 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,15 @@ export interface IClientConfig {

export { IAssignmentLogger, IAssignmentEvent, IEppoClient } from '@eppo/js-client-sdk-common';

const localStorage = new EppoLocalStorage();

/**
* Client for assigning experiment variations.
* @public
*/
export class EppoJSClient extends EppoClient {
public static instance: EppoJSClient;
public static instance: EppoJSClient = new EppoJSClient(localStorage);
public static initialized = false;

public getAssignment(
subjectKey: string,
Expand All @@ -83,6 +86,7 @@ export class EppoJSClient extends EppoClient {
subjectAttributes?: Record<string, any>,
assignmentHooks?: IAssignmentHooks,
): string | null {
EppoJSClient.getAssignmentInitializationCheck();
return super.getAssignment(subjectKey, flagKey, subjectAttributes, assignmentHooks, true);
}

Expand All @@ -93,6 +97,7 @@ export class EppoJSClient extends EppoClient {
subjectAttributes?: Record<string, any>,
assignmentHooks?: IAssignmentHooks,
): string | null {
EppoJSClient.getAssignmentInitializationCheck();
return super.getStringAssignment(subjectKey, flagKey, subjectAttributes, assignmentHooks, true);
}

Expand All @@ -103,6 +108,7 @@ export class EppoJSClient extends EppoClient {
subjectAttributes?: Record<string, any>,
assignmentHooks?: IAssignmentHooks,
): boolean | null {
EppoJSClient.getAssignmentInitializationCheck();
return super.getBoolAssignment(subjectKey, flagKey, subjectAttributes, assignmentHooks, true);
}

Expand All @@ -113,6 +119,7 @@ export class EppoJSClient extends EppoClient {
subjectAttributes?: Record<string, any>,
assignmentHooks?: IAssignmentHooks,
): number | null {
EppoJSClient.getAssignmentInitializationCheck();
return super.getNumericAssignment(
subjectKey,
flagKey,
Expand All @@ -129,6 +136,7 @@ export class EppoJSClient extends EppoClient {
subjectAttributes?: Record<string, any>,
assignmentHooks?: IAssignmentHooks,
): string | null {
EppoJSClient.getAssignmentInitializationCheck();
return super.getJSONStringAssignment(
subjectKey,
flagKey,
Expand All @@ -145,6 +153,7 @@ export class EppoJSClient extends EppoClient {
subjectAttributes?: Record<string, any>,
assignmentHooks?: IAssignmentHooks,
): object | null {
EppoJSClient.getAssignmentInitializationCheck();
return super.getParsedJSONAssignment(
subjectKey,
flagKey,
Expand All @@ -153,6 +162,12 @@ export class EppoJSClient extends EppoClient {
true,
);
}

private static getAssignmentInitializationCheck() {
if (!EppoJSClient.initialized) {
console.warn('Eppo SDK assignment requested before init() completed');
}
}
}

/**
Expand All @@ -169,8 +184,6 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
EppoJSClient.instance.stopPolling();
}

const localStorage = new EppoLocalStorage();

const requestConfiguration: ExperimentConfigurationRequestParameters = {
apiKey: config.apiKey,
sdkName,
Expand All @@ -184,18 +197,16 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
throwOnFailedInitialization: true, // always use true here as underlying instance fetch is surrounded by try/catch
};

EppoJSClient.instance = new EppoJSClient(localStorage, requestConfiguration);

EppoJSClient.instance.setLogger(config.assignmentLogger);

// default behavior is to use a LocalStorage-based assignment cache.
// this can be overridden after initialization.
EppoJSClient.instance.useCustomAssignmentCache(new LocalStorageAssignmentCache());

EppoJSClient.instance.setConfigurationRequestParameters(requestConfiguration);
await EppoJSClient.instance.fetchFlagConfigurations();
} catch (error) {
console.warn(
'Error encountered initializing the Eppo SDK, assignment calls will return null and not be logged' +
'Eppo SDK encountered an error initializing, assignment calls will return null and not be logged' +
(config.pollAfterFailedInitialization
? ' until an experiment configuration is successfully retrieved'
: ''),
Expand All @@ -204,6 +215,7 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
throw error;
}
}
EppoJSClient.initialized = true;
return EppoJSClient.instance;
}

Expand All @@ -214,8 +226,5 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
* @public
*/
export function getInstance(): IEppoClient {
if (!EppoJSClient.instance) {
throw Error('init() must first be called to initialize a client instance');
}
return EppoJSClient.instance;
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,10 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==

"@eppo/[email protected].0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@eppo/js-client-sdk-common/-/js-client-sdk-common-2.1.0.tgz#491016d969b4dbca7f0101eda2146493eb6670d5"
integrity sha512-oORik7V/+8IitINavwRB6crEDC2k6LjjIqUDn9olMgr3zZsTEHsnPfTzbAkSRhWt0MT9lhjKAmjHubb/eRQ7+A==
"@eppo/[email protected].1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@eppo/js-client-sdk-common/-/js-client-sdk-common-2.1.1.tgz#67e69998702d90d8e927418b31629c614483064c"
integrity sha512-fBFLUgTsTbzjxjxtk9Fcr3lSty02EnLFslzF39gkn5px4mgHQjLW9wwyY0i1E4LNkWsDhaBp52ni60Y7OHB2BA==
dependencies:
axios "^1.6.0"
lru-cache "^10.0.1"
Expand Down

0 comments on commit e842de5

Please sign in to comment.