Skip to content

Commit 6ec1b0e

Browse files
authored
feat(flipt): Add Flipt provider in openfeature-js (#724)
Signed-off-by: Yoofi Quansah <[email protected]> Signed-off-by: Mark Phelps <[email protected]>
1 parent 40bbaf6 commit 6ec1b0e

21 files changed

+810
-37
lines changed

.github/component_owners.yml

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ components:
2222
- kinyoklion
2323
- mateoc
2424
- sago2k8
25+
libs/providers/flipt:
26+
- yquansah
2527

2628
ignored-authors:
2729
- renovate-bot

.release-please-manifest.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
"libs/providers/config-cat": "0.4.0",
88
"libs/providers/launchdarkly-client": "0.2.0",
99
"libs/providers/go-feature-flag-web": "0.1.5",
10-
"libs/shared/flagd-core": "0.1.8"
10+
"libs/shared/flagd-core": "0.1.8",
11+
"libs/providers/flipt": "0.0.1"
1112
}

libs/providers/flipt/.eslintrc.json

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"extends": ["../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
},
17+
{
18+
"files": ["*.json"],
19+
"parser": "jsonc-eslint-parser",
20+
"rules": {
21+
"@nx/dependency-checks": "error"
22+
}
23+
}
24+
]
25+
}

libs/providers/flipt/README.md

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Flipt Provider
2+
3+
[Flipt](https://www.flipt.io/) is an open source developer friendly feature flagging solution, that allows for easy management and fast feature evaluation.
4+
5+
This provider is an implementation on top of the official [Flipt Node Server Side SDK](https://www.npmjs.com/package/@flipt-io/flipt).
6+
7+
## Installation
8+
9+
```
10+
$ npm install @openfeature/flipt-provider
11+
```
12+
13+
### Peer Dependencies
14+
15+
Both the OpenFeature SDK and the Flipt Node Server SDK are required as peer dependencies.
16+
17+
Please make sure to install `@flipt/flipt-io` at versions >= `1.0.0`, as the client API is different in earlier versions.
18+
19+
The peer dependency will also enforce the above version.
20+
21+
## Example initialization and usage
22+
23+
To initialize the OpenFeature client with Flipt, you can use the following code snippet:
24+
25+
```ts
26+
import { FliptProvider } from '@openfeature/flipt';
27+
28+
const provider = new FliptProvider('namespace-of-choice', { url: 'http://your.upstream.flipt.host' });
29+
OpenFeature.setProvider(provider);
30+
```
31+
32+
After the provider gets initialized, you can start evaluations of feature flags like so:
33+
34+
```ts
35+
const client = OpenFeature.getClient();
36+
const details = await client.getStringDetails('nonExistent', 'default', {
37+
targetingKey: 'myentity',
38+
39+
});
40+
```
41+
42+
## Evaluation Context Transformation
43+
44+
OpenFeature standardizes the evaluation context to include a `targetingKey`, and some other additional arbitrary properties that each provider can use fit for their use case.
45+
46+
For Flipt, we translate the `targetingKey` as the `entityId`, and the rest of the OpenFeature evaluation context as the `context` in Flipt vernacular. You can find the meaning of those two words [here](https://www.flipt.io/docs/reference/evaluation/variant-evaluation) in our API docs.
47+
48+
For example, an OpenFeature Evaluation context that has this structure:
49+
50+
```json
51+
{
52+
"targetingKey": "my-targeting-id",
53+
"email": "[email protected]",
54+
"userId": "this-very-long-user-id"
55+
}
56+
```
57+
58+
will get transformed to the following for Flipt:
59+
60+
```json
61+
{
62+
"entityId": "my-targeting-id",
63+
"context": {
64+
"email": "[email protected]",
65+
"userId": "this-very-long-user-id"
66+
}
67+
}
68+
```
69+
70+
## Building
71+
72+
Run `nx package providers-flipt` to build the library.
73+
74+
## Running unit tests
75+
76+
Run `nx test providers-flipt` to execute the unit tests via [Jest](https://jestjs.io).
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": [["minify", { "builtIns": false }]]
3+
}

libs/providers/flipt/jest.config.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* eslint-disable */
2+
export default {
3+
displayName: 'providers-flipt',
4+
preset: '../../../jest.preset.js',
5+
transform: {
6+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
7+
},
8+
moduleFileExtensions: ['ts', 'js', 'html'],
9+
coverageDirectory: '../../../coverage/libs/providers/flipt',
10+
};

libs/providers/flipt/package-lock.json

+47
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/providers/flipt/package.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@openfeature/flipt-provider",
3+
"version": "0.0.1",
4+
"dependencies": {
5+
"tslib": "^2.3.0"
6+
},
7+
"main": "./src/index.js",
8+
"typings": "./src/index.d.ts",
9+
"scripts": {
10+
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
11+
"current-version": "echo $npm_package_version"
12+
},
13+
"peerDependencies": {
14+
"@openfeature/server-sdk": "^1.6.0",
15+
"@flipt-io/flipt": "^1.0.0"
16+
}
17+
}

libs/providers/flipt/project.json

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"name": "providers-flipt",
3+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "libs/providers/flipt/src",
5+
"projectType": "library",
6+
"targets": {
7+
"publish": {
8+
"executor": "nx:run-commands",
9+
"options": {
10+
"command": "npm run publish-if-not-exists",
11+
"cwd": "dist/libs/providers/flipt"
12+
},
13+
"dependsOn": [
14+
{
15+
"projects": "self",
16+
"target": "package"
17+
}
18+
]
19+
},
20+
"lint": {
21+
"executor": "@nx/linter:eslint",
22+
"outputs": ["{options.outputFile}"],
23+
"options": {
24+
"lintFilePatterns": ["libs/providers/flipt/**/*.ts", "libs/providers/flipt/package.json"]
25+
}
26+
},
27+
"test": {
28+
"executor": "@nx/jest:jest",
29+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
30+
"options": {
31+
"jestConfig": "libs/providers/flipt/jest.config.ts",
32+
"passWithNoTests": true
33+
},
34+
"configurations": {
35+
"ci": {
36+
"ci": true,
37+
"codeCoverage": true
38+
}
39+
}
40+
},
41+
"package": {
42+
"executor": "@nx/rollup:rollup",
43+
"outputs": ["{options.outputPath}"],
44+
"options": {
45+
"project": "libs/providers/flipt/package.json",
46+
"outputPath": "dist/libs/providers/flipt",
47+
"entryFile": "libs/providers/flipt/src/index.ts",
48+
"tsConfig": "libs/providers/flipt/tsconfig.lib.json",
49+
"buildableProjectDepsInPackageJsonType": "dependencies",
50+
"compiler": "tsc",
51+
"generateExportsField": true,
52+
"umdName": "flipt",
53+
"external": "all",
54+
"format": ["cjs", "esm"],
55+
"assets": [
56+
{
57+
"glob": "package.json",
58+
"input": "./assets",
59+
"output": "./src/"
60+
},
61+
{
62+
"glob": "LICENSE",
63+
"input": "./",
64+
"output": "./"
65+
},
66+
{
67+
"glob": "README.md",
68+
"input": "./libs/providers/flipt",
69+
"output": "./"
70+
}
71+
]
72+
}
73+
}
74+
},
75+
"tags": []
76+
}

libs/providers/flipt/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './lib/flipt-provider';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { EvaluationContext } from '@openfeature/server-sdk';
2+
import { transformContext } from './context-transformer';
3+
4+
describe('context-transformer', () => {
5+
describe('transformContext', () => {
6+
it('should transform context correctly', () => {
7+
const context: EvaluationContext = {
8+
targetingKey: 'entity',
9+
customProp: 'test',
10+
};
11+
12+
const transformedContext: Record<string, string> = transformContext(context);
13+
14+
expect(transformedContext['customProp']).toBe('test');
15+
expect(transformedContext['targetingKey']).toBeUndefined();
16+
});
17+
});
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { EvaluationContext } from '@openfeature/server-sdk';
2+
3+
export function transformContext(context: EvaluationContext): Record<string, string> {
4+
const evalContext: Record<string, string> = {};
5+
for (const value in context) {
6+
if (value !== 'targetingKey') {
7+
evalContext[value] = context[value]?.toString() ?? '';
8+
}
9+
}
10+
11+
return evalContext;
12+
}

0 commit comments

Comments
 (0)