-
Notifications
You must be signed in to change notification settings - Fork 342
feat: add official PostHog OpenFeature server provider (Node) #3994
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
Changes from all commits
92704a5
c2ad670
8b61b03
9f8a7fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@posthog/openfeature-node-provider': minor | ||
| --- | ||
|
|
||
| Initial release of the official PostHog provider for the OpenFeature server SDK, backed by `posthog-node`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| /node_modules | ||
| .DS_Store | ||
| .env | ||
| .env.local | ||
| *.log |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Minimum age (in days) before a package version can be installed | ||
| min-release-age=7 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # example-openfeature-node | ||
|
|
||
| Minimal Node example of the **[`@posthog/openfeature-node-provider`](../../packages/openfeature-node-provider)** — evaluating PostHog feature flags through the standard [OpenFeature](https://openfeature.dev) server SDK, backed by `posthog-node`. | ||
|
|
||
| The browser equivalent lives in [`example-openfeature-web`](../example-openfeature-web) (see `@posthog/openfeature-web-provider`). | ||
|
|
||
| ## What it shows | ||
|
|
||
| - Constructing a `posthog-node` client (you own its lifecycle; call `shutdown()` on exit). | ||
| - Registering `PostHogServerProvider` with `OpenFeature.setProviderAndWait(...)`. | ||
| - Async flag evaluation via the vendor-neutral OpenFeature client | ||
| (`getBooleanValue` / `getStringValue` / `getObjectValue`), with the distinct id | ||
| supplied per-call through the evaluation context's `targetingKey`. | ||
|
|
||
| See [`index.ts`](./index.ts). | ||
|
|
||
| ## Run it | ||
|
|
||
| These examples install workspace packages as tarballs (see [`../README.md`](../README.md)). | ||
|
|
||
| 1. From the repo root, build the tarballs: | ||
| ```bash | ||
| pnpm package:watch | ||
| ``` | ||
| 2. In this folder, install and run: | ||
| ```bash | ||
| pnpm install | ||
| POSTHOG_PROJECT_API_KEY=<ph_project_api_key> pnpm start | ||
| ``` | ||
| Use real flag keys from your project. | ||
|
|
||
| > The lockfile is generated on first `pnpm install` per the examples tarball workflow. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { OpenFeature } from '@openfeature/server-sdk' | ||
| import { PostHogServerProvider } from '@posthog/openfeature-node-provider' | ||
| import { PostHog } from 'posthog-node' | ||
|
|
||
| // Configure via env vars or edit inline. | ||
| const PROJECT_API_KEY = process.env.POSTHOG_PROJECT_API_KEY ?? '<ph_project_api_key>' | ||
| const HOST = process.env.POSTHOG_HOST ?? 'https://us.i.posthog.com' | ||
|
|
||
| async function main(): Promise<void> { | ||
| // 1. You own the posthog-node lifecycle. Pass a personalApiKey to enable | ||
| // local evaluation. | ||
| const posthog = new PostHog(PROJECT_API_KEY, { host: HOST }) | ||
|
|
||
| // 2. Register the PostHog provider with OpenFeature. | ||
| await OpenFeature.setProviderAndWait(new PostHogServerProvider(posthog)) | ||
| const client = OpenFeature.getClient() | ||
|
|
||
| // 3. Evaluate flags through the vendor-neutral OpenFeature API. The distinct | ||
| // id comes from the evaluation context's targetingKey; other attributes | ||
| // map to person/group properties. Swap these keys for real flags. | ||
| const context = { targetingKey: 'user_distinct_id', plan: 'enterprise' } | ||
| // The reads are independent, so evaluate them concurrently. | ||
| const [boolean, multivariate, payload] = await Promise.all([ | ||
| client.getBooleanValue('my-boolean-flag', false, context), | ||
| client.getStringValue('my-multivariate-flag', 'control', context), | ||
| client.getObjectValue('my-payload-flag', {}, context), | ||
| ]) | ||
| const result = { | ||
| 'my-boolean-flag': boolean, | ||
| 'my-multivariate-flag': multivariate, | ||
| 'my-payload-flag': payload, | ||
| } | ||
|
|
||
| // eslint-disable-next-line no-console | ||
| console.log(result) | ||
|
|
||
| await posthog.shutdown() | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| // eslint-disable-next-line no-console | ||
| console.error('OpenFeature example failed:', err) | ||
| process.exit(1) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "name": "example-openfeature-node", | ||
| "version": "1.0.0", | ||
| "private": true, | ||
| "engines": { | ||
| "node": ">=22.22" | ||
| }, | ||
| "packageManager": "pnpm@10.15.0", | ||
| "license": "MIT", | ||
| "scripts": { | ||
| "start": "ts-node index.ts" | ||
| }, | ||
| "dependencies": { | ||
| "@openfeature/server-sdk": "^1.22.0", | ||
| "@posthog/openfeature-node-provider": "*", | ||
| "posthog-node": "*" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "20.19.7", | ||
| "ts-node": "^10.8.2", | ||
| "typescript": "^5.8.2" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pnpmfile: ../.pnpmfile.cjs | ||
| minimumReleaseAge: 10080 | ||
|
|
||
| blockExoticSubdeps: true | ||
| trustPolicy: no-downgrade |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "es2020", | ||
| "module": "commonjs", | ||
| "moduleResolution": "node", | ||
| "lib": ["es2020"], | ||
| "strict": true, | ||
| "esModuleInterop": true, | ||
| "skipLibCheck": true, | ||
| "resolveJsonModule": true, | ||
| "noEmit": true | ||
| }, | ||
| "include": ["*.ts"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "trailingComma": "es5", | ||
| "tabWidth": 2, | ||
| "semi": false, | ||
| "singleQuote": true, | ||
| "printWidth": 120 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # @posthog/openfeature-node-provider | ||
|
|
||
| Official [PostHog](https://posthog.com) provider for the [OpenFeature](https://openfeature.dev) **server** | ||
| SDK ([`@openfeature/server-sdk`](https://openfeature.dev/docs/reference/technologies/server/javascript)), | ||
| backed by [`posthog-node`](https://posthog.com/docs/libraries/node). | ||
|
|
||
| For the browser, use [`@posthog/openfeature-web-provider`](../openfeature-web-provider). | ||
|
|
||
| ## Documentation | ||
|
|
||
| Installation and usage instructions live in the PostHog docs, so they stay in one place and don't drift: | ||
|
|
||
| **https://posthog.com/docs/feature-flags/installation/openfeature-js** |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { createDefaultPreset } from 'ts-jest' | ||
|
|
||
| const tsJestTransformCfg = createDefaultPreset().transform | ||
|
|
||
| /** @type {import("jest").Config} **/ | ||
| export default { | ||
| testEnvironment: 'node', | ||
| testPathIgnorePatterns: ['/node_modules/', '/dist/'], | ||
| transform: { | ||
| ...tsJestTransformCfg, | ||
| }, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| { | ||
| "name": "@posthog/openfeature-node-provider", | ||
| "version": "0.0.0", | ||
| "bugs": { | ||
| "url": "https://github.com/PostHog/posthog-js/issues" | ||
| }, | ||
| "description": "Official PostHog provider for the OpenFeature server SDK (Node), backed by posthog-node", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/PostHog/posthog-js.git", | ||
| "directory": "packages/openfeature-node-provider" | ||
| }, | ||
| "homepage": "https://github.com/PostHog/posthog-js/tree/main/packages/openfeature-node-provider#readme", | ||
| "author": { | ||
| "name": "PostHog", | ||
| "email": "engineering@posthog.com", | ||
| "url": "https://posthog.com" | ||
| }, | ||
| "license": "MIT", | ||
| "main": "./dist/index.js", | ||
| "module": "./dist/index.mjs", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "import": { | ||
| "types": "./dist/index.d.ts", | ||
| "default": "./dist/index.mjs" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/index.d.ts", | ||
| "default": "./dist/index.js" | ||
| } | ||
| } | ||
| }, | ||
| "scripts": { | ||
| "clean": "rimraf dist", | ||
| "lint": "eslint src", | ||
| "lint:fix": "eslint src --fix", | ||
| "build": "rslib build", | ||
| "dev": "rslib build --watch", | ||
| "test:unit": "jest", | ||
| "prepublishOnly": "pnpm lint && pnpm test:unit && pnpm build", | ||
| "package": "pnpm pack --out $PACKAGE_DEST/%s.tgz" | ||
| }, | ||
| "engines": { | ||
| "node": "^20.20.0 || >=22.22.0" | ||
| }, | ||
| "files": [ | ||
| "dist/" | ||
| ], | ||
| "keywords": [ | ||
| "posthog", | ||
| "openfeature", | ||
| "feature-flags", | ||
| "node", | ||
| "server" | ||
| ], | ||
| "peerDependencies": { | ||
| "@openfeature/core": "^1.11.0", | ||
| "@openfeature/server-sdk": "^1.17.0", | ||
| "posthog-node": ">=5.21.2" | ||
| }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
"peerDependencies": {
"@openfeature/core": "^1.11.0",
- "@openfeature/server-sdk": "^1.7.0",
+ "@openfeature/server-sdk": "^1.17.0",
"posthog-node": ">=5.21.2"
},
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 7fffb7c — bumped the |
||
| "devDependencies": { | ||
| "@openfeature/core": "^1.11.0", | ||
| "@openfeature/server-sdk": "^1.22.0", | ||
| "@posthog-tooling/tsconfig-base": "workspace:*", | ||
| "@rslib/core": "catalog:", | ||
| "@types/jest": "catalog:", | ||
| "@types/node": "^22.15.23", | ||
| "jest": "catalog:", | ||
| "posthog-node": "workspace:*", | ||
| "ts-jest": "catalog:" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { defineConfig } from '@rslib/core' | ||
|
|
||
| export default defineConfig({ | ||
| lib: [ | ||
| { format: 'esm', syntax: 'es2023', dts: true, bundle: false }, | ||
| { format: 'cjs', syntax: 'es2023', dts: true, bundle: false }, | ||
| ], | ||
| output: { | ||
| // Server provider: targets the Node.js runtime. | ||
| target: 'node', | ||
| }, | ||
| source: { | ||
| entry: { | ||
| index: ['src/**/*', '!src/__tests__/**/*', '!src/**/*.spec.ts'], | ||
| }, | ||
| tsconfigPath: './tsconfig.build.json', | ||
| }, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: These three flag reads run sequentially even though they're independent.Promise.allwould model the better pattern here since example code tends to get copy-pasted, though at 3 flags in a one-shot demo the actual cost is negligible.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in c2ad670 — the three independent reads now run via
Promise.all.