diff --git a/.gitignore b/.gitignore index 5f456b045f..8254734ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ stats.html .env.local .env.*.local .claude/worktrees +.claude/stacks .mcp.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ed4eea95a1..8b1c550648 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -23,5 +23,6 @@ "packages/telemetry/node-server-sdk-otel": "1.3.13", "packages/tooling/jest": "1.0.14", "packages/sdk/shopify-oxygen": "0.1.10", - "packages/sdk/react": "4.0.0" + "packages/sdk/react": "4.0.0", + "packages/sdk/openfeature-node-server": "1.2.0" } diff --git a/package.json b/package.json index 0cfc4ff14e..6dca66da2f 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,8 @@ "packages/sdk/shopify-oxygen/contract-tests", "packages/sdk/shopify-oxygen/example", "packages/sdk/browser/example", - "packages/sdk/browser/example-fdv2" + "packages/sdk/browser/example-fdv2", + "packages/sdk/openfeature-node-server" ], "private": true, "scripts": { diff --git a/packages/sdk/openfeature-node-server/LICENSE b/packages/sdk/openfeature-node-server/LICENSE new file mode 100644 index 0000000000..e5c2b6985c --- /dev/null +++ b/packages/sdk/openfeature-node-server/LICENSE @@ -0,0 +1,13 @@ +Copyright 2025 Catamorphic, Co. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/sdk/openfeature-node-server/README.md b/packages/sdk/openfeature-node-server/README.md new file mode 100644 index 0000000000..3018d9d9f3 --- /dev/null +++ b/packages/sdk/openfeature-node-server/README.md @@ -0,0 +1,64 @@ +# LaunchDarkly OpenFeature Provider for the Node.js Server-Side SDK + + + +> [!CAUTION] +> This SDK is experimental and should NOT be considered ready for production use. +> It may change or be removed without notice and is not subject to backwards +> compatibility guarantees. + +This package provides an [OpenFeature](https://openfeature.dev/) provider that wraps the [LaunchDarkly Server-Side SDK for Node.js](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/server-node). + +## Installation + +```bash +npm install @openfeature/server-sdk @launchdarkly/node-server-sdk @launchdarkly/openfeature-node-server +``` + +## Usage + +```typescript +import { OpenFeature } from '@openfeature/server-sdk'; +import { LaunchDarklyProvider } from '@launchdarkly/openfeature-node-server'; + +const provider = new LaunchDarklyProvider('your-sdk-key'); +await OpenFeature.setProviderAndWait(provider); + +const client = OpenFeature.getClient(); +const flagValue = await client.getBooleanValue('flag-key', false, { + targetingKey: 'user-key', +}); +``` + +## Contributing + +See [Contributing](../../../CONTRIBUTING.md). + +## Verifying SDK build provenance with the SLSA framework + +LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md). + +## About LaunchDarkly + +- LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can: + - Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases. + - Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?). + - Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file. + - Grant access to certain features based on user attributes, like payment plan (eg: users on the 'gold' plan get access to more features than users in the 'silver' plan). + - Disable parts of your application to facilitate maintenance, without taking everything offline. +- LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out [our documentation](https://docs.launchdarkly.com/sdk) for a complete list. +- Explore LaunchDarkly + - [launchdarkly.com](https://www.launchdarkly.com/ 'LaunchDarkly Main Website') for more information + - [docs.launchdarkly.com](https://docs.launchdarkly.com/ 'LaunchDarkly Documentation') for our documentation and SDK reference guides + - [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ 'LaunchDarkly API Documentation') for our API documentation + - [blog.launchdarkly.com](https://blog.launchdarkly.com/ 'LaunchDarkly Blog Documentation') for the latest product updates + + diff --git a/packages/sdk/openfeature-node-server/__tests__/scaffold.test.ts b/packages/sdk/openfeature-node-server/__tests__/scaffold.test.ts new file mode 100644 index 0000000000..b94ef329da --- /dev/null +++ b/packages/sdk/openfeature-node-server/__tests__/scaffold.test.ts @@ -0,0 +1,3 @@ +it('package scaffolding builds and registers in the workspace', () => { + expect(true).toEqual(true); +}); diff --git a/packages/sdk/openfeature-node-server/jest.config.mjs b/packages/sdk/openfeature-node-server/jest.config.mjs new file mode 100644 index 0000000000..cd70e25844 --- /dev/null +++ b/packages/sdk/openfeature-node-server/jest.config.mjs @@ -0,0 +1,7 @@ +export default { + transform: { '^.+\\.ts?$': 'ts-jest' }, + testMatch: ['**/__tests__/**/*test.ts?(x)'], + testEnvironment: 'node', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + collectCoverageFrom: ['src/**/*.ts'], +}; diff --git a/packages/sdk/openfeature-node-server/package.json b/packages/sdk/openfeature-node-server/package.json new file mode 100644 index 0000000000..9481cdbe89 --- /dev/null +++ b/packages/sdk/openfeature-node-server/package.json @@ -0,0 +1,72 @@ +{ + "name": "@launchdarkly/openfeature-node-server", + "version": "1.2.0", + "description": "LaunchDarkly OpenFeature provider for the Node.js server SDK", + "homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/openfeature-node-server", + "repository": { + "type": "git", + "url": "https://github.com/launchdarkly/js-core.git" + }, + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/src/index.d.ts", + "type": "module", + "exports": { + ".": { + "require": { + "types": "./dist/src/index.d.ts", + "default": "./dist/index.cjs" + }, + "import": { + "types": "./dist/src/index.d.ts", + "default": "./dist/index.js" + } + } + }, + "engines": { + "node": ">=20" + }, + "scripts": { + "build": "tsup-node && npx tsc --emitDeclarationOnly --declaration --outDir dist", + "lint": "npx eslint . --ext .ts", + "lint:fix": "yarn run lint --fix", + "test": "jest", + "check": "yarn lint && yarn build && yarn test" + }, + "files": [ + "dist" + ], + "keywords": [ + "launchdarkly", + "openfeature", + "feature-flags" + ], + "author": "LaunchDarkly", + "license": "Apache-2.0", + "dependencies": { + "@launchdarkly/js-sdk-common": "2.24.3", + "@launchdarkly/openfeature-js-server-common": "0.1.0" + }, + "peerDependencies": { + "@launchdarkly/node-server-sdk": "9.x", + "@openfeature/server-sdk": "^1.16.0" + }, + "devDependencies": { + "@launchdarkly/node-server-sdk": "9.10.12", + "@openfeature/core": "^1.10.0", + "@openfeature/server-sdk": "^1.16.0", + "@types/jest": "^29.5.3", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", + "eslint": "^8.45.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.6.3", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.6.1", + "prettier": "^3.0.0", + "ts-jest": "^29.1.1", + "tsup": "^8.5.1", + "typescript": "5.1.6" + } +} diff --git a/packages/sdk/openfeature-node-server/src/index.ts b/packages/sdk/openfeature-node-server/src/index.ts new file mode 100644 index 0000000000..04981f02fc --- /dev/null +++ b/packages/sdk/openfeature-node-server/src/index.ts @@ -0,0 +1,9 @@ +/** + * This is the API reference for the LaunchDarkly OpenFeature provider for the + * Node.js server SDK. + * + * @module @launchdarkly/openfeature-node-server + */ + +// Provider implementation lands in a follow-up slice. +export {}; diff --git a/packages/sdk/openfeature-node-server/tsconfig.json b/packages/sdk/openfeature-node-server/tsconfig.json new file mode 100644 index 0000000000..05fb0d32ec --- /dev/null +++ b/packages/sdk/openfeature-node-server/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "rootDir": ".", + "outDir": "dist", + "target": "es2020", + "lib": ["es2020"], + "module": "ESNext", + "strict": true, + "noImplicitOverride": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "resolveJsonModule": true, + "stripInternal": true, + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["**/*.test.ts", "dist", "node_modules", "__tests__"] +} diff --git a/packages/sdk/openfeature-node-server/tsconfig.ref.json b/packages/sdk/openfeature-node-server/tsconfig.ref.json new file mode 100644 index 0000000000..34a1cb607a --- /dev/null +++ b/packages/sdk/openfeature-node-server/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*", "package.json"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sdk/openfeature-node-server/tsup.config.ts b/packages/sdk/openfeature-node-server/tsup.config.ts new file mode 100644 index 0000000000..615e8c638f --- /dev/null +++ b/packages/sdk/openfeature-node-server/tsup.config.ts @@ -0,0 +1,14 @@ +// It is a dev dependency and the linter doesn't understand. +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { + index: 'src/index.ts', + }, + format: ['esm', 'cjs'], + splitting: false, + sourcemap: true, + clean: true, + dts: false, +}); diff --git a/packages/shared/openfeature-server-common/src/index.ts b/packages/shared/openfeature-server-common/src/index.ts index ff00dd144a..626cf55d39 100644 --- a/packages/shared/openfeature-server-common/src/index.ts +++ b/packages/shared/openfeature-server-common/src/index.ts @@ -8,3 +8,4 @@ export { BaseOpenFeatureProvider } from './BaseOpenFeatureProvider'; export type { BaseProviderConfig } from './BaseOpenFeatureProvider'; export type { OpenFeatureLDClientContract } from './OpenFeatureLDClientContract'; +export { translateContext } from './translateContext'; diff --git a/release-please-config.json b/release-please-config.json index f7ed3327a0..5d7839b68c 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -303,6 +303,9 @@ "packages/shared/openfeature-server-common": { "bump-minor-pre-major": true, "prerelease": true + }, + "packages/sdk/openfeature-node-server": { + "bump-minor-pre-major": true } }, "plugins": [ diff --git a/tsconfig.json b/tsconfig.json index cc80831d10..f1e7ed1dd7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -96,6 +96,9 @@ }, { "path": "./packages/sdk/react/tsconfig.ref.json" + }, + { + "path": "./packages/sdk/openfeature-node-server/tsconfig.ref.json" } ] }