@openfeature/server-sdk 1.20.0 → 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/LICENSE +1 -1
- package/README.md +63 -48
- package/dist/cjs/index.js +56 -269
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/index.js +46 -249
- package/dist/esm/index.js.map +4 -4
- package/dist/types.d.ts +44 -117
- package/package.json +3 -3
package/LICENSE
CHANGED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright
|
|
189
|
+
Copyright OpenFeature Maintainers
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
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,18 +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
|
-
| ✅
|
|
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. |
|
|
113
114
|
|
|
114
115
|
<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
|
|
115
116
|
|
|
@@ -161,10 +162,7 @@ const primaryProvider = new YourPrimaryProvider();
|
|
|
161
162
|
const backupProvider = new YourBackupProvider();
|
|
162
163
|
|
|
163
164
|
// Create multi-provider with a strategy
|
|
164
|
-
const multiProvider = new MultiProvider(
|
|
165
|
-
[primaryProvider, backupProvider],
|
|
166
|
-
new FirstMatchStrategy()
|
|
167
|
-
);
|
|
165
|
+
const multiProvider = new MultiProvider([primaryProvider, backupProvider], new FirstMatchStrategy());
|
|
168
166
|
|
|
169
167
|
// Register the multi-provider
|
|
170
168
|
await OpenFeature.setProviderAndWait(multiProvider);
|
|
@@ -190,7 +188,7 @@ const oldProvider = new OldFlagProvider();
|
|
|
190
188
|
|
|
191
189
|
const multiProvider = new MultiProvider(
|
|
192
190
|
[newProvider, oldProvider], // New provider is consulted first
|
|
193
|
-
new FirstMatchStrategy()
|
|
191
|
+
new FirstMatchStrategy(),
|
|
194
192
|
);
|
|
195
193
|
|
|
196
194
|
await OpenFeature.setProviderAndWait(multiProvider);
|
|
@@ -206,13 +204,10 @@ const providerA = new ProviderA();
|
|
|
206
204
|
const providerB = new ProviderB();
|
|
207
205
|
|
|
208
206
|
const multiProvider = new MultiProvider(
|
|
209
|
-
[
|
|
210
|
-
{ provider: providerA },
|
|
211
|
-
{ provider: providerB }
|
|
212
|
-
],
|
|
207
|
+
[{ provider: providerA }, { provider: providerB }],
|
|
213
208
|
new ComparisonStrategy(providerA, (resolutions) => {
|
|
214
209
|
console.warn('Mismatch detected', resolutions);
|
|
215
|
-
})
|
|
210
|
+
}),
|
|
216
211
|
);
|
|
217
212
|
|
|
218
213
|
await OpenFeature.setProviderAndWait(multiProvider);
|
|
@@ -226,7 +221,7 @@ If the flag management system you're using supports targeting, you can provide t
|
|
|
226
221
|
|
|
227
222
|
```ts
|
|
228
223
|
// set a value to the global context
|
|
229
|
-
OpenFeature.setContext({ region:
|
|
224
|
+
OpenFeature.setContext({ region: 'us-east-1' });
|
|
230
225
|
|
|
231
226
|
// set a value to the client context
|
|
232
227
|
const client = OpenFeature.getClient();
|
|
@@ -236,7 +231,7 @@ client.setContext({ version: process.env.APP_VERSION });
|
|
|
236
231
|
const requestContext = {
|
|
237
232
|
targetingKey: req.session.id,
|
|
238
233
|
email: req.session.email,
|
|
239
|
-
product: req.productId
|
|
234
|
+
product: req.productId,
|
|
240
235
|
};
|
|
241
236
|
|
|
242
237
|
const boolValue = await client.getBooleanValue('some-flag', false, requestContext);
|
|
@@ -254,7 +249,7 @@ If the hook you're looking for hasn't been created yet, see the [develop a hook]
|
|
|
254
249
|
Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level.
|
|
255
250
|
|
|
256
251
|
```ts
|
|
257
|
-
import { OpenFeature } from
|
|
252
|
+
import { OpenFeature } from '@openfeature/server-sdk';
|
|
258
253
|
|
|
259
254
|
// add a hook globally, to run on all evaluations
|
|
260
255
|
OpenFeature.addHooks(new ExampleGlobalHook());
|
|
@@ -264,7 +259,7 @@ const client = OpenFeature.getClient();
|
|
|
264
259
|
client.addHooks(new ExampleClientHook());
|
|
265
260
|
|
|
266
261
|
// add a hook for this evaluation only
|
|
267
|
-
const boolValue = await client.getBooleanValue(
|
|
262
|
+
const boolValue = await client.getBooleanValue('bool-flag', false, { hooks: [new ExampleHook()] });
|
|
268
263
|
```
|
|
269
264
|
|
|
270
265
|
### Logging
|
|
@@ -274,7 +269,7 @@ This behavior can be overridden by passing a custom logger either globally or pe
|
|
|
274
269
|
A custom logger must implement the [Logger interface](../shared/src/logger/logger.ts).
|
|
275
270
|
|
|
276
271
|
```ts
|
|
277
|
-
import type { Logger } from
|
|
272
|
+
import type { Logger } from '@openfeature/server-sdk';
|
|
278
273
|
|
|
279
274
|
// The logger can be anything that conforms with the Logger interface
|
|
280
275
|
const logger: Logger = console;
|
|
@@ -294,28 +289,28 @@ A domain is a logical identifier which can be used to associate clients with a p
|
|
|
294
289
|
If a domain has no associated provider, the default provider is used.
|
|
295
290
|
|
|
296
291
|
```ts
|
|
297
|
-
import { OpenFeature, InMemoryProvider } from
|
|
292
|
+
import { OpenFeature, InMemoryProvider } from '@openfeature/server-sdk';
|
|
298
293
|
|
|
299
294
|
const myFlags = {
|
|
300
|
-
|
|
295
|
+
v2_enabled: {
|
|
301
296
|
variants: {
|
|
302
297
|
on: true,
|
|
303
|
-
off: false
|
|
298
|
+
off: false,
|
|
304
299
|
},
|
|
305
300
|
disabled: false,
|
|
306
|
-
defaultVariant:
|
|
307
|
-
}
|
|
301
|
+
defaultVariant: 'on',
|
|
302
|
+
},
|
|
308
303
|
};
|
|
309
304
|
|
|
310
305
|
// Registering the default provider
|
|
311
306
|
OpenFeature.setProvider(InMemoryProvider(myFlags));
|
|
312
307
|
// Registering a provider to a domain
|
|
313
|
-
OpenFeature.setProvider(
|
|
308
|
+
OpenFeature.setProvider('my-domain', new InMemoryProvider(someOtherFlags));
|
|
314
309
|
|
|
315
310
|
// A Client bound to the default provider
|
|
316
311
|
const clientWithDefault = OpenFeature.getClient();
|
|
317
312
|
// A Client bound to the InMemoryProvider provider
|
|
318
|
-
const domainScopedClient = OpenFeature.getClient(
|
|
313
|
+
const domainScopedClient = OpenFeature.getClient('my-domain');
|
|
319
314
|
```
|
|
320
315
|
|
|
321
316
|
Domains can be defined on a provider during registration.
|
|
@@ -352,22 +347,22 @@ Transaction context can be set where specific data is available (e.g. an auth se
|
|
|
352
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.
|
|
353
348
|
|
|
354
349
|
```ts
|
|
355
|
-
import express, { Request, Response, NextFunction } from
|
|
350
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
356
351
|
import { OpenFeature, AsyncLocalStorageTransactionContextPropagator } from '@openfeature/server-sdk';
|
|
357
352
|
|
|
358
|
-
OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContextPropagator())
|
|
353
|
+
OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContextPropagator());
|
|
359
354
|
|
|
360
355
|
/**
|
|
361
356
|
* This example is based on an express middleware.
|
|
362
357
|
*/
|
|
363
358
|
const app = express();
|
|
364
359
|
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
365
|
-
const ip = res.headers.get(
|
|
360
|
+
const ip = res.headers.get('X-Forwarded-For');
|
|
366
361
|
OpenFeature.setTransactionContext({ targetingKey: req.user.id, ipAddress: ip }, () => {
|
|
367
362
|
// The transaction context is used in any flag evaluation throughout the whole call chain of next
|
|
368
363
|
next();
|
|
369
364
|
});
|
|
370
|
-
})
|
|
365
|
+
});
|
|
371
366
|
```
|
|
372
367
|
|
|
373
368
|
### Tracking
|
|
@@ -393,7 +388,7 @@ This should only be called when your application is in the process of shutting d
|
|
|
393
388
|
```ts
|
|
394
389
|
import { OpenFeature } from '@openfeature/server-sdk';
|
|
395
390
|
|
|
396
|
-
await OpenFeature.close()
|
|
391
|
+
await OpenFeature.close();
|
|
397
392
|
```
|
|
398
393
|
|
|
399
394
|
## Extending
|
|
@@ -413,7 +408,7 @@ import {
|
|
|
413
408
|
Logger,
|
|
414
409
|
Provider,
|
|
415
410
|
ProviderEventEmitter,
|
|
416
|
-
ResolutionDetails
|
|
411
|
+
ResolutionDetails,
|
|
417
412
|
} from '@openfeature/server-sdk';
|
|
418
413
|
|
|
419
414
|
// implement the provider interface
|
|
@@ -425,16 +420,36 @@ class MyProvider implements Provider {
|
|
|
425
420
|
} as const;
|
|
426
421
|
// Optional provider managed hooks
|
|
427
422
|
hooks?: Hook[];
|
|
428
|
-
resolveBooleanEvaluation(
|
|
423
|
+
resolveBooleanEvaluation(
|
|
424
|
+
flagKey: string,
|
|
425
|
+
defaultValue: boolean,
|
|
426
|
+
context: EvaluationContext,
|
|
427
|
+
logger: Logger,
|
|
428
|
+
): Promise<ResolutionDetails<boolean>> {
|
|
429
429
|
// code to evaluate a boolean
|
|
430
430
|
}
|
|
431
|
-
resolveStringEvaluation(
|
|
431
|
+
resolveStringEvaluation(
|
|
432
|
+
flagKey: string,
|
|
433
|
+
defaultValue: string,
|
|
434
|
+
context: EvaluationContext,
|
|
435
|
+
logger: Logger,
|
|
436
|
+
): Promise<ResolutionDetails<string>> {
|
|
432
437
|
// code to evaluate a string
|
|
433
438
|
}
|
|
434
|
-
resolveNumberEvaluation(
|
|
439
|
+
resolveNumberEvaluation(
|
|
440
|
+
flagKey: string,
|
|
441
|
+
defaultValue: number,
|
|
442
|
+
context: EvaluationContext,
|
|
443
|
+
logger: Logger,
|
|
444
|
+
): Promise<ResolutionDetails<number>> {
|
|
435
445
|
// code to evaluate a number
|
|
436
446
|
}
|
|
437
|
-
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>> {
|
|
438
453
|
// code to evaluate an object
|
|
439
454
|
}
|
|
440
455
|
|
|
@@ -459,7 +474,7 @@ This can be a new repository or included in [the existing contrib repository](ht
|
|
|
459
474
|
Implement your own hook by conforming to the [Hook interface](../shared/src/hooks/hook.ts).
|
|
460
475
|
|
|
461
476
|
```ts
|
|
462
|
-
import type { Hook, HookContext, EvaluationDetails, FlagValue } from
|
|
477
|
+
import type { Hook, HookContext, EvaluationDetails, FlagValue } from '@openfeature/server-sdk';
|
|
463
478
|
|
|
464
479
|
export class MyHook implements Hook {
|
|
465
480
|
after(hookContext: HookContext, evaluationDetails: EvaluationDetails<FlagValue>) {
|