Skip to content
Open
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
20 changes: 20 additions & 0 deletions .github/workflows/openfeature-node-server-nightly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: sdk/openfeature-node-server nightly

on:
schedule:
- cron: '0 1 * * *'

jobs:
run-getting-started-example:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: ./actions/setup-yarn
with:
node-version: 20
- uses: ./actions/run-example
with:
workspace_name: '@launchdarkly/hello-openfeature-node-server'
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
14 changes: 14 additions & 0 deletions .github/workflows/openfeature-node-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,17 @@ jobs:
workspace_name: '@launchdarkly/openfeature-node-server'
workspace_path: packages/sdk/openfeature-node-server
# TODO: Add contract tests

run-getting-started-example:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: ./actions/setup-yarn
with:
node-version: 20
- uses: ./actions/run-example
with:
workspace_name: '@launchdarkly/hello-openfeature-node-server'
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
"packages/sdk/shopify-oxygen/example",
"packages/sdk/browser/example",
"packages/sdk/browser/example-fdv2",
"packages/sdk/openfeature-node-server"
"packages/sdk/openfeature-node-server",
"packages/sdk/openfeature-node-server/examples/getting-started"
],
"private": true,
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# LaunchDarkly sample OpenFeature provider application for Node.js

We've built a simple console application that demonstrates how to use the
LaunchDarkly OpenFeature provider for Node.js to evaluate flags through the
[OpenFeature](https://openfeature.dev/) interface.

Below, you'll find the build procedure. For more comprehensive instructions, you
can visit your [Quickstart page](https://app.launchdarkly.com/quickstart#/) or
the [Node.js (server-side) reference guide](https://docs.launchdarkly.com/sdk/server-side/node-js).

This demo requires Node.js 20 or higher.

## Build instructions

1. Set the environment variable `LAUNCHDARKLY_SDK_KEY` to your LaunchDarkly SDK
key. If there is an existing boolean feature flag in your LaunchDarkly
project that you want to evaluate, set `LAUNCHDARKLY_FLAG_KEY` to the flag
key; otherwise, a boolean flag of `sample-feature` will be assumed.

```bash
export LAUNCHDARKLY_SDK_KEY="my-sdk-key"
export LAUNCHDARKLY_FLAG_KEY="my-boolean-flag"
```

2. From the example directory, install dependencies and run the example:

```bash
yarn start
```

You should receive the message:

> The {flagKey} feature flag evaluates to {flagValue}.

The application will run continuously and react to flag changes in LaunchDarkly.
Toggle the flag in the LaunchDarkly dashboard to watch the demo re-evaluate live.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { spawnSync } from 'node:child_process';

// The Hello app prints "The <flagKey> feature flag evaluates to <flagValue>." on startup.
// The hello-world-demo boolean flag always returns true, so a successful run must contain this.
const EXPECTED = 'feature flag evaluates to true';

// Force one-shot mode so the continuously-running app exits after the initial evaluation.
const result = spawnSync('node', ['./dist/index.js'], {
encoding: 'utf8',
timeout: 60_000,
env: { ...process.env, CI: '1' },
});

process.stdout.write(result.stdout ?? '');
process.stderr.write(result.stderr ?? '');

if (result.status !== 0) {
console.error(`\n*** e2e failed: app exited with status ${result.status}.`);
process.exit(1);
}

if (!(result.stdout ?? '').includes(EXPECTED)) {
console.error(`\n*** e2e failed: expected output to contain "${EXPECTED}".`);
process.exit(1);
}

console.log(`\n*** e2e passed: output contained "${EXPECTED}".`);
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@launchdarkly/hello-openfeature-node-server",
"version": "0.1.0",
"description": "Hello LaunchDarkly OpenFeature Provider for the Node.js Server-Side SDK",
"private": true,
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"start": "yarn build && node ./dist/index.js",
"test": "node ./e2e/verify.mjs",
"lint": "npx eslint .",
"lint:fix": "yarn run lint --fix",
"check": "yarn lint && yarn build"
},
"keywords": [
"launchdarkly",
"openfeature",
"feature-flags"
],
"author": "LaunchDarkly",
"license": "Apache-2.0",
"dependencies": {
"@launchdarkly/node-server-sdk": "9.11.1",
"@launchdarkly/openfeature-node-server": "1.2.2",
"@openfeature/core": "^1.10.0",
"@openfeature/server-sdk": "^1.16.0"
},
"devDependencies": {
"@eslint/js": "^9.0.0",
"@tsconfig/node20": "20.1.4",
"@types/node": "^25.9.1",
"eslint": "^9.0.0",
"eslint-import-resolver-typescript": "^4.0.0",
"eslint-plugin-import-x": "^4.0.0",
"globals": "^16.0.0",
"prettier": "^3.0.0",
"typescript": "5.1.6",
"typescript-eslint": "^8.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { OpenFeature, ProviderEvents } from '@openfeature/server-sdk';

import { LaunchDarklyProvider } from '@launchdarkly/openfeature-node-server';

// The server-side SDK key is read from the LAUNCHDARKLY_SDK_KEY environment variable.
const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY;

// Set flagKey to the feature flag key you want to evaluate.
const flagKey = process.env.LAUNCHDARKLY_FLAG_KEY || 'sample-feature';

if (!sdkKey) {
console.error(
'*** LaunchDarkly SDK key is required: set the LAUNCHDARKLY_SDK_KEY environment variable and try again.',
);
process.exit(1);
}

if (!flagKey) {
console.error(
"*** LaunchDarkly flag key is required: set the 'flagKey' variable in src/index.ts, or the LAUNCHDARKLY_FLAG_KEY environment variable and try again.",
);
process.exit(1);
}

const BANNER = ` ██
██
████████
███████
██ LAUNCHDARKLY █
███████
████████
██
██
`;

// Set up the evaluation context. This context should appear on your LaunchDarkly contexts dashboard
// soon after you run the demo.
const context = {
kind: 'user',
targetingKey: 'example-user-key',
name: 'Sandy',
};

let lastFlagValue: boolean | null = null;

function printFlagState(flagValue: boolean): void {
if (lastFlagValue === flagValue) {
return;
}
console.log(`*** The '${flagKey}' feature flag evaluates to ${flagValue}.\n`);
if (flagValue) {
console.log(BANNER);
}
lastFlagValue = flagValue;
}

async function main(): Promise<void> {
const provider = new LaunchDarklyProvider(sdkKey!);

try {
await OpenFeature.setProviderAndWait(provider);
console.log('*** SDK successfully initialized!\n');
} catch (error) {
console.error(
`*** SDK failed to initialize. Please check your internet connection and SDK credential for any typo.\n${error}`,
);
process.exit(1);
}

const ofClient = OpenFeature.getClient();
const initialValue = await ofClient.getBooleanValue(flagKey, false, context);
printFlagState(initialValue);

// Subscribe to provider configuration changes so the demo reacts to LaunchDarkly flag updates
// without polling.
ofClient.addHandler(ProviderEvents.ConfigurationChanged, async (eventDetails) => {
if (eventDetails?.flagsChanged?.includes(flagKey)) {
const updatedValue = await ofClient.getBooleanValue(flagKey, false, context);
printFlagState(updatedValue);
}
});

if (process.env.CI !== undefined) {
process.exit(0);
}
}

main().catch((error) => {
console.error(`*** Unhandled error: ${error}`);
process.exit(1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"declaration": true,
"sourceMap": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false
},
"include": ["src"],
"exclude": ["dist", "node_modules"]
}
14 changes: 13 additions & 1 deletion release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@
"type": "json",
"path": "/packages/sdk/server-ai/examples/agent-graph-traversal/package.json",
"jsonpath": "$.dependencies['@launchdarkly/node-server-sdk']"
},
{
"type": "json",
"path": "/packages/sdk/openfeature-node-server/examples/getting-started/package.json",
"jsonpath": "$.dependencies['@launchdarkly/node-server-sdk']"
}
]
},
Expand Down Expand Up @@ -347,7 +352,14 @@
},
"packages/sdk/openfeature-node-server": {
"bump-minor-pre-major": true,
"extra-files": ["src/LaunchDarklyProvider.ts"]
"extra-files": [
"src/LaunchDarklyProvider.ts",
{
"type": "json",
"path": "/packages/sdk/openfeature-node-server/examples/getting-started/package.json",
"jsonpath": "$.dependencies['@launchdarkly/openfeature-node-server']"
}
]
}
},
"plugins": [
Expand Down
Loading