@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 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.1">
20
- <img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.20.1&color=blue&style=for-the-badge" />
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("v2 is enabled");
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
- | ✅ | [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. |
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: "us-east-1" });
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 "@openfeature/server-sdk";
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("bool-flag", false, { hooks: [new ExampleHook()]});
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 "@openfeature/server-sdk";
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, InMemoryProvider } from "@openfeature/server-sdk";
296
+ import { OpenFeature, TypedInMemoryProvider } from '@openfeature/server-sdk';
299
297
 
300
298
  const myFlags = {
301
- 'v2_enabled': {
299
+ v2_enabled: {
302
300
  variants: {
303
301
  on: true,
304
- off: false
302
+ off: false,
305
303
  },
306
304
  disabled: false,
307
- defaultVariant: "on"
308
- }
309
- };
305
+ defaultVariant: 'on',
306
+ },
307
+ } as const;
310
308
 
311
309
  // Registering the default provider
312
- OpenFeature.setProvider(InMemoryProvider(myFlags));
310
+ OpenFeature.setProvider(new TypedInMemoryProvider(myFlags));
313
311
  // Registering a provider to a domain
314
- OpenFeature.setProvider("my-domain", new InMemoryProvider(someOtherFlags));
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 InMemoryProvider provider
319
- const domainScopedClient = OpenFeature.getClient("my-domain");
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 "express";
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 = res.headers.get("X-Forwarded-For")
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(flagKey: string, defaultValue: boolean, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<boolean>> {
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(flagKey: string, defaultValue: string, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<string>> {
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(flagKey: string, defaultValue: number, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<number>> {
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>(flagKey: string, defaultValue: T, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<T>> {
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 "@openfeature/server-sdk";
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>) {