Skip to content

Commit 917ef42

Browse files
authored
ldAdapter() » ldAdapter.variation() (#94)
* expose ldClient on default ldAdapter * change API * adapt README * change to type import * fix path
1 parent b375e4e commit 917ef42

File tree

5 files changed

+132
-20
lines changed

5 files changed

+132
-20
lines changed

.changeset/chilly-bags-agree.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@flags-sdk/launchdarkly': minor
3+
---
4+
5+
change API from ldAdapter() to ldAdapter.variation()

.changeset/smooth-turkeys-protect.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@flags-sdk/launchdarkly': patch
3+
---
4+
5+
expose ldClient on default ldAdapter

packages/adapter-launchdarkly/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const identify = dedupe(async (): Promise<LDContext> => {
4444
export const showBanner = flag<boolean, LDContext>({
4545
key: 'show-banner',
4646
identify,
47-
adapter: ldAdapter(),
47+
adapter: ldAdapter.variation(),
4848
});
4949
```
5050

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { ReadonlyHeaders, ReadonlyRequestCookies } from 'flags';
2+
import { expect, it, describe, vi, beforeAll } from 'vitest';
3+
import { ldAdapter, type LDContext } from '.';
4+
5+
const ldClientMock = {
6+
waitForInitialization: vi.fn(),
7+
variation: vi.fn(),
8+
};
9+
10+
vi.mock('@launchdarkly/vercel-server-sdk', () => ({
11+
init: vi.fn(() => ldClientMock),
12+
}));
13+
14+
vi.mock('@vercel/edge-config', () => ({
15+
createClient: vi.fn(),
16+
}));
17+
18+
describe('ldAdapter', () => {
19+
it('should variation should be a function', () => {
20+
expect(ldAdapter.variation).toBeInstanceOf(Function);
21+
});
22+
23+
describe('with a missing environment', () => {
24+
it('should throw an error', () => {
25+
expect(() => ldAdapter.variation()).toThrowError(
26+
'LaunchDarkly Adapter: Missing EDGE_CONFIG environment variable',
27+
);
28+
});
29+
});
30+
31+
describe('with an environment', () => {
32+
beforeAll(() => {
33+
process.env.LAUNCHDARKLY_PROJECT_SLUG = 'test-project';
34+
process.env.LAUNCHDARKLY_CLIENT_SIDE_ID = 'test-client-side-id';
35+
process.env.EDGE_CONFIG = 'https://edge-config.com/test-edge-config';
36+
});
37+
38+
it('should expose the ldClient', () => {
39+
expect(ldAdapter).toHaveProperty('ldClient');
40+
});
41+
42+
describe('variation', () => {
43+
it('should return the origin', () => {
44+
const v = ldAdapter.variation();
45+
expect(typeof v.origin).toEqual('function');
46+
47+
if (typeof v.origin !== 'function')
48+
throw new Error('origin is not a function');
49+
50+
expect(v.origin?.('test-flag')).toEqual(
51+
'https://app.launchdarkly.com/projects/test-project/flags/test-flag/',
52+
);
53+
});
54+
it('should decide', async () => {
55+
ldClientMock.variation.mockReturnValue(true);
56+
57+
const valuePromise = ldAdapter.variation().decide({
58+
key: 'test-flag',
59+
headers: {} as ReadonlyHeaders,
60+
cookies: {} as ReadonlyRequestCookies,
61+
entities: {} as LDContext,
62+
defaultValue: false,
63+
});
64+
65+
await expect(valuePromise).resolves.toEqual(true);
66+
expect(ldClientMock.variation).toHaveBeenCalled();
67+
});
68+
});
69+
});
70+
});

packages/adapter-launchdarkly/src/index.ts

+51-19
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ interface AdapterOptions<ValueType> {
1313
defaultValue?: ValueType;
1414
}
1515

16+
type AdapterResponse = {
17+
variation: <ValueType>(
18+
options?: AdapterOptions<ValueType>,
19+
) => Adapter<ValueType, LDContext>;
20+
/** The LaunchDarkly client instance used by the adapter. */
21+
ldClient: LDClient;
22+
};
23+
1624
let defaultLaunchDarklyAdapter:
1725
| ReturnType<typeof createLaunchDarklyAdapter>
1826
| undefined;
@@ -35,23 +43,19 @@ export function createLaunchDarklyAdapter({
3543
projectSlug: string;
3644
clientSideId: string;
3745
edgeConfigConnectionString: string;
38-
}): {
39-
<ValueType>(
40-
options?: AdapterOptions<ValueType>,
41-
): Adapter<ValueType, LDContext>;
42-
/** The LaunchDarkly client instance used by the adapter. */
43-
ldClient: LDClient;
44-
} {
46+
}): AdapterResponse {
4547
const edgeConfigClient = createClient(edgeConfigConnectionString);
4648
const ldClient = init(clientSideId, edgeConfigClient);
4749

48-
const launchDarklyAdapter = function launchDarklyAdapter<ValueType>(
50+
function origin(key: string) {
51+
return `https://app.launchdarkly.com/projects/${projectSlug}/flags/${key}/`;
52+
}
53+
54+
function variation<ValueType>(
4955
options: AdapterOptions<ValueType> = {},
5056
): Adapter<ValueType, LDContext> {
5157
return {
52-
origin(key) {
53-
return `https://app.launchdarkly.com/projects/${projectSlug}/flags/${key}/`;
54-
},
58+
origin,
5559
async decide({ key, entities }): Promise<ValueType> {
5660
await ldClient.waitForInitialization();
5761
return ldClient.variation(
@@ -61,16 +65,15 @@ export function createLaunchDarklyAdapter({
6165
) as ValueType;
6266
},
6367
};
64-
};
65-
66-
launchDarklyAdapter.ldClient = ldClient;
68+
}
6769

68-
return launchDarklyAdapter;
70+
return {
71+
ldClient,
72+
variation,
73+
};
6974
}
7075

71-
export function ldAdapter<ValueType>(
72-
options?: AdapterOptions<ValueType>,
73-
): Adapter<ValueType, LDContext> {
76+
function getOrCreateDeaultAdapter() {
7477
if (!defaultLaunchDarklyAdapter) {
7578
const edgeConfigConnectionString = assertEnv('EDGE_CONFIG');
7679
const clientSideId = assertEnv('LAUNCHDARKLY_CLIENT_SIDE_ID');
@@ -83,9 +86,38 @@ export function ldAdapter<ValueType>(
8386
});
8487
}
8588

86-
return defaultLaunchDarklyAdapter(options);
89+
return defaultLaunchDarklyAdapter;
8790
}
8891

92+
/**
93+
* The default LaunchDarkly adapter.
94+
*
95+
* This is a convenience object that pre-initializes the LaunchDarkly SDK and provides
96+
* the adapter function for usage with the Flags SDK.
97+
*
98+
* This is the recommended way to use the LaunchDarkly adapter.
99+
*
100+
* ```ts
101+
* // flags.ts
102+
* import { flag } from 'flags/next';
103+
* import { ldAdapter, type LDContext } from '@flags-sdk/launchdarkly';
104+
*
105+
* const flag = flag<boolean, LDContext>({
106+
* key: 'my-flag',
107+
* defaultValue: false,
108+
* identify: () => ({ key: "user-123" }),
109+
* adapter: ldAdapter.variation(),
110+
* });
111+
* ```
112+
*/
113+
export const ldAdapter: AdapterResponse = {
114+
variation: (...args) => getOrCreateDeaultAdapter().variation(...args),
115+
get ldClient() {
116+
return getOrCreateDeaultAdapter().ldClient;
117+
},
118+
};
119+
120+
/**
89121
/**
90122
* This is the previous name for the LaunchDarkly adapter.
91123
*

0 commit comments

Comments
 (0)