Skip to content

Commit e842de5

Browse files
authored
Default uninitialized EppoClient instance (#48)
* initialize instance off the bat; set request configuration later * make initialize flag public * correct docs * more doc fixes * use newly published common sdk
1 parent f742feb commit e842de5

9 files changed

+77
-19
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
2+
3+
[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)
4+
5+
## EppoJSClient.initialized property
6+
7+
**Signature:**
8+
9+
```typescript
10+
static initialized: boolean;
11+
```

docs/js-client-sdk.eppojsclient.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export declare class EppoJSClient extends EppoClient
1717
1818
| Property | Modifiers | Type | Description |
1919
| --- | --- | --- | --- |
20+
| [initialized](./js-client-sdk.eppojsclient.initialized.md) | <code>static</code> | boolean | |
2021
| [instance](./js-client-sdk.eppojsclient.instance.md) | <code>static</code> | [EppoJSClient](./js-client-sdk.eppojsclient.md) | |
2122
2223
## Methods

docs/js-client-sdk.iclientconfig.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ export interface IClientConfig
2424
| [pollAfterFailedInitialization?](./js-client-sdk.iclientconfig.pollafterfailedinitialization.md) | | boolean | _(Optional)_ Poll for new configurations even if the initial configuration request failed. (default: false) |
2525
| [pollAfterSuccessfulInitialization?](./js-client-sdk.iclientconfig.pollaftersuccessfulinitialization.md) | | boolean | _(Optional)_ Poll for new configurations (every 30 seconds) after successfully requesting the initial configuration. (default: false) |
2626
| [requestTimeoutMs?](./js-client-sdk.iclientconfig.requesttimeoutms.md) | | number | _(Optional)_ \* Timeout in milliseconds for the HTTPS request for the experiment configuration. (Default: 5000) |
27-
| [throwOnFailedInitialization?](./js-client-sdk.iclientconfig.throwonfailedinitialization.md) | | boolean | _(Optional)_ Throw on error if unable to fetch an initial configuration during initialization. (default: true) |
27+
| [throwOnFailedInitialization?](./js-client-sdk.iclientconfig.throwonfailedinitialization.md) | | boolean | _(Optional)_ Throw an error if unable to fetch an initial configuration during initialization. (default: true) |
2828

docs/js-client-sdk.iclientconfig.throwonfailedinitialization.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## IClientConfig.throwOnFailedInitialization property
66

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

99
**Signature:**
1010

js-client-sdk.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export class EppoJSClient extends EppoClient {
2525
// (undocumented)
2626
getStringAssignment(subjectKey: string, flagKey: string, subjectAttributes?: Record<string, any>, assignmentHooks?: IAssignmentHooks): string | null;
2727
// (undocumented)
28+
static initialized: boolean;
29+
// (undocumented)
2830
static instance: EppoJSClient;
2931
}
3032

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"xhr-mock": "^2.5.1"
5959
},
6060
"dependencies": {
61-
"@eppo/js-client-sdk-common": "2.1.0",
61+
"@eppo/js-client-sdk-common": "2.1.1",
6262
"md5": "^2.3.0"
6363
}
6464
}

src/index.spec.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

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

2830
const apiKey = 'dummy';
2931
const baseUrl = 'http://127.0.0.1:4000';
@@ -81,7 +83,7 @@ describe('EppoJSClient E2E test', () => {
8183
beforeAll(async () => {
8284
mock.setup();
8385
mock.get(/randomized_assignment\/v3\/config*/, (_req, res) => {
84-
const rac = readMockRacResponse();
86+
const rac = returnRac();
8587
return res.status(200).body(JSON.stringify(rac));
8688
});
8789
mockLogger = td.object<IAssignmentLogger>();
@@ -93,6 +95,7 @@ describe('EppoJSClient E2E test', () => {
9395
});
9496

9597
afterEach(() => {
98+
returnRac = readMockRacResponse;
9699
globalClient.setLogger(mockLogger);
97100
td.reset();
98101
});
@@ -379,7 +382,7 @@ describe('EppoJSClient E2E test', () => {
379382
flags: {
380383
[hashedFlagKey]: mockExperimentConfig,
381384
},
382-
};
385+
} as unknown as Record<string, IExperimentConfiguration>;
383386

384387
beforeAll(() => {
385388
jest.useFakeTimers({
@@ -545,5 +548,37 @@ describe('EppoJSClient E2E test', () => {
545548
// Assignments now working
546549
expect(client.getStringAssignment('subject', flagKey)).toBe('control');
547550
});
551+
552+
describe('With reloaded index module', () => {
553+
// eslint-disable-next-line @typescript-eslint/ban-types
554+
let init: Function;
555+
// eslint-disable-next-line @typescript-eslint/ban-types
556+
let getInstance: Function;
557+
beforeEach(() => {
558+
jest.isolateModules(() => {
559+
// Isolate and re-require so that the static instance is reset to it's default state
560+
// eslint-disable-next-line @typescript-eslint/no-var-requires
561+
const reloadedModule = require('./index');
562+
init = reloadedModule.init;
563+
getInstance = reloadedModule.getInstance;
564+
});
565+
});
566+
567+
it('returns empty assignments pre-initialization by default', async () => {
568+
returnRac = () => mockConfigResponse;
569+
const client = getInstance();
570+
expect(client.getStringAssignment('subject', flagKey)).toBeNull();
571+
// don't await
572+
init({
573+
apiKey,
574+
baseUrl,
575+
assignmentLogger: mockLogger,
576+
});
577+
expect(client.getStringAssignment('subject', flagKey)).toBeNull();
578+
// Advance time so a poll happened and check again
579+
await jest.advanceTimersByTimeAsync(POLL_INTERVAL_MS);
580+
expect(client.getStringAssignment('subject', flagKey)).toBe('control');
581+
});
582+
});
548583
});
549584
});

src/index.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,15 @@ export interface IClientConfig {
6969

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

72+
const localStorage = new EppoLocalStorage();
73+
7274
/**
7375
* Client for assigning experiment variations.
7476
* @public
7577
*/
7678
export class EppoJSClient extends EppoClient {
77-
public static instance: EppoJSClient;
79+
public static instance: EppoJSClient = new EppoJSClient(localStorage);
80+
public static initialized = false;
7881

7982
public getAssignment(
8083
subjectKey: string,
@@ -83,6 +86,7 @@ export class EppoJSClient extends EppoClient {
8386
subjectAttributes?: Record<string, any>,
8487
assignmentHooks?: IAssignmentHooks,
8588
): string | null {
89+
EppoJSClient.getAssignmentInitializationCheck();
8690
return super.getAssignment(subjectKey, flagKey, subjectAttributes, assignmentHooks, true);
8791
}
8892

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

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

@@ -113,6 +119,7 @@ export class EppoJSClient extends EppoClient {
113119
subjectAttributes?: Record<string, any>,
114120
assignmentHooks?: IAssignmentHooks,
115121
): number | null {
122+
EppoJSClient.getAssignmentInitializationCheck();
116123
return super.getNumericAssignment(
117124
subjectKey,
118125
flagKey,
@@ -129,6 +136,7 @@ export class EppoJSClient extends EppoClient {
129136
subjectAttributes?: Record<string, any>,
130137
assignmentHooks?: IAssignmentHooks,
131138
): string | null {
139+
EppoJSClient.getAssignmentInitializationCheck();
132140
return super.getJSONStringAssignment(
133141
subjectKey,
134142
flagKey,
@@ -145,6 +153,7 @@ export class EppoJSClient extends EppoClient {
145153
subjectAttributes?: Record<string, any>,
146154
assignmentHooks?: IAssignmentHooks,
147155
): object | null {
156+
EppoJSClient.getAssignmentInitializationCheck();
148157
return super.getParsedJSONAssignment(
149158
subjectKey,
150159
flagKey,
@@ -153,6 +162,12 @@ export class EppoJSClient extends EppoClient {
153162
true,
154163
);
155164
}
165+
166+
private static getAssignmentInitializationCheck() {
167+
if (!EppoJSClient.initialized) {
168+
console.warn('Eppo SDK assignment requested before init() completed');
169+
}
170+
}
156171
}
157172

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

172-
const localStorage = new EppoLocalStorage();
173-
174187
const requestConfiguration: ExperimentConfigurationRequestParameters = {
175188
apiKey: config.apiKey,
176189
sdkName,
@@ -184,18 +197,16 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
184197
throwOnFailedInitialization: true, // always use true here as underlying instance fetch is surrounded by try/catch
185198
};
186199

187-
EppoJSClient.instance = new EppoJSClient(localStorage, requestConfiguration);
188-
189200
EppoJSClient.instance.setLogger(config.assignmentLogger);
190201

191202
// default behavior is to use a LocalStorage-based assignment cache.
192203
// this can be overridden after initialization.
193204
EppoJSClient.instance.useCustomAssignmentCache(new LocalStorageAssignmentCache());
194-
205+
EppoJSClient.instance.setConfigurationRequestParameters(requestConfiguration);
195206
await EppoJSClient.instance.fetchFlagConfigurations();
196207
} catch (error) {
197208
console.warn(
198-
'Error encountered initializing the Eppo SDK, assignment calls will return null and not be logged' +
209+
'Eppo SDK encountered an error initializing, assignment calls will return null and not be logged' +
199210
(config.pollAfterFailedInitialization
200211
? ' until an experiment configuration is successfully retrieved'
201212
: ''),
@@ -204,6 +215,7 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
204215
throw error;
205216
}
206217
}
218+
EppoJSClient.initialized = true;
207219
return EppoJSClient.instance;
208220
}
209221

@@ -214,8 +226,5 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
214226
* @public
215227
*/
216228
export function getInstance(): IEppoClient {
217-
if (!EppoJSClient.instance) {
218-
throw Error('init() must first be called to initialize a client instance');
219-
}
220229
return EppoJSClient.instance;
221230
}

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,10 @@
380380
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
381381
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
382382

383-
384-
version "2.1.0"
385-
resolved "https://registry.yarnpkg.com/@eppo/js-client-sdk-common/-/js-client-sdk-common-2.1.0.tgz#491016d969b4dbca7f0101eda2146493eb6670d5"
386-
integrity sha512-oORik7V/+8IitINavwRB6crEDC2k6LjjIqUDn9olMgr3zZsTEHsnPfTzbAkSRhWt0MT9lhjKAmjHubb/eRQ7+A==
383+
384+
version "2.1.1"
385+
resolved "https://registry.yarnpkg.com/@eppo/js-client-sdk-common/-/js-client-sdk-common-2.1.1.tgz#67e69998702d90d8e927418b31629c614483064c"
386+
integrity sha512-fBFLUgTsTbzjxjxtk9Fcr3lSty02EnLFslzF39gkn5px4mgHQjLW9wwyY0i1E4LNkWsDhaBp52ni60Y7OHB2BA==
387387
dependencies:
388388
axios "^1.6.0"
389389
lru-cache "^10.0.1"

0 commit comments

Comments
 (0)