Skip to content

Commit 2fcc446

Browse files
AndyBitzdferber90
andauthored
Create LaunchDarkly adapter (#2)
* Create LaunchDarkly adapter * Update example to include a LaunchDarkly flag * Add missing env to turbo * Rename to defaultLaunchDarklyAdapter * Add winter sale example for providers * Update winter-sale * Document usage in readme * Allow to set the default value through the adapter * Fix readme * Add default value for adapter * Make argument optional * Add @ts-expect-error * Add a LaunchDarkly adapter (#10) * add ld example * reword * remove winter-sale example * remove defaultValue * use @flags-sdk/launchdarkly * add install steps * add changeset * move @vercel/edge-config to peer deps * add more secrets * spell out name * remove unused env var * rename properties * Capitalize * Change import * Fix more imports * expose ldClient * move peerDeps to real deps * clarify Edge Config requirement in README * remove adapter-launchdarkly snippet * upgrade next@canary * polish @flags-sdk/launchdarkly README --------- Co-authored-by: Dominik Ferber <[email protected]>
1 parent a5bfb13 commit 2fcc446

File tree

11 files changed

+329
-160
lines changed

11 files changed

+329
-160
lines changed

.changeset/kind-carpets-unite.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@flags-sdk/launchdarkly': minor
3+
---
4+
5+
Add LaunchDarkly adapter

.github/workflows/release-snapshot.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@ jobs:
8484
EDGE_CONFIG: ${{ secrets.EDGE_CONFIG }}
8585
FLAGS_SECRET: ${{ secrets.FLAGS_SECRET }}
8686
FLAGS: ${{ secrets.FLAGS }}
87-
LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }}
8887
HAPPYKIT_API_TOKEN: ${{ secrets.HAPPYKIT_API_TOKEN }}
8988
HAPPYKIT_ENV_KEY: ${{ secrets.HAPPYKIT_ENV_KEY }}
89+
LAUNCHDARKLY_CLIENT_SIDE_ID: ${{ secrets.LAUNCHDARKLY_CLIENT_SIDE_ID }}
90+
LAUNCHDARKLY_PROJECT_SLUG: ${{ secrets.LAUNCHDARKLY_PROJECT_SLUG }}
9091

9192
- name: Publish Snapshot Release
9293
run: pnpm changeset publish --no-git-tag --tag snapshot

.github/workflows/release.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ jobs:
4242
NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
4343
EDGE_CONFIG: ${{ secrets.EDGE_CONFIG }}
4444
FLAGS_SECRET: ${{ secrets.FLAGS_SECRET }}
45-
LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }}
4645
HAPPYKIT_API_TOKEN: ${{ secrets.HAPPYKIT_API_TOKEN }}
4746
HAPPYKIT_ENV_KEY: ${{ secrets.HAPPYKIT_ENV_KEY }}
47+
LAUNCHDARKLY_CLIENT_SIDE_ID: ${{ secrets.LAUNCHDARKLY_CLIENT_SIDE_ID }}
48+
LAUNCHDARKLY_PROJECT_SLUG: ${{ secrets.LAUNCHDARKLY_PROJECT_SLUG }}

examples/shirt-shop/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"framer-motion": "12.4.7",
2626
"js-xxhash": "4.0.0",
2727
"nanoid": "5.1.2",
28-
"next": "15.2.1-canary.4",
28+
"next": "15.2.2-canary.4",
2929
"react": "^19.0.0",
3030
"react-dom": "^19.0.0",
3131
"sonner": "2.0.1"

examples/snippets/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"start": "next start"
1010
},
1111
"dependencies": {
12+
"@flags-sdk/launchdarkly": "workspace:*",
13+
"@launchdarkly/vercel-server-sdk": "1.3.21",
1214
"@radix-ui/react-dialog": "1.1.2",
1315
"@radix-ui/react-separator": "1.1.0",
1416
"@radix-ui/react-slot": "1.1.0",
@@ -19,7 +21,7 @@
1921
"clsx": "2.0.0",
2022
"flags": "workspace:*",
2123
"nanoid": "5.0.7",
22-
"next": "15.2.1-canary.2",
24+
"next": "15.2.2-canary.4",
2325
"next-themes": "0.4.4",
2426
"react": "19.0.0-rc.1",
2527
"react-dom": "19.0.0-rc.1",
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Flags SDK - LaunchDarkly Provider
2+
3+
The [LaunchDarkly provider](https://flags-sdk.dev/docs/api-reference/adapters/launchdarkly) for the [Flags SDK](https://flags-sdk.dev/) contains support for LaunchDarkly's Feature Flags.
4+
5+
## Setup
6+
7+
The Statsig provider is available in the `@flags-sdk/statsig` module. You can install it with
8+
9+
```bash
10+
npm i @flags-sdk/launchdarkly
11+
```
12+
13+
## Provider Instance
14+
15+
**NOTE:** The [LaunchDarkly Vercel integration](https://vercel.com/integrations/launchdarkly) must be installed on your account, as this adapter loads LaunchDarkly from Edge Config. The adapter can not be used without Edge Config.
16+
17+
Import the default adapter instance `launchDarkly` from `@flags-sdk/launchdarkly`:
18+
19+
```ts
20+
import { launchDarkly } from '@flags-sdk/launchdarkly';
21+
```
22+
23+
The default adapter uses the following environment variables to configure itself:
24+
25+
```sh
26+
export LAUNCHDARKLY_CLIENT_SIDE_ID="612376f91b8f5713a58777a1"
27+
export LAUNCHDARKLY_PROJECT_SLUG="my-project"
28+
# Provided by Vercel when connecting an Edge Config to the project
29+
export EDGE_CONFIG="https://edge-config.vercel.com/ecfg_abdc1234?token=xxx-xxx-xxx"
30+
```
31+
32+
## Example
33+
34+
```ts
35+
import { flag, dedupe } from 'flags/next';
36+
import { launchDarkly, type LDContext } from '@flags-sdk/launchdarkly';
37+
38+
const identify = dedupe(async (): Promise<LDContext> => {
39+
return {
40+
key: 'user_123',
41+
};
42+
});
43+
44+
export const showBanner = flag<boolean, LDContext>({
45+
key: 'show-banner',
46+
identify,
47+
adapter: launchDarkly(),
48+
});
49+
```
50+
51+
## Custom Adapter
52+
53+
Create an adapter by using the `createLaunchDarklyAdapter` function:
54+
55+
```ts
56+
import { createLaunchDarklyAdapter } from '@flags-sdk/launchdarkly';
57+
58+
const adapter = createLaunchDarklyAdapter({
59+
projectSlug: 'my-project',
60+
ldClientSideKey: '612376f91b8f5713a58777a1',
61+
edgeConfigConnectionString: process.env.EDGE_CONFIG,
62+
});
63+
```
64+
65+
## Documentation
66+
67+
Please check out the [LaunchDarkly provider documentation](https://flags-sdk.dev/docs/api-reference/adapters/launchdarkly) for more information.

packages/adapter-launchdarkly/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
"test:watch": "vitest",
3636
"type-check": "tsc --noEmit"
3737
},
38-
"dependencies": {},
38+
"dependencies": {
39+
"@launchdarkly/vercel-server-sdk": "^1.3.23",
40+
"@vercel/edge-config": "^1.4.0"
41+
},
3942
"devDependencies": {
4043
"@types/node": "20.11.17",
4144
"eslint-config-custom": "workspace:*",
@@ -48,7 +51,6 @@
4851
"vite": "5.1.1",
4952
"vitest": "1.4.0"
5053
},
51-
"peerDependencies": {},
5254
"publishConfig": {
5355
"access": "public"
5456
}
+78
Original file line numberDiff line numberDiff line change
@@ -1 +1,79 @@
1+
import type { Adapter } from 'flags';
2+
import { createClient } from '@vercel/edge-config';
3+
import {
4+
init,
5+
LDClient,
6+
type LDContext,
7+
} from '@launchdarkly/vercel-server-sdk';
8+
19
export { getProviderData } from './provider';
10+
export type { LDContext };
11+
12+
interface AdapterOptions<ValueType> {
13+
defaultValue?: ValueType;
14+
}
15+
16+
let defaultLaunchDarklyAdapter:
17+
| ReturnType<typeof createLaunchDarklyAdapter>
18+
| undefined;
19+
20+
function assertEnv(name: string): string {
21+
const value = process.env[name];
22+
if (!value) {
23+
throw new Error(
24+
`LaunchDarkly Adapter: Missing ${name} environment variable`,
25+
);
26+
}
27+
return value;
28+
}
29+
30+
export function createLaunchDarklyAdapter({
31+
projectSlug,
32+
clientSideId,
33+
edgeConfigConnectionString,
34+
}: {
35+
projectSlug: string;
36+
clientSideId: string;
37+
edgeConfigConnectionString: string;
38+
}) {
39+
const edgeConfigClient = createClient(edgeConfigConnectionString);
40+
const ldClient = init(clientSideId, edgeConfigClient);
41+
42+
return function launchDarklyAdapter<ValueType>(
43+
options: AdapterOptions<ValueType> = {},
44+
): Adapter<ValueType, LDContext> & { ldClient: LDClient } {
45+
return {
46+
/** The LaunchDarkly client instance used by the adapter. */
47+
ldClient,
48+
origin(key) {
49+
return `https://app.launchdarkly.com/projects/${projectSlug}/flags/${key}/`;
50+
},
51+
async decide({ key, entities }): Promise<ValueType> {
52+
await ldClient.waitForInitialization();
53+
return ldClient.variation(
54+
key,
55+
entities as LDContext,
56+
options.defaultValue,
57+
) as ValueType;
58+
},
59+
};
60+
};
61+
}
62+
63+
export function launchDarkly<ValueType>(
64+
options?: AdapterOptions<ValueType>,
65+
): Adapter<ValueType, LDContext> {
66+
if (!defaultLaunchDarklyAdapter) {
67+
const edgeConfigConnectionString = assertEnv('EDGE_CONFIG');
68+
const clientSideId = assertEnv('LAUNCHDARKLY_CLIENT_SIDE_ID');
69+
const projectSlug = assertEnv('LAUNCHDARKLY_PROJECT_SLUG');
70+
71+
defaultLaunchDarklyAdapter = createLaunchDarklyAdapter({
72+
projectSlug,
73+
clientSideId,
74+
edgeConfigConnectionString,
75+
});
76+
}
77+
78+
return defaultLaunchDarklyAdapter(options);
79+
}

packages/flags/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export interface Adapter<ValueType, EntitiesType> {
150150
entities?: EntitiesType;
151151
headers: ReadonlyHeaders;
152152
cookies: ReadonlyRequestCookies;
153+
defaultValue?: ValueType;
153154
}) => Promise<ValueType> | ValueType;
154155
}
155156

0 commit comments

Comments
 (0)