Skip to content

Commit 4248319

Browse files
committed
chore(OF-node-server): adding example application
1 parent 99a96d2 commit 4248319

9 files changed

Lines changed: 262 additions & 2 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: sdk/openfeature-node-server nightly
2+
3+
on:
4+
schedule:
5+
- cron: '0 1 * * *'
6+
7+
jobs:
8+
run-getting-started-example:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
id-token: write
12+
steps:
13+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
14+
- uses: ./actions/setup-yarn
15+
with:
16+
node-version: 20
17+
- uses: ./actions/run-example
18+
with:
19+
workspace_name: '@launchdarkly/hello-openfeature-node-server'
20+
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}

.github/workflows/openfeature-node-server.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,17 @@ jobs:
3232
workspace_name: '@launchdarkly/openfeature-node-server'
3333
workspace_path: packages/sdk/openfeature-node-server
3434
# TODO: Add contract tests
35+
36+
run-getting-started-example:
37+
runs-on: ubuntu-latest
38+
permissions:
39+
id-token: write
40+
steps:
41+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
42+
- uses: ./actions/setup-yarn
43+
with:
44+
node-version: 20
45+
- uses: ./actions/run-example
46+
with:
47+
workspace_name: '@launchdarkly/hello-openfeature-node-server'
48+
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@
6060
"packages/sdk/shopify-oxygen/example",
6161
"packages/sdk/browser/example",
6262
"packages/sdk/browser/example-fdv2",
63-
"packages/sdk/openfeature-node-server"
63+
"packages/sdk/openfeature-node-server",
64+
"packages/sdk/openfeature-node-server/examples/getting-started"
6465
],
6566
"private": true,
6667
"scripts": {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# LaunchDarkly sample OpenFeature provider application for Node.js
2+
3+
We've built a simple console application that demonstrates how to use the
4+
LaunchDarkly OpenFeature provider for Node.js to evaluate flags through the
5+
[OpenFeature](https://openfeature.dev/) interface.
6+
7+
Below, you'll find the build procedure. For more comprehensive instructions, you
8+
can visit your [Quickstart page](https://app.launchdarkly.com/quickstart#/) or
9+
the [Node.js (server-side) reference guide](https://docs.launchdarkly.com/sdk/server-side/node-js).
10+
11+
This demo requires Node.js 20 or higher.
12+
13+
## Build instructions
14+
15+
1. Set the environment variable `LAUNCHDARKLY_SDK_KEY` to your LaunchDarkly SDK
16+
key. If there is an existing boolean feature flag in your LaunchDarkly
17+
project that you want to evaluate, set `LAUNCHDARKLY_FLAG_KEY` to the flag
18+
key; otherwise, a boolean flag of `sample-feature` will be assumed.
19+
20+
```bash
21+
export LAUNCHDARKLY_SDK_KEY="my-sdk-key"
22+
export LAUNCHDARKLY_FLAG_KEY="my-boolean-flag"
23+
```
24+
25+
2. From the example directory, install dependencies and run the example:
26+
27+
```bash
28+
yarn start
29+
```
30+
31+
You should receive the message:
32+
33+
> The {flagKey} feature flag evaluates to {flagValue}.
34+
35+
The application will run continuously and react to flag changes in LaunchDarkly.
36+
Toggle the flag in the LaunchDarkly dashboard to watch the demo re-evaluate live.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { spawnSync } from 'node:child_process';
2+
3+
// The Hello app prints "The <flagKey> feature flag evaluates to <flagValue>." on startup.
4+
// The hello-world-demo boolean flag always returns true, so a successful run must contain this.
5+
const EXPECTED = 'feature flag evaluates to true';
6+
7+
// Force one-shot mode so the continuously-running app exits after the initial evaluation.
8+
const result = spawnSync('node', ['./dist/index.js'], {
9+
encoding: 'utf8',
10+
timeout: 60_000,
11+
env: { ...process.env, CI: '1' },
12+
});
13+
14+
process.stdout.write(result.stdout ?? '');
15+
process.stderr.write(result.stderr ?? '');
16+
17+
if (result.status !== 0) {
18+
console.error(`\n*** e2e failed: app exited with status ${result.status}.`);
19+
process.exit(1);
20+
}
21+
22+
if (!(result.stdout ?? '').includes(EXPECTED)) {
23+
console.error(`\n*** e2e failed: expected output to contain "${EXPECTED}".`);
24+
process.exit(1);
25+
}
26+
27+
console.log(`\n*** e2e passed: output contained "${EXPECTED}".`);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "@launchdarkly/hello-openfeature-node-server",
3+
"version": "0.1.0",
4+
"description": "Hello LaunchDarkly OpenFeature Provider for the Node.js Server-Side SDK",
5+
"private": true,
6+
"type": "module",
7+
"main": "dist/index.js",
8+
"types": "dist/index.d.ts",
9+
"scripts": {
10+
"build": "tsc",
11+
"start": "yarn build && node ./dist/index.js",
12+
"test": "node ./e2e.mjs",
13+
"lint": "npx eslint . --ext .ts",
14+
"lint:fix": "yarn run lint --fix",
15+
"check": "yarn lint && yarn build"
16+
},
17+
"keywords": [
18+
"launchdarkly",
19+
"openfeature",
20+
"feature-flags"
21+
],
22+
"author": "LaunchDarkly",
23+
"license": "Apache-2.0",
24+
"dependencies": {
25+
"@launchdarkly/node-server-sdk": "9.10.14",
26+
"@launchdarkly/openfeature-node-server": "1.2.0",
27+
"@openfeature/core": "^1.10.0",
28+
"@openfeature/server-sdk": "^1.16.0"
29+
},
30+
"devDependencies": {
31+
"@tsconfig/node20": "20.1.4",
32+
"@types/node": "^25.9.1",
33+
"@typescript-eslint/eslint-plugin": "^6.20.0",
34+
"@typescript-eslint/parser": "^6.20.0",
35+
"eslint": "^8.45.0",
36+
"eslint-config-prettier": "^8.8.0",
37+
"eslint-plugin-import": "^2.27.5",
38+
"eslint-plugin-prettier": "^5.0.0",
39+
"prettier": "^3.0.0",
40+
"typescript": "5.1.6"
41+
}
42+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* eslint-disable no-console */
2+
import { OpenFeature, ProviderEvents } from '@openfeature/server-sdk';
3+
4+
import { LaunchDarklyProvider } from '@launchdarkly/openfeature-node-server';
5+
6+
// The server-side SDK key is read from the LAUNCHDARKLY_SDK_KEY environment variable.
7+
const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY;
8+
9+
// Set flagKey to the feature flag key you want to evaluate.
10+
const flagKey = process.env.LAUNCHDARKLY_FLAG_KEY || 'sample-feature';
11+
12+
if (!sdkKey) {
13+
console.error(
14+
'*** LaunchDarkly SDK key is required: set the LAUNCHDARKLY_SDK_KEY environment variable and try again.',
15+
);
16+
process.exit(1);
17+
}
18+
19+
if (!flagKey) {
20+
console.error(
21+
"*** LaunchDarkly flag key is required: set the 'flagKey' variable in src/index.ts, or the LAUNCHDARKLY_FLAG_KEY environment variable and try again.",
22+
);
23+
process.exit(1);
24+
}
25+
26+
const BANNER = ` ██
27+
██
28+
████████
29+
███████
30+
██ LAUNCHDARKLY █
31+
███████
32+
████████
33+
██
34+
██
35+
`;
36+
37+
// Set up the evaluation context. This context should appear on your LaunchDarkly contexts dashboard
38+
// soon after you run the demo.
39+
const context = {
40+
kind: 'user',
41+
targetingKey: 'example-user-key',
42+
name: 'Sandy',
43+
};
44+
45+
let lastFlagValue: boolean | null = null;
46+
47+
function printFlagState(flagValue: boolean): void {
48+
if (lastFlagValue === flagValue) {
49+
return;
50+
}
51+
console.log(`*** The '${flagKey}' feature flag evaluates to ${flagValue}.\n`);
52+
if (flagValue) {
53+
console.log(BANNER);
54+
}
55+
lastFlagValue = flagValue;
56+
}
57+
58+
async function main(): Promise<void> {
59+
const provider = new LaunchDarklyProvider(sdkKey!);
60+
61+
try {
62+
await OpenFeature.setProviderAndWait(provider);
63+
console.log('*** SDK successfully initialized!\n');
64+
} catch (error) {
65+
console.error(
66+
`*** SDK failed to initialize. Please check your internet connection and SDK credential for any typo.\n${error}`,
67+
);
68+
process.exit(1);
69+
}
70+
71+
const ofClient = OpenFeature.getClient();
72+
const initialValue = await ofClient.getBooleanValue(flagKey, false, context);
73+
printFlagState(initialValue);
74+
75+
// Subscribe to provider configuration changes so the demo reacts to LaunchDarkly flag updates
76+
// without polling.
77+
ofClient.addHandler(ProviderEvents.ConfigurationChanged, async (eventDetails) => {
78+
if (eventDetails?.flagsChanged?.includes(flagKey)) {
79+
const updatedValue = await ofClient.getBooleanValue(flagKey, false, context);
80+
printFlagState(updatedValue);
81+
}
82+
});
83+
84+
if (process.env.CI !== undefined) {
85+
process.exit(0);
86+
}
87+
}
88+
89+
main().catch((error) => {
90+
console.error(`*** Unhandled error: ${error}`);
91+
process.exit(1);
92+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"extends": "@tsconfig/node20/tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"declaration": true,
6+
"sourceMap": true,
7+
"noUncheckedIndexedAccess": true,
8+
"noUnusedLocals": true,
9+
"noUnusedParameters": true,
10+
"noFallthroughCasesInSwitch": true,
11+
"allowUnreachableCode": false,
12+
"allowUnusedLabels": false
13+
},
14+
"include": ["src"],
15+
"exclude": ["dist", "node_modules"]
16+
}

release-please-config.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@
209209
"type": "json",
210210
"path": "/packages/sdk/server-ai/examples/agent-graph-traversal/package.json",
211211
"jsonpath": "$.dependencies['@launchdarkly/node-server-sdk']"
212+
},
213+
{
214+
"type": "json",
215+
"path": "/packages/sdk/openfeature-node-server/examples/getting-started/package.json",
216+
"jsonpath": "$.dependencies['@launchdarkly/node-server-sdk']"
212217
}
213218
]
214219
},
@@ -347,7 +352,14 @@
347352
},
348353
"packages/sdk/openfeature-node-server": {
349354
"bump-minor-pre-major": true,
350-
"extra-files": ["src/LaunchDarklyProvider.ts"]
355+
"extra-files": [
356+
"src/LaunchDarklyProvider.ts",
357+
{
358+
"type": "json",
359+
"path": "/packages/sdk/openfeature-node-server/examples/getting-started/package.json",
360+
"jsonpath": "$.dependencies['@launchdarkly/openfeature-node-server']"
361+
}
362+
]
351363
}
352364
},
353365
"plugins": [

0 commit comments

Comments
 (0)