@openfeature/server-sdk 1.20.1 → 1.21.0
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 +74 -52
- package/dist/cjs/index.js +88 -266
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/index.js +77 -246
- package/dist/esm/index.js.map +4 -4
- package/dist/types.d.ts +190 -145
- package/package.json +5 -4
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
|
-
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.
|
|
19
|
+
<a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.21.0">
|
|
20
|
+
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.21.0&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
|
|
|
@@ -163,8 +163,8 @@ const backupProvider = new YourBackupProvider();
|
|
|
163
163
|
|
|
164
164
|
// Create multi-provider with a strategy
|
|
165
165
|
const multiProvider = new MultiProvider(
|
|
166
|
-
[primaryProvider, backupProvider],
|
|
167
|
-
new FirstMatchStrategy()
|
|
166
|
+
[{ provider: primaryProvider }, { provider: backupProvider }],
|
|
167
|
+
new FirstMatchStrategy(),
|
|
168
168
|
);
|
|
169
169
|
|
|
170
170
|
// Register the multi-provider
|
|
@@ -177,7 +177,8 @@ const value = await client.getBooleanValue('my-flag', false);
|
|
|
177
177
|
|
|
178
178
|
**Available Strategies:**
|
|
179
179
|
|
|
180
|
-
- `FirstMatchStrategy`: Returns the first successful result from the list of providers
|
|
180
|
+
- `FirstMatchStrategy`: Returns the first successful result from the list of providers (short-circuits on error)
|
|
181
|
+
- `FirstSuccessfulStrategy`: Returns the first successful result, ignoring errors from earlier providers
|
|
181
182
|
- `ComparisonStrategy`: Compares results from multiple providers and can handle discrepancies
|
|
182
183
|
|
|
183
184
|
**Migration Example:**
|
|
@@ -190,8 +191,8 @@ const newProvider = new NewFlagProvider();
|
|
|
190
191
|
const oldProvider = new OldFlagProvider();
|
|
191
192
|
|
|
192
193
|
const multiProvider = new MultiProvider(
|
|
193
|
-
[newProvider, oldProvider], // New provider is consulted first
|
|
194
|
-
new FirstMatchStrategy()
|
|
194
|
+
[{ provider: newProvider }, { provider: oldProvider }], // New provider is consulted first
|
|
195
|
+
new FirstMatchStrategy(),
|
|
195
196
|
);
|
|
196
197
|
|
|
197
198
|
await OpenFeature.setProviderAndWait(multiProvider);
|
|
@@ -207,13 +208,10 @@ const providerA = new ProviderA();
|
|
|
207
208
|
const providerB = new ProviderB();
|
|
208
209
|
|
|
209
210
|
const multiProvider = new MultiProvider(
|
|
210
|
-
[
|
|
211
|
-
{ provider: providerA },
|
|
212
|
-
{ provider: providerB }
|
|
213
|
-
],
|
|
211
|
+
[{ provider: providerA }, { provider: providerB }],
|
|
214
212
|
new ComparisonStrategy(providerA, (resolutions) => {
|
|
215
213
|
console.warn('Mismatch detected', resolutions);
|
|
216
|
-
})
|
|
214
|
+
}),
|
|
217
215
|
);
|
|
218
216
|
|
|
219
217
|
await OpenFeature.setProviderAndWait(multiProvider);
|
|
@@ -227,7 +225,7 @@ If the flag management system you're using supports targeting, you can provide t
|
|
|
227
225
|
|
|
228
226
|
```ts
|
|
229
227
|
// set a value to the global context
|
|
230
|
-
OpenFeature.setContext({ region:
|
|
228
|
+
OpenFeature.setContext({ region: 'us-east-1' });
|
|
231
229
|
|
|
232
230
|
// set a value to the client context
|
|
233
231
|
const client = OpenFeature.getClient();
|
|
@@ -237,7 +235,7 @@ client.setContext({ version: process.env.APP_VERSION });
|
|
|
237
235
|
const requestContext = {
|
|
238
236
|
targetingKey: req.session.id,
|
|
239
237
|
email: req.session.email,
|
|
240
|
-
product: req.productId
|
|
238
|
+
product: req.productId,
|
|
241
239
|
};
|
|
242
240
|
|
|
243
241
|
const boolValue = await client.getBooleanValue('some-flag', false, requestContext);
|
|
@@ -255,7 +253,7 @@ If the hook you're looking for hasn't been created yet, see the [develop a hook]
|
|
|
255
253
|
Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level.
|
|
256
254
|
|
|
257
255
|
```ts
|
|
258
|
-
import { OpenFeature } from
|
|
256
|
+
import { OpenFeature } from '@openfeature/server-sdk';
|
|
259
257
|
|
|
260
258
|
// add a hook globally, to run on all evaluations
|
|
261
259
|
OpenFeature.addHooks(new ExampleGlobalHook());
|
|
@@ -265,7 +263,7 @@ const client = OpenFeature.getClient();
|
|
|
265
263
|
client.addHooks(new ExampleClientHook());
|
|
266
264
|
|
|
267
265
|
// add a hook for this evaluation only
|
|
268
|
-
const boolValue = await client.getBooleanValue(
|
|
266
|
+
const boolValue = await client.getBooleanValue('bool-flag', false, { hooks: [new ExampleHook()] });
|
|
269
267
|
```
|
|
270
268
|
|
|
271
269
|
### Logging
|
|
@@ -275,7 +273,7 @@ This behavior can be overridden by passing a custom logger either globally or pe
|
|
|
275
273
|
A custom logger must implement the [Logger interface](../shared/src/logger/logger.ts).
|
|
276
274
|
|
|
277
275
|
```ts
|
|
278
|
-
import type { Logger } from
|
|
276
|
+
import type { Logger } from '@openfeature/server-sdk';
|
|
279
277
|
|
|
280
278
|
// The logger can be anything that conforms with the Logger interface
|
|
281
279
|
const logger: Logger = console;
|
|
@@ -295,28 +293,28 @@ A domain is a logical identifier which can be used to associate clients with a p
|
|
|
295
293
|
If a domain has no associated provider, the default provider is used.
|
|
296
294
|
|
|
297
295
|
```ts
|
|
298
|
-
import { OpenFeature,
|
|
296
|
+
import { OpenFeature, TypedInMemoryProvider } from '@openfeature/server-sdk';
|
|
299
297
|
|
|
300
298
|
const myFlags = {
|
|
301
|
-
|
|
299
|
+
v2_enabled: {
|
|
302
300
|
variants: {
|
|
303
301
|
on: true,
|
|
304
|
-
off: false
|
|
302
|
+
off: false,
|
|
305
303
|
},
|
|
306
304
|
disabled: false,
|
|
307
|
-
defaultVariant:
|
|
308
|
-
}
|
|
309
|
-
};
|
|
305
|
+
defaultVariant: 'on',
|
|
306
|
+
},
|
|
307
|
+
} as const;
|
|
310
308
|
|
|
311
309
|
// Registering the default provider
|
|
312
|
-
OpenFeature.setProvider(
|
|
310
|
+
OpenFeature.setProvider(new TypedInMemoryProvider(myFlags));
|
|
313
311
|
// Registering a provider to a domain
|
|
314
|
-
OpenFeature.setProvider(
|
|
312
|
+
OpenFeature.setProvider('my-domain', new TypedInMemoryProvider(someOtherFlags));
|
|
315
313
|
|
|
316
314
|
// A Client bound to the default provider
|
|
317
315
|
const clientWithDefault = OpenFeature.getClient();
|
|
318
|
-
// A Client bound to the
|
|
319
|
-
const domainScopedClient = OpenFeature.getClient(
|
|
316
|
+
// A Client bound to the TypedInMemoryProvider provider
|
|
317
|
+
const domainScopedClient = OpenFeature.getClient('my-domain');
|
|
320
318
|
```
|
|
321
319
|
|
|
322
320
|
Domains can be defined on a provider during registration.
|
|
@@ -353,22 +351,22 @@ Transaction context can be set where specific data is available (e.g. an auth se
|
|
|
353
351
|
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
352
|
|
|
355
353
|
```ts
|
|
356
|
-
import express, { Request, Response, NextFunction } from
|
|
354
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
357
355
|
import { OpenFeature, AsyncLocalStorageTransactionContextPropagator } from '@openfeature/server-sdk';
|
|
358
356
|
|
|
359
|
-
OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContextPropagator())
|
|
357
|
+
OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContextPropagator());
|
|
360
358
|
|
|
361
359
|
/**
|
|
362
360
|
* This example is based on an express middleware.
|
|
363
361
|
*/
|
|
364
362
|
const app = express();
|
|
365
363
|
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
366
|
-
const ip =
|
|
364
|
+
const ip = req.headers['x-forwarded-for'];
|
|
367
365
|
OpenFeature.setTransactionContext({ targetingKey: req.user.id, ipAddress: ip }, () => {
|
|
368
366
|
// The transaction context is used in any flag evaluation throughout the whole call chain of next
|
|
369
367
|
next();
|
|
370
368
|
});
|
|
371
|
-
})
|
|
369
|
+
});
|
|
372
370
|
```
|
|
373
371
|
|
|
374
372
|
### Tracking
|
|
@@ -394,9 +392,13 @@ This should only be called when your application is in the process of shutting d
|
|
|
394
392
|
```ts
|
|
395
393
|
import { OpenFeature } from '@openfeature/server-sdk';
|
|
396
394
|
|
|
397
|
-
await OpenFeature.close()
|
|
395
|
+
await OpenFeature.close();
|
|
398
396
|
```
|
|
399
397
|
|
|
398
|
+
### Type-Safe Flag Keys
|
|
399
|
+
|
|
400
|
+
For enhanced type safety and autocompletion, you can override flag key types using TypeScript module augmentation. See the [`@openfeature/core` README](../shared/README.md#type-safe-flag-keys) for details.
|
|
401
|
+
|
|
400
402
|
## Extending
|
|
401
403
|
|
|
402
404
|
### Develop a provider
|
|
@@ -414,7 +416,7 @@ import {
|
|
|
414
416
|
Logger,
|
|
415
417
|
Provider,
|
|
416
418
|
ProviderEventEmitter,
|
|
417
|
-
ResolutionDetails
|
|
419
|
+
ResolutionDetails,
|
|
418
420
|
} from '@openfeature/server-sdk';
|
|
419
421
|
|
|
420
422
|
// implement the provider interface
|
|
@@ -426,16 +428,36 @@ class MyProvider implements Provider {
|
|
|
426
428
|
} as const;
|
|
427
429
|
// Optional provider managed hooks
|
|
428
430
|
hooks?: Hook[];
|
|
429
|
-
resolveBooleanEvaluation(
|
|
431
|
+
resolveBooleanEvaluation(
|
|
432
|
+
flagKey: string,
|
|
433
|
+
defaultValue: boolean,
|
|
434
|
+
context: EvaluationContext,
|
|
435
|
+
logger: Logger,
|
|
436
|
+
): Promise<ResolutionDetails<boolean>> {
|
|
430
437
|
// code to evaluate a boolean
|
|
431
438
|
}
|
|
432
|
-
resolveStringEvaluation(
|
|
439
|
+
resolveStringEvaluation(
|
|
440
|
+
flagKey: string,
|
|
441
|
+
defaultValue: string,
|
|
442
|
+
context: EvaluationContext,
|
|
443
|
+
logger: Logger,
|
|
444
|
+
): Promise<ResolutionDetails<string>> {
|
|
433
445
|
// code to evaluate a string
|
|
434
446
|
}
|
|
435
|
-
resolveNumberEvaluation(
|
|
447
|
+
resolveNumberEvaluation(
|
|
448
|
+
flagKey: string,
|
|
449
|
+
defaultValue: number,
|
|
450
|
+
context: EvaluationContext,
|
|
451
|
+
logger: Logger,
|
|
452
|
+
): Promise<ResolutionDetails<number>> {
|
|
436
453
|
// code to evaluate a number
|
|
437
454
|
}
|
|
438
|
-
resolveObjectEvaluation<T extends JsonValue>(
|
|
455
|
+
resolveObjectEvaluation<T extends JsonValue>(
|
|
456
|
+
flagKey: string,
|
|
457
|
+
defaultValue: T,
|
|
458
|
+
context: EvaluationContext,
|
|
459
|
+
logger: Logger,
|
|
460
|
+
): Promise<ResolutionDetails<T>> {
|
|
439
461
|
// code to evaluate an object
|
|
440
462
|
}
|
|
441
463
|
|
|
@@ -460,7 +482,7 @@ This can be a new repository or included in [the existing contrib repository](ht
|
|
|
460
482
|
Implement your own hook by conforming to the [Hook interface](../shared/src/hooks/hook.ts).
|
|
461
483
|
|
|
462
484
|
```ts
|
|
463
|
-
import type { Hook, HookContext, EvaluationDetails, FlagValue } from
|
|
485
|
+
import type { Hook, HookContext, EvaluationDetails, FlagValue } from '@openfeature/server-sdk';
|
|
464
486
|
|
|
465
487
|
export class MyHook implements Hook {
|
|
466
488
|
after(hookContext: HookContext, evaluationDetails: EvaluationDetails<FlagValue>) {
|