Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create LaunchDarkly adapter #2

Merged
merged 34 commits into from
Mar 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5a0cdd1
Create LaunchDarkly adapter
AndyBitz Dec 12, 2024
57c6df2
Update example to include a LaunchDarkly flag
AndyBitz Dec 12, 2024
3005d5e
Add missing env to turbo
AndyBitz Dec 13, 2024
8dad467
Rename to defaultLaunchDarklyAdapter
AndyBitz Dec 16, 2024
fda8ffe
Add winter sale example for providers
AndyBitz Dec 16, 2024
eaec3e4
Update winter-sale
AndyBitz Dec 16, 2024
7d79635
Document usage in readme
AndyBitz Dec 16, 2024
a61b43e
Allow to set the default value through the adapter
AndyBitz Dec 17, 2024
03aec1e
Fix readme
AndyBitz Dec 17, 2024
5700ea6
Add default value for adapter
AndyBitz Dec 17, 2024
e6ff6fc
Make argument optional
AndyBitz Dec 17, 2024
9af38a7
Add @ts-expect-error
AndyBitz Dec 18, 2024
26b12ae
Add a LaunchDarkly adapter (#10)
dferber90 Jan 24, 2025
0c2079c
remove winter-sale example
dferber90 Jan 24, 2025
d371749
remove defaultValue
dferber90 Jan 24, 2025
1a4bb69
use @flags-sdk/launchdarkly
dferber90 Jan 24, 2025
7a70576
add install steps
dferber90 Jan 24, 2025
4e19b9f
add changeset
dferber90 Jan 24, 2025
723207a
move @vercel/edge-config to peer deps
dferber90 Jan 24, 2025
7c7b392
add more secrets
dferber90 Jan 27, 2025
90de7db
spell out name
dferber90 Jan 27, 2025
0672606
remove unused env var
dferber90 Jan 27, 2025
dd9e48a
rename properties
dferber90 Jan 27, 2025
c1259f6
Merge branch 'main' of github.com:vercel/flags into add-launch-darkly…
AndyBitz Feb 25, 2025
04e2faa
Capitalize
AndyBitz Feb 25, 2025
4ef97cc
Change import
AndyBitz Feb 25, 2025
45a7a31
Fix more imports
AndyBitz Feb 25, 2025
2b647f8
Merge branch 'main' into add-launch-darkly-adapter
dferber90 Mar 8, 2025
c4be1ea
expose ldClient
dferber90 Mar 8, 2025
44c5810
move peerDeps to real deps
dferber90 Mar 8, 2025
1fa4aea
clarify Edge Config requirement in README
dferber90 Mar 8, 2025
ff51af2
remove adapter-launchdarkly snippet
dferber90 Mar 8, 2025
34eee7e
upgrade next@canary
dferber90 Mar 8, 2025
b4fda10
polish @flags-sdk/launchdarkly README
dferber90 Mar 8, 2025
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
5 changes: 5 additions & 0 deletions .changeset/kind-carpets-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@flags-sdk/launchdarkly': minor
---

Add LaunchDarkly adapter
3 changes: 2 additions & 1 deletion .github/workflows/release-snapshot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ jobs:
EDGE_CONFIG: ${{ secrets.EDGE_CONFIG }}
FLAGS_SECRET: ${{ secrets.FLAGS_SECRET }}
FLAGS: ${{ secrets.FLAGS }}
LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }}
HAPPYKIT_API_TOKEN: ${{ secrets.HAPPYKIT_API_TOKEN }}
HAPPYKIT_ENV_KEY: ${{ secrets.HAPPYKIT_ENV_KEY }}
LAUNCHDARKLY_CLIENT_SIDE_ID: ${{ secrets.LAUNCHDARKLY_CLIENT_SIDE_ID }}
LAUNCHDARKLY_PROJECT_SLUG: ${{ secrets.LAUNCHDARKLY_PROJECT_SLUG }}

- name: Publish Snapshot Release
run: pnpm changeset publish --no-git-tag --tag snapshot
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
EDGE_CONFIG: ${{ secrets.EDGE_CONFIG }}
FLAGS_SECRET: ${{ secrets.FLAGS_SECRET }}
LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }}
HAPPYKIT_API_TOKEN: ${{ secrets.HAPPYKIT_API_TOKEN }}
HAPPYKIT_ENV_KEY: ${{ secrets.HAPPYKIT_ENV_KEY }}
LAUNCHDARKLY_CLIENT_SIDE_ID: ${{ secrets.LAUNCHDARKLY_CLIENT_SIDE_ID }}
LAUNCHDARKLY_PROJECT_SLUG: ${{ secrets.LAUNCHDARKLY_PROJECT_SLUG }}
2 changes: 1 addition & 1 deletion examples/shirt-shop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"framer-motion": "12.4.7",
"js-xxhash": "4.0.0",
"nanoid": "5.1.2",
"next": "15.2.1-canary.4",
"next": "15.2.2-canary.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sonner": "2.0.1"
Expand Down
4 changes: 3 additions & 1 deletion examples/snippets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"start": "next start"
},
"dependencies": {
"@flags-sdk/launchdarkly": "workspace:*",
"@launchdarkly/vercel-server-sdk": "1.3.21",
"@radix-ui/react-dialog": "1.1.2",
"@radix-ui/react-separator": "1.1.0",
"@radix-ui/react-slot": "1.1.0",
Expand All @@ -19,7 +21,7 @@
"clsx": "2.0.0",
"flags": "workspace:*",
"nanoid": "5.0.7",
"next": "15.2.1-canary.2",
"next": "15.2.2-canary.4",
"next-themes": "0.4.4",
"react": "19.0.0-rc.1",
"react-dom": "19.0.0-rc.1",
Expand Down
67 changes: 67 additions & 0 deletions packages/adapter-launchdarkly/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Flags SDK - LaunchDarkly Provider

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.

## Setup

The Statsig provider is available in the `@flags-sdk/statsig` module. You can install it with

```bash
npm i @flags-sdk/launchdarkly
```

## Provider Instance

**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.

Import the default adapter instance `launchDarkly` from `@flags-sdk/launchdarkly`:

```ts
import { launchDarkly } from '@flags-sdk/launchdarkly';
```

The default adapter uses the following environment variables to configure itself:

```sh
export LAUNCHDARKLY_CLIENT_SIDE_ID="612376f91b8f5713a58777a1"
export LAUNCHDARKLY_PROJECT_SLUG="my-project"
# Provided by Vercel when connecting an Edge Config to the project
export EDGE_CONFIG="https://edge-config.vercel.com/ecfg_abdc1234?token=xxx-xxx-xxx"
```

## Example

```ts
import { flag, dedupe } from 'flags/next';
import { launchDarkly, type LDContext } from '@flags-sdk/launchdarkly';

const identify = dedupe(async (): Promise<LDContext> => {
return {
key: 'user_123',
};
});

export const showBanner = flag<boolean, LDContext>({
key: 'show-banner',
identify,
adapter: launchDarkly(),
});
```

## Custom Adapter

Create an adapter by using the `createLaunchDarklyAdapter` function:

```ts
import { createLaunchDarklyAdapter } from '@flags-sdk/launchdarkly';

const adapter = createLaunchDarklyAdapter({
projectSlug: 'my-project',
ldClientSideKey: '612376f91b8f5713a58777a1',
edgeConfigConnectionString: process.env.EDGE_CONFIG,
});
```

## Documentation

Please check out the [LaunchDarkly provider documentation](https://flags-sdk.dev/docs/api-reference/adapters/launchdarkly) for more information.
6 changes: 4 additions & 2 deletions packages/adapter-launchdarkly/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
"test:watch": "vitest",
"type-check": "tsc --noEmit"
},
"dependencies": {},
"dependencies": {
"@launchdarkly/vercel-server-sdk": "^1.3.23",
"@vercel/edge-config": "^1.4.0"
},
"devDependencies": {
"@types/node": "20.11.17",
"eslint-config-custom": "workspace:*",
Expand All @@ -48,7 +51,6 @@
"vite": "5.1.1",
"vitest": "1.4.0"
},
"peerDependencies": {},
"publishConfig": {
"access": "public"
}
Expand Down
78 changes: 78 additions & 0 deletions packages/adapter-launchdarkly/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,79 @@
import type { Adapter } from 'flags';
import { createClient } from '@vercel/edge-config';
import {
init,
LDClient,
type LDContext,
} from '@launchdarkly/vercel-server-sdk';

export { getProviderData } from './provider';
export type { LDContext };

interface AdapterOptions<ValueType> {
defaultValue?: ValueType;
}

let defaultLaunchDarklyAdapter:
| ReturnType<typeof createLaunchDarklyAdapter>
| undefined;

function assertEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(
`LaunchDarkly Adapter: Missing ${name} environment variable`,
);
}
return value;
}

export function createLaunchDarklyAdapter({
projectSlug,
clientSideId,
edgeConfigConnectionString,
}: {
projectSlug: string;
clientSideId: string;
edgeConfigConnectionString: string;
}) {
const edgeConfigClient = createClient(edgeConfigConnectionString);
const ldClient = init(clientSideId, edgeConfigClient);

return function launchDarklyAdapter<ValueType>(
options: AdapterOptions<ValueType> = {},
): Adapter<ValueType, LDContext> & { ldClient: LDClient } {
return {
/** The LaunchDarkly client instance used by the adapter. */
ldClient,
origin(key) {
return `https://app.launchdarkly.com/projects/${projectSlug}/flags/${key}/`;
},
async decide({ key, entities }): Promise<ValueType> {
await ldClient.waitForInitialization();
return ldClient.variation(
key,
entities as LDContext,
options.defaultValue,
) as ValueType;
},
};
};
}

export function launchDarkly<ValueType>(
options?: AdapterOptions<ValueType>,
): Adapter<ValueType, LDContext> {
if (!defaultLaunchDarklyAdapter) {
const edgeConfigConnectionString = assertEnv('EDGE_CONFIG');
const clientSideId = assertEnv('LAUNCHDARKLY_CLIENT_SIDE_ID');
const projectSlug = assertEnv('LAUNCHDARKLY_PROJECT_SLUG');

defaultLaunchDarklyAdapter = createLaunchDarklyAdapter({
projectSlug,
clientSideId,
edgeConfigConnectionString,
});
}

return defaultLaunchDarklyAdapter(options);
}
1 change: 1 addition & 0 deletions packages/flags/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export interface Adapter<ValueType, EntitiesType> {
entities?: EntitiesType;
headers: ReadonlyHeaders;
cookies: ReadonlyRequestCookies;
defaultValue?: ValueType;
}) => Promise<ValueType> | ValueType;
}

Expand Down
Loading
Loading