Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .github/actions/publish-docs/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Publish Documentation
description: 'Publish documentation to github pages.'

inputs:
github_token:
description: 'The github token to use for committing'
required: true

runs:
using: composite
steps:
- uses: launchdarkly/gh-actions/actions/publish-pages@publish-pages-v1.0.2
name: 'Publish to Github pages'
with:
docs_path: docs
github_token: ${{ inputs.github_token }}
20 changes: 20 additions & 0 deletions .github/actions/publish-npm/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Publish to NPM
description: Publish an npm package.
inputs:
prerelease:
description: 'Is this a prerelease. If so, then the latest tag will not be updated in npm.'
required: false
dry-run:
description: 'Is this a dry run. If so no package will be published.'
required: false

runs:
using: composite
steps:
- name: Publish
shell: bash
run: |
./scripts/publish-npm.sh
env:
LD_RELEASE_IS_PRERELEASE: ${{ inputs.prerelease }}
LD_RELEASE_IS_DRYRUN: ${{ inputs.dry-run }}
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
matrix:
variations: [
{os: ubuntu-latest, node: latest},
{os: ubuntu-latest, node: 16},
{os: ubuntu-latest, node: 18},
{os: windows-latest, node: latest}
]

Expand All @@ -29,12 +29,12 @@ jobs:
node-version: ${{ matrix.variations.node }}
registry-url: 'https://registry.npmjs.org'
- name: Install
run: yarn
run: npm install
- name: Build
run: yarn run build
run: npm run build
- name: Test
run: yarn test
run: npm test
env:
JEST_JUNIT_OUTPUT_FILE: "reports/junit/js-test-results.xml"
- name: Lint
run: yarn run lint
run: npm run lint
58 changes: 58 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Release Please

on:
push:
branches:
- main

jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
steps:
- uses: googleapis/release-please-action@v4
id: release
with:
token: ${{secrets.GITHUB_TOKEN}}

publish-package:
runs-on: ubuntu-latest
needs: ['release-please']
permissions:
id-token: write
contents: write
if: ${{ needs.release-please.outputs.release_created == 'true' }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20.x
registry-url: 'https://registry.npmjs.org'

- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.0.1
name: 'Get NPM token'
with:
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
ssm_parameter_pairs: '/production/common/releasing/npm/token = NODE_AUTH_TOKEN'

- name: Install Dependencies
run: npm install
# Publishing will build because we have a prepublish script.

- id: publish-npm
name: Publish NPM Package
uses: ./.github/actions/publish-npm
with:
dry-run: 'false'
prerelease: 'false'

- name: Build Documentation
run: npm run doc

- id: publish-docs
name: Publish Documentation
uses: ./.github/actions/publish-docs
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
23 changes: 0 additions & 23 deletions .ldrelease/config.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.5.1"
}
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This provider is a beta version and should not be considered ready for productio

## Supported Node versions

This version of the LaunchDarkly OpenFeature provider is compatible with Node.js versions 16 and above.
This version of the LaunchDarkly OpenFeature provider is compatible with Node.js versions 18 and above.

## Getting started

Expand All @@ -31,12 +31,20 @@ npm install @launchdarkly/openfeature-node-server
import { OpenFeature } from '@openfeature/server-sdk';
import { LaunchDarklyProvider } from '@launchdarkly/openfeature-node-server';

// The LaunchDarkly provider will use a 10 second timeout by default when waiting for the SDK
// to initialize. This can be controlled by the optional third parameter to the LaunchDarklyProvider
// constructor.
const ldProvider = new LaunchDarklyProvider('<your-sdk-key>', {/* LDOptions here */});

OpenFeature.setProvider(ldProvider);

// Alternatively await OpenFeature.setProviderAndWait(ldProvider); can be used.
// This eliminated the need to listen for the ready event, but the user should be careful to handle
// any exceptions that are thrown.

// If you need access to the LDClient, then you can use ldProvider.getClient()

// Evaluations before the provider indicates it is ready may get default values with a
// Evaluations before the provider indicates it is ready may get default values with a
// CLIENT_NOT_READY reason.
OpenFeature.addHandler(ProviderEvents.Ready, (eventDetails) => {
const client = OpenFeature.getClient();
Expand Down Expand Up @@ -71,7 +79,7 @@ The `kind` attribute should be a string containing only contain ASCII letters, n

The OpenFeature specification allows for an optional targeting key, but LaunchDarkly requires a key for evaluation. A targeting key must be specified for each context being evaluated. It may be specified using either `targetingKey`, as it is in the OpenFeature specification, or `key`, which is the typical LaunchDarkly identifier for the targeting key. If a `targetingKey` and a `key` are specified, then the `targetingKey` will take precedence.

There are several other attributes which have special functionality within a single or multi-context.
There are several other attributes which have special functionality within a single or multi-context.
- A key of `privateAttributes`. Must be an array of string values. [Equivalent to '_meta.privateAttributes' in the SDK.](https://launchdarkly.github.io/node-server-sdk/interfaces/_launchdarkly_node_server_sdk_.LDContextMeta.html#privateAttributes)
- A key of `anonymous`. Must be a boolean value. [Equivalent to 'anonymous' in the SDK.](https://launchdarkly.github.io/node-server-sdk/interfaces/_launchdarkly_node_server_sdk_.LDSingleKindContext.html#anonymous)
- A key of `name`. Must be a string. [Equivalent to 'name' in the SDK.](https://launchdarkly.github.io/node-server-sdk/interfaces/_launchdarkly_node_server_sdk_.LDSingleKindContext.html#name)
Expand Down
18 changes: 12 additions & 6 deletions __tests__/LaunchDarklyProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ it('can be initialized', async () => {
const ldProvider = new LaunchDarklyProvider('sdk-key', { offline: true });
await ldProvider.initialize({});

expect(ldProvider.status).toEqual(ProviderStatus.READY);
await OpenFeature.setProviderAndWait(ldProvider);

const ofClient = OpenFeature.getClient();

expect(ofClient.providerStatus).toEqual(ProviderStatus.READY);
await ldProvider.onClose();
});

Expand All @@ -30,15 +34,17 @@ it('can fail to initialize client', async () => {
start: () => {
setTimeout(() => errorHandler?.({ code: 401 } as any), 20);
},
close: () => {},
}),
sendEvents: false,
});
try {
await ldProvider.initialize({});
await OpenFeature.setProviderAndWait(ldProvider);
} catch (e) {
expect((e as Error).message).toEqual('Authentication failed. Double check your SDK key.');
}
expect(ldProvider.status).toEqual(ProviderStatus.ERROR);
const ofClient = OpenFeature.getClient();
expect(ofClient.providerStatus).toEqual(ProviderStatus.ERROR);
});

it('emits events for flag changes', async () => {
Expand Down Expand Up @@ -66,10 +72,10 @@ describe('given a mock LaunchDarkly client', () => {
let ldProvider: LaunchDarklyProvider;
const logger: TestLogger = new TestLogger();

beforeEach(() => {
beforeEach(async () => {
ldProvider = new LaunchDarklyProvider('sdk-key', { logger, offline: true });
ldClient = ldProvider.getClient();
OpenFeature.setProvider(ldProvider);
await OpenFeature.setProviderAndWait(ldProvider);

ofClient = OpenFeature.getClient();
logger.reset();
Expand Down Expand Up @@ -283,7 +289,7 @@ describe('given a mock LaunchDarkly client', () => {
errorKind: ldError,
},
}));
const res = await ofClient.getObjectDetails(testFlagKey, {}, basicContext);
const res = await ofClient.getObjectDetails(testFlagKey, { yes: 'no' }, basicContext);
expect(res).toMatchObject({
flagKey: testFlagKey,
value: { yes: 'no' },
Expand Down
12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"lint:fix": "npm run lint -- --fix",
"test": "jest",
"coverage": "npm run test -- --coverage",
"prepublishOnly": "npm run build"
"prepublishOnly": "npm run build",
"doc": "typedoc"
},
"keywords": [
"launchdarkly",
Expand All @@ -20,11 +21,12 @@
],
"license": "Apache-2.0",
"peerDependencies": {
"@openfeature/server-sdk": "^1.6.3",
"@launchdarkly/node-server-sdk": "9.x"
"@launchdarkly/node-server-sdk": "9.x",
"@openfeature/server-sdk": "^1.14.0"
},
"devDependencies": {
"@openfeature/server-sdk": "^1.6.3",
"@launchdarkly/node-server-sdk": "9.x",
"@openfeature/server-sdk": "^1.14.0",
"@types/jest": "^27.4.1",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
Expand All @@ -34,8 +36,8 @@
"eslint-plugin-import": "^2.26.0",
"jest": "^27.5.1",
"jest-junit": "^14.0.1",
"@launchdarkly/node-server-sdk": "9.x",
"ts-jest": "^27.1.4",
"typedoc": "^0.25.13",
"typescript": "^4.7.4"
}
}
9 changes: 9 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"packages": {
"bootstrap-sha": "6b38e5a3ddb23705fbaec8770e715b4ef5827223",
".": {
"release-type": "node",
"bump-minor-pre-major": true
}
}
}
11 changes: 11 additions & 0 deletions scripts/publish-npm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
if $LD_RELEASE_IS_DRYRUN ; then
echo "Doing a dry run of publishing."
else
if $LD_RELEASE_IS_PRERELEASE ; then
echo "Publishing with prerelease tag."
npm publish --tag prerelease --provenance --access public || { echo "npm publish failed" >&2; exit 1; }
else
npm publish --provenance --access public || { echo "npm publish failed" >&2; exit 1; }
fi
fi
31 changes: 10 additions & 21 deletions src/LaunchDarklyProvider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
ErrorCode,
EvaluationContext, FlagValue, Hook,
EvaluationContext, Hook,
JsonValue,
OpenFeatureEventEmitter,
Paradigm,
Provider,
ProviderEvents,
ProviderMetadata,
ProviderStatus,
ResolutionDetails,
StandardResolutionReasons,
} from '@openfeature/server-sdk';
Expand Down Expand Up @@ -45,22 +45,18 @@ export default class LaunchDarklyProvider implements Provider {
name: 'launchdarkly-node-provider',
};

private innerStatus: ProviderStatus = ProviderStatus.NOT_READY;
readonly runsOn?: Paradigm = 'server';

public readonly events = new OpenFeatureEventEmitter();

/**
* Get the status of the LaunchDarkly provider.
*/
public get status() {
return this.innerStatus;
}

/**
* Construct a {@link LaunchDarklyProvider}.
* @param client The LaunchDarkly client instance to use.
* @param sdkKey The SDK key.
* @param options Any options for the SDK.
* @param initTimeoutSeconds The default amount of time to wait for initialization in seconds.
* Defaults to 10 seconds.
*/
constructor(sdkKey: string, options: LDOptions = {}) {
constructor(sdkKey: string, options: LDOptions = {}, private initTimeoutSeconds: number = 10) {
if (options.logger) {
this.logger = new SafeLogger(options.logger, basicLogger({ level: 'info' }));
} else {
Expand All @@ -78,7 +74,6 @@ export default class LaunchDarklyProvider implements Provider {
} catch (e) {
this.clientConstructionError = e;
this.logger.error(`Encountered unrecoverable initialization error, ${e}`);
this.innerStatus = ProviderStatus.ERROR;
}
}

Expand All @@ -91,13 +86,7 @@ export default class LaunchDarklyProvider implements Provider {
}
throw new Error('Unknown problem encountered during initialization');
}
try {
await this.client.waitForInitialization();
this.innerStatus = ProviderStatus.READY;
} catch (e) {
this.innerStatus = ProviderStatus.ERROR;
throw e;
}
await this.client.waitForInitialization({ timeout: this.initTimeoutSeconds });
}

/**
Expand Down Expand Up @@ -215,7 +204,7 @@ export default class LaunchDarklyProvider implements Provider {
}

// eslint-disable-next-line class-methods-use-this
get hooks(): Hook<FlagValue>[] {
get hooks(): Hook[] {
return [];
}

Expand Down
7 changes: 3 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
{
"include": ["src/**/*"],
"compilerOptions": {
"module": "commonjs",
"module": "ES2022",
"esModuleInterop": true,
"target": "es6",
"target": "es2022",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"declarationMap": true // enables importers to jump to source
},
"lib": ["es2015"]
"lib": ["ES2022"]
}

Loading