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
3 changes: 3 additions & 0 deletions packages/sdk/akamai-base/example/ldClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export const evaluateFlagFromCustomFeatureStore = async (
const client = init({
sdkKey: 'Your-launchdarkly-environment-client-id',
featureStoreProvider: new MyCustomStoreProvider(),
options: {
cacheTtlMs: 1_000,
},
});

return client.variation(flagKey, context, defaultValue);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { AsyncStoreFacade, LDFeatureStore } from '@launchdarkly/js-server-sdk-common';

import { EdgeFeatureStore, EdgeProvider } from '../../src/featureStore';
import * as testData from '../testData.json';

describe('EdgeFeatureStore', () => {
const sdkKey = 'sdkKey';
const mockLogger = {
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
};
const mockEdgeProvider: EdgeProvider = {
get: jest.fn(),
};
const mockGet = mockEdgeProvider.get as jest.Mock;
let featureStore: LDFeatureStore;
let asyncFeatureStore: AsyncStoreFacade;

describe('with infinite cache', () => {
beforeEach(() => {
mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData)));
featureStore = new EdgeFeatureStore(
mockEdgeProvider,
sdkKey,
'MockEdgeProvider',
mockLogger,
0,
);
asyncFeatureStore = new AsyncStoreFacade(featureStore);
});

afterEach(() => {
jest.resetAllMocks();
});

it('will cache the initial request', async () => {
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.all({ namespace: 'features' });

expect(mockGet).toHaveBeenCalledTimes(1);
});
});

describe('with cache disabled', () => {
beforeEach(() => {
mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData)));
featureStore = new EdgeFeatureStore(
mockEdgeProvider,
sdkKey,
'MockEdgeProvider',
mockLogger,
-1,
);
asyncFeatureStore = new AsyncStoreFacade(featureStore);
});

afterEach(() => {
jest.resetAllMocks();
});

it('caches nothing', async () => {
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.all({ namespace: 'features' });

expect(mockGet).toHaveBeenCalledTimes(3);
});
});

describe('with finite cache', () => {
beforeEach(() => {
mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData)));
featureStore = new EdgeFeatureStore(
mockEdgeProvider,
sdkKey,
'MockEdgeProvider',
mockLogger,
100,
);
asyncFeatureStore = new AsyncStoreFacade(featureStore);
});

afterEach(() => {
jest.resetAllMocks();
});

it('expires after configured duration', async () => {
jest.spyOn(Date, 'now').mockImplementation(() => 0);
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.all({ namespace: 'features' });

expect(mockGet).toHaveBeenCalledTimes(1);

jest.spyOn(Date, 'now').mockImplementation(() => 99);
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.all({ namespace: 'features' });

expect(mockGet).toHaveBeenCalledTimes(1);

jest.spyOn(Date, 'now').mockImplementation(() => 100);
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1');
await asyncFeatureStore.all({ namespace: 'features' });

expect(mockGet).toHaveBeenCalledTimes(2);
});
});
});

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ describe('EdgeFeatureStore', () => {

beforeEach(() => {
mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData)));
featureStore = new EdgeFeatureStore(mockEdgeProvider, sdkKey, 'MockEdgeProvider', mockLogger);
featureStore = new EdgeFeatureStore(
mockEdgeProvider,
sdkKey,
'MockEdgeProvider',
mockLogger,
0,
);
asyncFeatureStore = new AsyncStoreFacade(featureStore);
});

Expand Down
11 changes: 2 additions & 9 deletions packages/shared/akamai-edgeworker-sdk/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const createClient = (sdkKey: string, mockLogger: LDLogger, mockEdgeProvider: Ed
sdkKey,
options: {
logger: mockLogger,
cacheTtlMs: 0,
},
featureStoreProvider: mockEdgeProvider,
platformName: 'platform-name',
Expand Down Expand Up @@ -40,22 +41,14 @@ describe('EdgeWorker', () => {
it('should call edge providers get method only once', async () => {
const client = createClient(sdkKey, mockLogger, mockEdgeProvider);
await client.waitForInitialization();
await client.allFlagsState({ kind: 'multi', l: { key: 'key' } });

expect(mockGet).toHaveBeenCalledTimes(1);
});

it('should call edge providers get method only 3 times', async () => {
const client = createClient(sdkKey, mockLogger, mockEdgeProvider);
await client.waitForInitialization();

const context: LDMultiKindContext = { kind: 'multi', l: { key: 'key' } };

await client.allFlagsState(context, { clientSideOnly: true });
await client.variation('testFlag1', context, false);
await client.variationDetail('testFlag1', context, false);

expect(mockGet).toHaveBeenCalledTimes(3);
expect(mockGet).toHaveBeenCalledTimes(1);
});

it('should successfully return data for allFlagsState', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import EdgeRequests from '../../src/platform/requests';
const TEXT_RESPONSE = '';
const JSON_RESPONSE = {};

describe('given a default instance of requets', () => {
describe('given a default instance of requests', () => {
const requests = new EdgeRequests();

describe('fetch', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const mockOptions = ({
}) => {
const mockLogger = logger ?? BasicLogger.get();
const mockFeatureStore =
featureStore ?? new EdgeFeatureStore(edgeProvider, SDK_KEY, 'validationTest', mockLogger);
featureStore ?? new EdgeFeatureStore(edgeProvider, SDK_KEY, 'validationTest', mockLogger, 0);

return {
featureStore: allowEmptyFS ? undefined : mockFeatureStore,
Expand Down
49 changes: 3 additions & 46 deletions packages/shared/akamai-edgeworker-sdk/src/api/LDClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,9 @@
import {
LDClientImpl,
LDClient as LDClientType,
LDContext,
LDEvaluationDetail,
LDFlagsState,
LDFlagsStateOptions,
LDFlagValue,
LDOptions,
} from '@launchdarkly/js-server-sdk-common';

import CacheableStoreProvider from '../featureStore/cacheableStoreProvider';
import EdgePlatform from '../platform';
import { createCallbacks, createOptions } from '../utils';

Expand All @@ -20,58 +14,21 @@ export interface CustomLDOptions extends LDOptions {}
* The LaunchDarkly Akamai SDK edge client object.
*/
class LDClient extends LDClientImpl {
private _cacheableStoreProvider!: CacheableStoreProvider;

// sdkKey is only used to query featureStore, not to initialize with LD servers
constructor(
sdkKey: string,
platform: EdgePlatform,
options: LDOptions,
storeProvider: CacheableStoreProvider,
) {
constructor(sdkKey: string, platform: EdgePlatform, options: LDOptions) {
const finalOptions = createOptions(options);
super(sdkKey, platform, finalOptions, createCallbacks(finalOptions.logger));
this._cacheableStoreProvider = storeProvider;
}

override initialized(): boolean {
return true;
}

override waitForInitialization(): Promise<LDClientType> {
// we need to resolve the promise immediately because Akamai's runtime doesnt
// have a setimeout so everything executes synchronously.
// we need to resolve the promise immediately because Akamai's runtime doesn't
// have a setTimeout so everything executes synchronously.
return Promise.resolve(this);
}

override async variation(
key: string,
context: LDContext,
defaultValue: LDFlagValue,
callback?: (err: any, res: LDFlagValue) => void,
): Promise<LDFlagValue> {
await this._cacheableStoreProvider.prefetchPayloadFromOriginStore();
return super.variation(key, context, defaultValue, callback);
}

override async variationDetail(
key: string,
context: LDContext,
defaultValue: LDFlagValue,
callback?: (err: any, res: LDEvaluationDetail) => void,
): Promise<LDEvaluationDetail> {
await this._cacheableStoreProvider.prefetchPayloadFromOriginStore();
return super.variationDetail(key, context, defaultValue, callback);
}

override async allFlagsState(
context: LDContext,
options?: LDFlagsStateOptions,
callback?: (err: Error | null, res: LDFlagsState) => void,
): Promise<LDFlagsState> {
await this._cacheableStoreProvider.prefetchPayloadFromOriginStore();
return super.allFlagsState(context, options, callback);
}
}

export default LDClient;
Loading