@openfeature/server-sdk 1.20.1 → 1.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -49
- package/dist/cjs/index.js +54 -267
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/index.js +44 -247
- package/dist/esm/index.js.map +4 -4
- package/dist/types.d.ts +44 -117
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.8.0&color=yellow&style=for-the-badge" />
|
|
17
17
|
</a>
|
|
18
18
|
<!-- x-release-please-start-version -->
|
|
19
|
-
<a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.20.
|
|
20
|
-
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.20.
|
|
19
|
+
<a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.20.2">
|
|
20
|
+
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.20.2&color=blue&style=for-the-badge" />
|
|
21
21
|
</a>
|
|
22
22
|
<!-- x-release-please-end -->
|
|
23
23
|
<br/>
|
|
@@ -88,7 +88,7 @@ const client = OpenFeature.getClient();
|
|
|
88
88
|
const v2Enabled = await client.getBooleanValue('v2_enabled', false);
|
|
89
89
|
|
|
90
90
|
if (v2Enabled) {
|
|
91
|
-
console.log(
|
|
91
|
+
console.log('v2 is enabled');
|
|
92
92
|
}
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -98,19 +98,19 @@ See [here](https://open-feature.github.io/js-sdk/modules/_openfeature_server_sdk
|
|
|
98
98
|
|
|
99
99
|
## 🌟 Features
|
|
100
100
|
|
|
101
|
-
| Status | Features | Description
|
|
102
|
-
| ------ | ------------------------------------------------------------------- |
|
|
103
|
-
| ✅
|
|
104
|
-
| ✅
|
|
105
|
-
| ✅
|
|
106
|
-
| ✅
|
|
107
|
-
| ✅
|
|
108
|
-
| ✅
|
|
109
|
-
| ✅
|
|
110
|
-
| ✅
|
|
111
|
-
| ✅
|
|
112
|
-
| ✅
|
|
113
|
-
| ✅
|
|
101
|
+
| Status | Features | Description |
|
|
102
|
+
| ------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
103
|
+
| ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
|
|
104
|
+
| ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
|
|
105
|
+
| ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
|
|
106
|
+
| ✅ | [Logging](#logging) | Integrate with popular logging packages. |
|
|
107
|
+
| ✅ | [Domains](#domains) | Logically bind clients with providers. |
|
|
108
|
+
| ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
|
|
109
|
+
| ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread) |
|
|
110
|
+
| ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations, particularly for A/B testing. |
|
|
111
|
+
| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
|
|
112
|
+
| ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
|
|
113
|
+
| ✅ | [Multi-Provider](#multi-provider) | Combine multiple providers with configurable evaluation strategies. |
|
|
114
114
|
|
|
115
115
|
<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
|
|
116
116
|
|
|
@@ -162,10 +162,7 @@ const primaryProvider = new YourPrimaryProvider();
|
|
|
162
162
|
const backupProvider = new YourBackupProvider();
|
|
163
163
|
|
|
164
164
|
// Create multi-provider with a strategy
|
|
165
|
-
const multiProvider = new MultiProvider(
|
|
166
|
-
[primaryProvider, backupProvider],
|
|
167
|
-
new FirstMatchStrategy()
|
|
168
|
-
);
|
|
165
|
+
const multiProvider = new MultiProvider([primaryProvider, backupProvider], new FirstMatchStrategy());
|
|
169
166
|
|
|
170
167
|
// Register the multi-provider
|
|
171
168
|
await OpenFeature.setProviderAndWait(multiProvider);
|
|
@@ -191,7 +188,7 @@ const oldProvider = new OldFlagProvider();
|
|
|
191
188
|
|
|
192
189
|
const multiProvider = new MultiProvider(
|
|
193
190
|
[newProvider, oldProvider], // New provider is consulted first
|
|
194
|
-
new FirstMatchStrategy()
|
|
191
|
+
new FirstMatchStrategy(),
|
|
195
192
|
);
|
|
196
193
|
|
|
197
194
|
await OpenFeature.setProviderAndWait(multiProvider);
|
|
@@ -207,13 +204,10 @@ const providerA = new ProviderA();
|
|
|
207
204
|
const providerB = new ProviderB();
|
|
208
205
|
|
|
209
206
|
const multiProvider = new MultiProvider(
|
|
210
|
-
[
|
|
211
|
-
{ provider: providerA },
|
|
212
|
-
{ provider: providerB }
|
|
213
|
-
],
|
|
207
|
+
[{ provider: providerA }, { provider: providerB }],
|
|
214
208
|
new ComparisonStrategy(providerA, (resolutions) => {
|
|
215
209
|
console.warn('Mismatch detected', resolutions);
|
|
216
|
-
})
|
|
210
|
+
}),
|
|
217
211
|
);
|
|
218
212
|
|
|
219
213
|
await OpenFeature.setProviderAndWait(multiProvider);
|
|
@@ -227,7 +221,7 @@ If the flag management system you're using supports targeting, you can provide t
|
|
|
227
221
|
|
|
228
222
|
```ts
|
|
229
223
|
// set a value to the global context
|
|
230
|
-
OpenFeature.setContext({ region:
|
|
224
|
+
OpenFeature.setContext({ region: 'us-east-1' });
|
|
231
225
|
|
|
232
226
|
// set a value to the client context
|
|
233
227
|
const client = OpenFeature.getClient();
|
|
@@ -237,7 +231,7 @@ client.setContext({ version: process.env.APP_VERSION });
|
|
|
237
231
|
const requestContext = {
|
|
238
232
|
targetingKey: req.session.id,
|
|
239
233
|
email: req.session.email,
|
|
240
|
-
product: req.productId
|
|
234
|
+
product: req.productId,
|
|
241
235
|
};
|
|
242
236
|
|
|
243
237
|
const boolValue = await client.getBooleanValue('some-flag', false, requestContext);
|
|
@@ -255,7 +249,7 @@ If the hook you're looking for hasn't been created yet, see the [develop a hook]
|
|
|
255
249
|
Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level.
|
|
256
250
|
|
|
257
251
|
```ts
|
|
258
|
-
import { OpenFeature } from
|
|
252
|
+
import { OpenFeature } from '@openfeature/server-sdk';
|
|
259
253
|
|
|
260
254
|
// add a hook globally, to run on all evaluations
|
|
261
255
|
OpenFeature.addHooks(new ExampleGlobalHook());
|
|
@@ -265,7 +259,7 @@ const client = OpenFeature.getClient();
|
|
|
265
259
|
client.addHooks(new ExampleClientHook());
|
|
266
260
|
|
|
267
261
|
// add a hook for this evaluation only
|
|
268
|
-
const boolValue = await client.getBooleanValue(
|
|
262
|
+
const boolValue = await client.getBooleanValue('bool-flag', false, { hooks: [new ExampleHook()] });
|
|
269
263
|
```
|
|
270
264
|
|
|
271
265
|
### Logging
|
|
@@ -275,7 +269,7 @@ This behavior can be overridden by passing a custom logger either globally or pe
|
|
|
275
269
|
A custom logger must implement the [Logger interface](../shared/src/logger/logger.ts).
|
|
276
270
|
|
|
277
271
|
```ts
|
|
278
|
-
import type { Logger } from
|
|
272
|
+
import type { Logger } from '@openfeature/server-sdk';
|
|
279
273
|
|
|
280
274
|
// The logger can be anything that conforms with the Logger interface
|
|
281
275
|
const logger: Logger = console;
|
|
@@ -295,28 +289,28 @@ A domain is a logical identifier which can be used to associate clients with a p
|
|
|
295
289
|
If a domain has no associated provider, the default provider is used.
|
|
296
290
|
|
|
297
291
|
```ts
|
|
298
|
-
import { OpenFeature, InMemoryProvider } from
|
|
292
|
+
import { OpenFeature, InMemoryProvider } from '@openfeature/server-sdk';
|
|
299
293
|
|
|
300
294
|
const myFlags = {
|
|
301
|
-
|
|
295
|
+
v2_enabled: {
|
|
302
296
|
variants: {
|
|
303
297
|
on: true,
|
|
304
|
-
off: false
|
|
298
|
+
off: false,
|
|
305
299
|
},
|
|
306
300
|
disabled: false,
|
|
307
|
-
defaultVariant:
|
|
308
|
-
}
|
|
301
|
+
defaultVariant: 'on',
|
|
302
|
+
},
|
|
309
303
|
};
|
|
310
304
|
|
|
311
305
|
// Registering the default provider
|
|
312
306
|
OpenFeature.setProvider(InMemoryProvider(myFlags));
|
|
313
307
|
// Registering a provider to a domain
|
|
314
|
-
OpenFeature.setProvider(
|
|
308
|
+
OpenFeature.setProvider('my-domain', new InMemoryProvider(someOtherFlags));
|
|
315
309
|
|
|
316
310
|
// A Client bound to the default provider
|
|
317
311
|
const clientWithDefault = OpenFeature.getClient();
|
|
318
312
|
// A Client bound to the InMemoryProvider provider
|
|
319
|
-
const domainScopedClient = OpenFeature.getClient(
|
|
313
|
+
const domainScopedClient = OpenFeature.getClient('my-domain');
|
|
320
314
|
```
|
|
321
315
|
|
|
322
316
|
Domains can be defined on a provider during registration.
|
|
@@ -353,22 +347,22 @@ Transaction context can be set where specific data is available (e.g. an auth se
|
|
|
353
347
|
The following example shows an Express middleware using transaction context propagation to propagate the request ip and user id into request scoped transaction context.
|
|
354
348
|
|
|
355
349
|
```ts
|
|
356
|
-
import express, { Request, Response, NextFunction } from
|
|
350
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
357
351
|
import { OpenFeature, AsyncLocalStorageTransactionContextPropagator } from '@openfeature/server-sdk';
|
|
358
352
|
|
|
359
|
-
OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContextPropagator())
|
|
353
|
+
OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContextPropagator());
|
|
360
354
|
|
|
361
355
|
/**
|
|
362
356
|
* This example is based on an express middleware.
|
|
363
357
|
*/
|
|
364
358
|
const app = express();
|
|
365
359
|
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
366
|
-
const ip = res.headers.get(
|
|
360
|
+
const ip = res.headers.get('X-Forwarded-For');
|
|
367
361
|
OpenFeature.setTransactionContext({ targetingKey: req.user.id, ipAddress: ip }, () => {
|
|
368
362
|
// The transaction context is used in any flag evaluation throughout the whole call chain of next
|
|
369
363
|
next();
|
|
370
364
|
});
|
|
371
|
-
})
|
|
365
|
+
});
|
|
372
366
|
```
|
|
373
367
|
|
|
374
368
|
### Tracking
|
|
@@ -394,7 +388,7 @@ This should only be called when your application is in the process of shutting d
|
|
|
394
388
|
```ts
|
|
395
389
|
import { OpenFeature } from '@openfeature/server-sdk';
|
|
396
390
|
|
|
397
|
-
await OpenFeature.close()
|
|
391
|
+
await OpenFeature.close();
|
|
398
392
|
```
|
|
399
393
|
|
|
400
394
|
## Extending
|
|
@@ -414,7 +408,7 @@ import {
|
|
|
414
408
|
Logger,
|
|
415
409
|
Provider,
|
|
416
410
|
ProviderEventEmitter,
|
|
417
|
-
ResolutionDetails
|
|
411
|
+
ResolutionDetails,
|
|
418
412
|
} from '@openfeature/server-sdk';
|
|
419
413
|
|
|
420
414
|
// implement the provider interface
|
|
@@ -426,16 +420,36 @@ class MyProvider implements Provider {
|
|
|
426
420
|
} as const;
|
|
427
421
|
// Optional provider managed hooks
|
|
428
422
|
hooks?: Hook[];
|
|
429
|
-
resolveBooleanEvaluation(
|
|
423
|
+
resolveBooleanEvaluation(
|
|
424
|
+
flagKey: string,
|
|
425
|
+
defaultValue: boolean,
|
|
426
|
+
context: EvaluationContext,
|
|
427
|
+
logger: Logger,
|
|
428
|
+
): Promise<ResolutionDetails<boolean>> {
|
|
430
429
|
// code to evaluate a boolean
|
|
431
430
|
}
|
|
432
|
-
resolveStringEvaluation(
|
|
431
|
+
resolveStringEvaluation(
|
|
432
|
+
flagKey: string,
|
|
433
|
+
defaultValue: string,
|
|
434
|
+
context: EvaluationContext,
|
|
435
|
+
logger: Logger,
|
|
436
|
+
): Promise<ResolutionDetails<string>> {
|
|
433
437
|
// code to evaluate a string
|
|
434
438
|
}
|
|
435
|
-
resolveNumberEvaluation(
|
|
439
|
+
resolveNumberEvaluation(
|
|
440
|
+
flagKey: string,
|
|
441
|
+
defaultValue: number,
|
|
442
|
+
context: EvaluationContext,
|
|
443
|
+
logger: Logger,
|
|
444
|
+
): Promise<ResolutionDetails<number>> {
|
|
436
445
|
// code to evaluate a number
|
|
437
446
|
}
|
|
438
|
-
resolveObjectEvaluation<T extends JsonValue>(
|
|
447
|
+
resolveObjectEvaluation<T extends JsonValue>(
|
|
448
|
+
flagKey: string,
|
|
449
|
+
defaultValue: T,
|
|
450
|
+
context: EvaluationContext,
|
|
451
|
+
logger: Logger,
|
|
452
|
+
): Promise<ResolutionDetails<T>> {
|
|
439
453
|
// code to evaluate an object
|
|
440
454
|
}
|
|
441
455
|
|
|
@@ -460,7 +474,7 @@ This can be a new repository or included in [the existing contrib repository](ht
|
|
|
460
474
|
Implement your own hook by conforming to the [Hook interface](../shared/src/hooks/hook.ts).
|
|
461
475
|
|
|
462
476
|
```ts
|
|
463
|
-
import type { Hook, HookContext, EvaluationDetails, FlagValue } from
|
|
477
|
+
import type { Hook, HookContext, EvaluationDetails, FlagValue } from '@openfeature/server-sdk';
|
|
464
478
|
|
|
465
479
|
export class MyHook implements Hook {
|
|
466
480
|
after(hookContext: HookContext, evaluationDetails: EvaluationDetails<FlagValue>) {
|