Skip to content

Commit 31b9a72

Browse files
feat(go-feature-flag)!: Introduce in-process evaluation + tracking
Signed-off-by: Thomas Poignant <[email protected]>
1 parent c3c3d01 commit 31b9a72

File tree

106 files changed

+8037
-2028
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+8037
-2028
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@
1717
[submodule "libs/shared/flagd-core/spec"]
1818
path = libs/shared/flagd-core/spec
1919
url = https://github.com/open-feature/spec
20+
[submodule "libs/providers/go-feature-flag-server/wasm-releases"]
21+
path = libs/providers/go-feature-flag-server/wasm-releases
22+
url = https://github.com/go-feature-flag/wasm-releases.git

libs/providers/go-feature-flag/.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"extends": ["../../../.eslintrc.json"],
3-
"ignorePatterns": ["!**/*"],
3+
"ignorePatterns": ["!**/*", "src/lib/wasm/wasm_exec.js"],
44
"rules": {
55
"@typescript-eslint/no-explicit-any": "off",
66
"quotes": ["error", "single"]
Lines changed: 327 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,337 @@
1-
# Server-side Go Feature Flag Provider
1+
# Server-side GO Feature Flag Provider
22

3-
This provider is an implementation for [`go-feature-flag`](https://github.com/thomaspoignant/go-feature-flag) a simple and complete
4-
feature flag solution, without any complex backend system to install, all you need is a file as your backend.
3+
A feature flag provider for [OpenFeature](https://openfeature.dev/) that integrates with [go-feature-flag](https://github.com/thomaspoignant/go-feature-flag), a simple and complete feature flag solution.
54

6-
It uses [`go-feature-flag-relay-proxy`](https://github.com/thomaspoignant/go-feature-flag-relay-proxy) which expose the capabilities of the SDK through an API layer.
5+
This provider supports both **in-process** and **remote** evaluation modes, offering flexibility for different deployment scenarios.
76

8-
## Installation
7+
## Features 🚀
98

9+
- **Dual Evaluation Modes**: In-process evaluation for performance and remote evaluation for centralized control
10+
- **Real-time Configuration Updates**: Automatic polling for flag configuration changes
11+
- **Comprehensive Data Collection**: Built-in event tracking and analytics
12+
- **Flexible Context Support**: Rich evaluation context with targeting rules
13+
- **Caching**: Intelligent caching with automatic cache invalidation
14+
- **Error Handling**: Robust error handling with fallback mechanisms
15+
- **TypeScript Support**: Full TypeScript support with type safety
16+
- **OpenFeature Compliance**: Full compliance with OpenFeature specification
17+
18+
## Installation 📦
19+
20+
```bash
21+
npm install @openfeature/go-feature-flag-provider
1022
```
11-
$ npm install @openfeature/go-feature-flag-provider
23+
24+
### Peer Dependencies
25+
26+
```bash
27+
npm install @openfeature/server-sdk
1228
```
1329

14-
Required peer dependencies
30+
## Quick Start 🏃‍♂️
31+
32+
### Basic Setup
33+
34+
```typescript
35+
import { OpenFeature } from '@openfeature/server-sdk';
36+
import { GoFeatureFlagProvider, EvaluationType } from '@openfeature/go-feature-flag-provider';
37+
38+
// Initialize the provider
39+
const provider = new GoFeatureFlagProvider({
40+
endpoint: 'https://your-relay-proxy.com',
41+
evaluationType: EvaluationType.Remote,
42+
});
43+
44+
// Register the provider
45+
OpenFeature.setProvider(provider);
46+
47+
// Get a client
48+
const client = OpenFeature.getClient();
49+
50+
// Evaluate a flag
51+
const flagValue = await client.getBooleanValue('my-feature-flag', false, {
52+
targetingKey: 'user-123',
53+
54+
});
55+
```
56+
57+
### In-Process Evaluation
58+
59+
For high-performance scenarios where you want to evaluate flags locally:
1560

61+
```typescript
62+
import { GoFeatureFlagProvider, EvaluationType } from '@openfeature/go-feature-flag-provider';
63+
64+
const provider = new GoFeatureFlagProvider({
65+
endpoint: 'https://your-relay-proxy.com',
66+
evaluationType: EvaluationType.InProcess,
67+
flagChangePollingIntervalMs: 30000, // Poll every 30 seconds
68+
});
1669
```
17-
$ npm install @openfeature/server-sdk
70+
71+
## Configuration Options ⚙️
72+
73+
### Provider Options
74+
75+
| Option | Type | Default | Description |
76+
| ----------------------------- | ------------------ | ------------ | ----------------------------------------------- |
77+
| `endpoint` | `string` | **Required** | The endpoint of the GO Feature Flag relay-proxy |
78+
| `evaluationType` | `EvaluationType` | `InProcess` | Evaluation mode: `InProcess` or `Remote` |
79+
| `timeout` | `number` | `10000` | HTTP request timeout in milliseconds |
80+
| `flagChangePollingIntervalMs` | `number` | `120000` | Polling interval for configuration changes |
81+
| `dataFlushInterval` | `number` | `1000` | Data collection flush interval |
82+
| `maxPendingEvents` | `number` | `10000` | Maximum pending events before flushing |
83+
| `disableDataCollection` | `boolean` | `false` | Disable data collection entirely |
84+
| `apiKey` | `string` | `undefined` | API key for authentication |
85+
| `exporterMetadata` | `ExporterMetadata` | `undefined` | Custom metadata for events |
86+
| `fetchImplementation` | `FetchAPI` | `undefined` | Custom fetch implementation |
87+
88+
### Evaluation Types
89+
90+
#### InProcess Evaluation
91+
92+
- **Performance**: Fastest evaluation with local caching
93+
- **Network**: Minimal network calls, only for configuration updates
94+
- **Use Case**: High-performance applications, real-time evaluation
95+
96+
#### Remote Evaluation
97+
98+
- **Performance**: Network-dependent evaluation
99+
- **Network**: Each evaluation requires a network call
100+
- **Use Case**: Centralized control, complex targeting rules
101+
102+
## Advanced Usage 🔧
103+
104+
### Custom Context and Targeting
105+
106+
```typescript
107+
const context = {
108+
targetingKey: 'user-123',
109+
110+
firstname: 'John',
111+
lastname: 'Doe',
112+
anonymous: false,
113+
professional: true,
114+
rate: 3.14,
115+
age: 30,
116+
company_info: {
117+
name: 'my_company',
118+
size: 120,
119+
},
120+
labels: ['pro', 'beta'],
121+
};
122+
123+
const flagValue = await client.getBooleanValue('my-feature-flag', false, context);
18124
```
125+
126+
### Data Collection and Analytics
127+
128+
The provider automatically collects evaluation data. You can customize this behavior:
129+
130+
```typescript
131+
const provider = new GoFeatureFlagProvider({
132+
endpoint: 'https://your-relay-proxy.com',
133+
evaluationType: EvaluationType.Remote,
134+
disableDataCollection: false, // Enable data collection
135+
dataFlushInterval: 2000, // Flush every 2 seconds
136+
maxPendingEvents: 5000, // Max 5000 pending events
137+
});
138+
```
139+
140+
### Custom Exporter Metadata
141+
142+
Add custom metadata to your evaluation events:
143+
144+
```typescript
145+
import { ExporterMetadata } from '@openfeature/go-feature-flag-provider';
146+
147+
const metadata = new ExporterMetadata()
148+
.add('environment', 'production')
149+
.add('version', '1.0.0')
150+
.add('region', 'us-east-1');
151+
152+
const provider = new GoFeatureFlagProvider({
153+
endpoint: 'https://your-relay-proxy.com',
154+
evaluationType: EvaluationType.Remote,
155+
exporterMetadata: metadata,
156+
});
157+
```
158+
159+
### Custom Fetch Implementation
160+
161+
For environments with specific HTTP requirements:
162+
163+
```typescript
164+
const provider = new GoFeatureFlagProvider({
165+
endpoint: 'https://your-relay-proxy.com',
166+
evaluationType: EvaluationType.Remote,
167+
fetchImplementation: customFetch, // Your custom fetch implementation
168+
});
169+
```
170+
171+
### Error Handling
172+
173+
The provider includes comprehensive error handling:
174+
175+
```typescript
176+
try {
177+
const flagValue = await client.getBooleanValue('my-feature-flag', false, context);
178+
} catch (error) {
179+
if (error.code === 'FLAG_NOT_FOUND') {
180+
// Handle flag not found
181+
} else if (error.code === 'UNAUTHORIZED') {
182+
// Handle authentication error
183+
} else {
184+
// Handle other errors
185+
}
186+
}
187+
```
188+
189+
## Flag Types Supported 🎯
190+
191+
The provider supports all OpenFeature flag types:
192+
193+
### Boolean Flags
194+
195+
```typescript
196+
const isEnabled = await client.getBooleanValue('feature-flag', false, context);
197+
const details = await client.getBooleanDetails('feature-flag', false, context);
198+
```
199+
200+
### String Flags
201+
202+
```typescript
203+
const message = await client.getStringValue('welcome-message', 'Hello!', context);
204+
const details = await client.getStringDetails('welcome-message', 'Hello!', context);
205+
```
206+
207+
### Number Flags
208+
209+
```typescript
210+
const percentage = await client.getNumberValue('discount-percentage', 0, context);
211+
const details = await client.getNumberDetails('discount-percentage', 0, context);
212+
```
213+
214+
### Object Flags
215+
216+
```typescript
217+
const config = await client.getObjectValue('user-config', {}, context);
218+
const details = await client.getObjectDetails('user-config', {}, context);
219+
```
220+
221+
## Tracking Events 📊
222+
223+
The provider supports custom event tracking:
224+
225+
```typescript
226+
// Track a custom event
227+
client.track('user_action', context, {
228+
action: 'button_click',
229+
page: 'homepage',
230+
timestamp: Date.now(),
231+
});
232+
```
233+
234+
## Performance Considerations ⚡
235+
236+
### Caching Strategy
237+
238+
- **InProcess**: Local caching with automatic invalidation on configuration changes
239+
- **Remote**: HTTP caching headers respected
240+
- **Configuration**: Automatic polling with configurable intervals
241+
242+
### Best Practices
243+
244+
1. **Use InProcess for high-frequency evaluations**
245+
2. **Configure appropriate polling intervals**
246+
3. **Monitor data collection volume**
247+
4. **Set reasonable timeouts for your network**
248+
249+
## Troubleshooting 🔧
250+
251+
### Common Issues
252+
253+
**Configuration not updating**
254+
255+
- Check `flagChangePollingIntervalMs` setting
256+
- Verify relay-proxy endpoint is accessible
257+
258+
**High latency**
259+
260+
- Consider switching to `InProcess` evaluation
261+
- Check network connectivity to relay-proxy
262+
263+
**Data collection issues**
264+
265+
- Verify `disableDataCollection` is not set to `true`
266+
- Check `maxPendingEvents` and `dataFlushInterval` settings
267+
268+
### Debug Mode
269+
270+
Enable debug logging:
271+
272+
```typescript
273+
import { Logger } from '@openfeature/server-sdk';
274+
275+
const logger = {
276+
debug: (message: string) => console.log(`[DEBUG] ${message}`),
277+
info: (message: string) => console.log(`[INFO] ${message}`),
278+
warn: (message: string) => console.log(`[WARN] ${message}`),
279+
error: (message: string) => console.log(`[ERROR] ${message}`),
280+
};
281+
282+
const provider = new GoFeatureFlagProvider(
283+
{
284+
endpoint: 'https://your-relay-proxy.com',
285+
evaluationType: EvaluationType.Remote,
286+
},
287+
logger,
288+
);
289+
```
290+
291+
## API Reference 📚
292+
293+
### GoFeatureFlagProvider
294+
295+
The main provider class that implements the OpenFeature Provider interface.
296+
297+
#### Constructor
298+
299+
```typescript
300+
constructor(options: GoFeatureFlagProviderOptions, logger?: Logger)
301+
```
302+
303+
#### Methods
304+
305+
- `resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, context: EvaluationContext): Promise<ResolutionDetails<boolean>>`
306+
- `resolveStringEvaluation(flagKey: string, defaultValue: string, context: EvaluationContext): Promise<ResolutionDetails<string>>`
307+
- `resolveNumberEvaluation(flagKey: string, defaultValue: number, context: EvaluationContext): Promise<ResolutionDetails<number>>`
308+
- `resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T, context: EvaluationContext): Promise<ResolutionDetails<T>>`
309+
- `track(trackingEventName: string, context?: EvaluationContext, trackingEventDetails?: TrackingEventDetails): void`
310+
311+
### EvaluationType
312+
313+
Enum defining evaluation modes:
314+
315+
- `EvaluationType.InProcess`: Local evaluation
316+
- `EvaluationType.Remote`: Remote evaluation
317+
318+
### ExporterMetadata
319+
320+
Class for adding custom metadata to evaluation events:
321+
322+
- `add(key: string, value: string | boolean | number): ExporterMetadata`
323+
- `asObject(): Record<string, string | boolean | number>`
324+
325+
## Contributing 🤝
326+
327+
We welcome contributions! Please see our [contributing guidelines](CONTRIBUTING.md) for details.
328+
329+
## License 📄
330+
331+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
332+
333+
## Support 💬
334+
335+
- **Documentation**: [OpenFeature Documentation](https://openfeature.dev/)
336+
- **Issues**: [GitHub Issues](https://github.com/open-feature/js-sdk-contrib/issues)
337+
- **Discussions**: [GitHub Discussions](https://github.com/open-feature/js-sdk-contrib/discussions)

libs/providers/go-feature-flag/jest.config.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
export default {
33
displayName: 'provider-go-feature-flag',
44
preset: '../../../jest.preset.js',
5-
globals: {
6-
'ts-jest': {
7-
tsconfig: '<rootDir>/tsconfig.spec.json',
8-
},
9-
},
105
transform: {
11-
'^.+\\.[tj]s$': 'ts-jest',
6+
'^.+\\.ts$': [
7+
'ts-jest',
8+
{
9+
tsconfig: '<rootDir>/tsconfig.spec.json',
10+
},
11+
],
1212
},
1313
moduleFileExtensions: ['ts', 'js', 'html'],
1414
coverageDirectory: '../../../coverage/libs/providers/go-feature-flag',

0 commit comments

Comments
 (0)