@openfeature/web-sdk 0.4.12 → 0.4.14
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 +33 -32
- package/dist/cjs/index.js +113 -84
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/index.js +117 -87
- package/dist/esm/index.js.map +4 -4
- package/dist/types.d.ts +85 -57
- 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.7.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/web-sdk-v0.4.
|
|
20
|
-
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.4.
|
|
19
|
+
<a href="https://github.com/open-feature/js-sdk/releases/tag/web-sdk-v0.4.14">
|
|
20
|
+
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.4.14&color=blue&style=for-the-badge" />
|
|
21
21
|
</a>
|
|
22
22
|
<!-- x-release-please-end -->
|
|
23
23
|
<br/>
|
|
@@ -89,16 +89,16 @@ See [here](https://open-feature.github.io/js-sdk/modules/_openfeature_web_sdk.ht
|
|
|
89
89
|
|
|
90
90
|
## 🌟 Features
|
|
91
91
|
|
|
92
|
-
| Status | Features
|
|
93
|
-
| ------ |
|
|
94
|
-
| ✅ | [Providers](#providers)
|
|
95
|
-
| ✅ | [Targeting](#targeting-and-context)
|
|
96
|
-
| ✅ | [Hooks](#hooks)
|
|
97
|
-
| ✅ | [Logging](#logging)
|
|
98
|
-
| ✅ | [
|
|
99
|
-
| ✅ | [Eventing](#eventing)
|
|
100
|
-
| ✅ | [Shutdown](#shutdown)
|
|
101
|
-
| ✅ | [Extending](#extending)
|
|
92
|
+
| Status | Features | Description |
|
|
93
|
+
| ------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
|
|
95
|
+
| ✅ | [Targeting](#targeting-and-context) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
|
|
96
|
+
| ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
|
|
97
|
+
| ✅ | [Logging](#logging) | Integrate with popular logging packages. |
|
|
98
|
+
| ✅ | [Domains](#domains) | Logically bind clients with providers. |
|
|
99
|
+
| ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
|
|
100
|
+
| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
|
|
101
|
+
| ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
|
|
102
102
|
|
|
103
103
|
<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
|
|
104
104
|
|
|
@@ -129,7 +129,7 @@ OpenFeature.setProvider(new MyProvider());
|
|
|
129
129
|
Once the provider has been registered, the status can be tracked using [events](#eventing).
|
|
130
130
|
|
|
131
131
|
In some situations, it may be beneficial to register multiple providers in the same application.
|
|
132
|
-
This is possible using [
|
|
132
|
+
This is possible using [domains](#domains), which is covered in more detail below.
|
|
133
133
|
|
|
134
134
|
### Flag evaluation flow
|
|
135
135
|
|
|
@@ -159,12 +159,13 @@ await OpenFeature.setContext({ origin: document.location.host });
|
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
Context is global and setting it is `async`.
|
|
162
|
-
Providers may implement an `onContextChanged` method that receives the old
|
|
163
|
-
|
|
162
|
+
Providers may implement an `onContextChanged` method that receives the old and newer contexts.
|
|
163
|
+
Given a context change, providers can use this method internally to detect if the flag values cached on the client are still valid.
|
|
164
|
+
If needed, a request will be made to the provider with the new context in order to get the correct flag values.
|
|
164
165
|
|
|
165
166
|
### Hooks
|
|
166
167
|
|
|
167
|
-
[Hooks](https://openfeature.dev/docs/reference/concepts/hooks) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle
|
|
168
|
+
[Hooks](https://openfeature.dev/docs/reference/concepts/hooks) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle.
|
|
168
169
|
Look [here](https://openfeature.dev/ecosystem/?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook&instant_search%5BrefinementList%5D%5Bcategory%5D%5B0%5D=Client-side&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=JavaScript) for a complete list of available hooks.
|
|
169
170
|
If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself.
|
|
170
171
|
|
|
@@ -186,7 +187,7 @@ const boolValue = client.getBooleanValue("bool-flag", false, { hooks: [new Examp
|
|
|
186
187
|
|
|
187
188
|
### Logging
|
|
188
189
|
|
|
189
|
-
The
|
|
190
|
+
The Web SDK will log warnings and errors to the console by default.
|
|
190
191
|
This behavior can be overridden by passing a custom logger either globally or per client.
|
|
191
192
|
A custom logger must implement the [Logger interface](../shared/src/logger/logger.ts).
|
|
192
193
|
|
|
@@ -204,26 +205,29 @@ const client = OpenFeature.getClient();
|
|
|
204
205
|
client.setLogger(logger);
|
|
205
206
|
```
|
|
206
207
|
|
|
207
|
-
###
|
|
208
|
+
### Domains
|
|
208
209
|
|
|
209
|
-
Clients can be
|
|
210
|
-
A
|
|
211
|
-
If a
|
|
210
|
+
Clients can be assigned to a domain.
|
|
211
|
+
A domain is a logical identifier which can be used to associate clients with a particular provider.
|
|
212
|
+
If a domain has no associated provider, the default provider is used.
|
|
212
213
|
|
|
213
214
|
```ts
|
|
214
|
-
import { OpenFeature } from "@openfeature/web-sdk";
|
|
215
|
+
import { OpenFeature, InMemoryProvider } from "@openfeature/web-sdk";
|
|
215
216
|
|
|
216
217
|
// Registering the default provider
|
|
217
|
-
OpenFeature.setProvider(
|
|
218
|
-
// Registering a
|
|
219
|
-
OpenFeature.setProvider("
|
|
218
|
+
OpenFeature.setProvider(InMemoryProvider(myFlags));
|
|
219
|
+
// Registering a provider to a domain
|
|
220
|
+
OpenFeature.setProvider("my-domain", new InMemoryProvider(someOtherFlags));
|
|
220
221
|
|
|
221
|
-
// A Client
|
|
222
|
+
// A Client bound to the default provider
|
|
222
223
|
const clientWithDefault = OpenFeature.getClient();
|
|
223
|
-
// A Client
|
|
224
|
-
const
|
|
224
|
+
// A Client bound to the InMemoryProvider provider
|
|
225
|
+
const domainScopedClient = OpenFeature.getClient("my-domain");
|
|
225
226
|
```
|
|
226
227
|
|
|
228
|
+
Domains can be defined on a provider during registration.
|
|
229
|
+
For more details, please refer to the [providers](#providers) section.
|
|
230
|
+
|
|
227
231
|
### Eventing
|
|
228
232
|
|
|
229
233
|
Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions.
|
|
@@ -275,14 +279,13 @@ import {
|
|
|
275
279
|
Logger,
|
|
276
280
|
Provider,
|
|
277
281
|
ProviderEventEmitter,
|
|
278
|
-
ProviderStatus,
|
|
279
282
|
ResolutionDetails
|
|
280
283
|
} from '@openfeature/web-sdk';
|
|
281
284
|
|
|
282
285
|
// implement the provider interface
|
|
283
286
|
class MyProvider implements Provider {
|
|
284
287
|
// Adds runtime validation that the provider is used with the expected SDK
|
|
285
|
-
public readonly runsOn = '
|
|
288
|
+
public readonly runsOn = 'client';
|
|
286
289
|
readonly metadata = {
|
|
287
290
|
name: 'My Provider',
|
|
288
291
|
} as const;
|
|
@@ -305,8 +308,6 @@ class MyProvider implements Provider {
|
|
|
305
308
|
// reconcile the provider's cached flags, if applicable
|
|
306
309
|
}
|
|
307
310
|
|
|
308
|
-
status?: ProviderStatus | undefined;
|
|
309
|
-
|
|
310
311
|
// implement with "new OpenFeatureEventEmitter()", and use "emit()" to emit events
|
|
311
312
|
events?: ProviderEventEmitter<AnyProviderEvent> | undefined;
|
|
312
313
|
|
package/dist/cjs/index.js
CHANGED
|
@@ -412,7 +412,8 @@ __export(src_exports, {
|
|
|
412
412
|
OpenFeatureAPI: () => OpenFeatureAPI,
|
|
413
413
|
OpenFeatureClient: () => OpenFeatureClient,
|
|
414
414
|
OpenFeatureEventEmitter: () => OpenFeatureEventEmitter,
|
|
415
|
-
ProviderEvents: () => import_core2.ClientProviderEvents
|
|
415
|
+
ProviderEvents: () => import_core2.ClientProviderEvents,
|
|
416
|
+
ProviderStatus: () => import_core3.ClientProviderStatus
|
|
416
417
|
});
|
|
417
418
|
module.exports = __toCommonJS(src_exports);
|
|
418
419
|
|
|
@@ -426,7 +427,7 @@ var import_core6 = require("@openfeature/core");
|
|
|
426
427
|
var import_core = require("@openfeature/core");
|
|
427
428
|
var import_events = __toESM(require_events());
|
|
428
429
|
var OpenFeatureEventEmitter = class extends import_core.GenericEventEmitter {
|
|
429
|
-
eventEmitter = new import_events.
|
|
430
|
+
eventEmitter = new import_events.EventEmitter({ captureRejections: true });
|
|
430
431
|
constructor() {
|
|
431
432
|
super();
|
|
432
433
|
this.eventEmitter.on("error", (err) => {
|
|
@@ -438,16 +439,15 @@ var OpenFeatureEventEmitter = class extends import_core.GenericEventEmitter {
|
|
|
438
439
|
// src/events/events.ts
|
|
439
440
|
var import_core2 = require("@openfeature/core");
|
|
440
441
|
|
|
441
|
-
// src/provider/
|
|
442
|
+
// src/provider/provider.ts
|
|
442
443
|
var import_core3 = require("@openfeature/core");
|
|
444
|
+
|
|
445
|
+
// src/provider/no-op-provider.ts
|
|
443
446
|
var REASON_NO_OP = "No-op";
|
|
444
447
|
var NoopFeatureProvider = class {
|
|
445
448
|
metadata = {
|
|
446
449
|
name: "No-op Provider"
|
|
447
450
|
};
|
|
448
|
-
get status() {
|
|
449
|
-
return import_core3.ProviderStatus.NOT_READY;
|
|
450
|
-
}
|
|
451
451
|
resolveBooleanEvaluation(_, defaultValue) {
|
|
452
452
|
return this.noOp(defaultValue);
|
|
453
453
|
}
|
|
@@ -474,11 +474,11 @@ var import_core5 = require("@openfeature/core");
|
|
|
474
474
|
|
|
475
475
|
// src/provider/in-memory-provider/variant-not-found-error.ts
|
|
476
476
|
var import_core4 = require("@openfeature/core");
|
|
477
|
-
var VariantNotFoundError = class extends import_core4.OpenFeatureError {
|
|
477
|
+
var VariantNotFoundError = class _VariantNotFoundError extends import_core4.OpenFeatureError {
|
|
478
478
|
code;
|
|
479
479
|
constructor(message) {
|
|
480
480
|
super(message);
|
|
481
|
-
Object.setPrototypeOf(this,
|
|
481
|
+
Object.setPrototypeOf(this, _VariantNotFoundError.prototype);
|
|
482
482
|
this.name = "VariantNotFoundError";
|
|
483
483
|
this.code = import_core4.ErrorCode.GENERAL;
|
|
484
484
|
}
|
|
@@ -488,7 +488,6 @@ var VariantNotFoundError = class extends import_core4.OpenFeatureError {
|
|
|
488
488
|
var InMemoryProvider = class {
|
|
489
489
|
events = new OpenFeatureEventEmitter();
|
|
490
490
|
runsOn = "client";
|
|
491
|
-
status = import_core5.ProviderStatus.NOT_READY;
|
|
492
491
|
metadata = {
|
|
493
492
|
name: "in-memory"
|
|
494
493
|
};
|
|
@@ -503,10 +502,8 @@ var InMemoryProvider = class {
|
|
|
503
502
|
this.resolveFlagWithReason(key, context);
|
|
504
503
|
}
|
|
505
504
|
this._context = context;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
this.status = import_core5.ProviderStatus.ERROR;
|
|
509
|
-
throw error;
|
|
505
|
+
} catch (err) {
|
|
506
|
+
throw new Error("initialization failure", { cause: err });
|
|
510
507
|
}
|
|
511
508
|
}
|
|
512
509
|
/**
|
|
@@ -515,13 +512,10 @@ var InMemoryProvider = class {
|
|
|
515
512
|
*/
|
|
516
513
|
async putConfiguration(flagConfiguration) {
|
|
517
514
|
const flagsChanged = Object.entries(flagConfiguration).filter(([key, value]) => this._flagConfiguration[key] !== value).map(([key]) => key);
|
|
518
|
-
this.status = import_core5.ProviderStatus.STALE;
|
|
519
|
-
this.events.emit(import_core2.ClientProviderEvents.Stale);
|
|
520
515
|
this._flagConfiguration = { ...flagConfiguration };
|
|
521
|
-
this.events.emit(import_core2.ClientProviderEvents.ConfigurationChanged, { flagsChanged });
|
|
522
516
|
try {
|
|
523
517
|
await this.initialize(this._context);
|
|
524
|
-
this.events.emit(import_core2.ClientProviderEvents.
|
|
518
|
+
this.events.emit(import_core2.ClientProviderEvents.ConfigurationChanged, { flagsChanged });
|
|
525
519
|
} catch (err) {
|
|
526
520
|
this.events.emit(import_core2.ClientProviderEvents.Error);
|
|
527
521
|
throw err;
|
|
@@ -575,8 +569,7 @@ var InMemoryProvider = class {
|
|
|
575
569
|
const isContextEval = ctx && flagSpec?.contextEvaluator;
|
|
576
570
|
const variant = isContextEval ? flagSpec.contextEvaluator?.(ctx) : flagSpec.defaultVariant;
|
|
577
571
|
const value = variant && flagSpec?.variants[variant];
|
|
578
|
-
const
|
|
579
|
-
const reason = this.status === import_core5.ProviderStatus.STALE ? import_core5.StandardResolutionReasons.CACHED : evalReason;
|
|
572
|
+
const reason = isContextEval ? import_core5.StandardResolutionReasons.TARGETING_MATCH : import_core5.StandardResolutionReasons.STATIC;
|
|
580
573
|
return {
|
|
581
574
|
value,
|
|
582
575
|
...variant && { variant },
|
|
@@ -588,11 +581,12 @@ var InMemoryProvider = class {
|
|
|
588
581
|
// src/open-feature.ts
|
|
589
582
|
var GLOBAL_OPENFEATURE_API_KEY = Symbol.for("@openfeature/web-sdk/api");
|
|
590
583
|
var _globalThis = globalThis;
|
|
591
|
-
var OpenFeatureAPI = class extends import_core6.OpenFeatureCommonAPI {
|
|
592
|
-
|
|
593
|
-
|
|
584
|
+
var OpenFeatureAPI = class _OpenFeatureAPI extends import_core6.OpenFeatureCommonAPI {
|
|
585
|
+
_statusEnumType = import_core3.ClientProviderStatus;
|
|
586
|
+
_apiEmitter = new OpenFeatureEventEmitter();
|
|
587
|
+
_defaultProvider = new import_core6.ProviderWrapper(NOOP_PROVIDER, import_core3.ClientProviderStatus.NOT_READY, this._statusEnumType);
|
|
588
|
+
_domainScopedProviders = /* @__PURE__ */ new Map();
|
|
594
589
|
_createEventEmitter = () => new OpenFeatureEventEmitter();
|
|
595
|
-
_namedProviderContext = /* @__PURE__ */ new Map();
|
|
596
590
|
constructor() {
|
|
597
591
|
super("client");
|
|
598
592
|
}
|
|
@@ -606,64 +600,70 @@ var OpenFeatureAPI = class extends import_core6.OpenFeatureCommonAPI {
|
|
|
606
600
|
if (globalApi) {
|
|
607
601
|
return globalApi;
|
|
608
602
|
}
|
|
609
|
-
const instance = new
|
|
603
|
+
const instance = new _OpenFeatureAPI();
|
|
610
604
|
_globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
|
|
611
605
|
return instance;
|
|
612
606
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
607
|
+
getProviderStatus(domain) {
|
|
608
|
+
if (!domain) {
|
|
609
|
+
return this._defaultProvider.status;
|
|
610
|
+
}
|
|
611
|
+
return this._domainScopedProviders.get(domain)?.status ?? this._defaultProvider.status;
|
|
612
|
+
}
|
|
613
|
+
async setContext(domainOrContext, contextOrUndefined) {
|
|
614
|
+
const domain = (0, import_core6.stringOrUndefined)(domainOrContext);
|
|
615
|
+
const context = (0, import_core6.objectOrUndefined)(domainOrContext) ?? (0, import_core6.objectOrUndefined)(contextOrUndefined) ?? {};
|
|
616
|
+
if (domain) {
|
|
617
|
+
const wrapper = this._domainScopedProviders.get(domain);
|
|
618
|
+
if (wrapper) {
|
|
619
|
+
const oldContext = this.getContext(domain);
|
|
620
|
+
this._domainScopedContext.set(domain, context);
|
|
621
|
+
await this.runProviderContextChangeHandler(domain, wrapper, oldContext, context);
|
|
622
622
|
} else {
|
|
623
|
-
this.
|
|
623
|
+
this._domainScopedContext.set(domain, context);
|
|
624
624
|
}
|
|
625
625
|
} else {
|
|
626
626
|
const oldContext = this._context;
|
|
627
627
|
this._context = context;
|
|
628
|
-
const
|
|
629
|
-
acc.push({
|
|
628
|
+
const unboundProviders = Array.from(this._domainScopedProviders.entries()).filter(([domain2]) => !this._domainScopedContext.has(domain2)).reduce((acc, [domain2, wrapper]) => {
|
|
629
|
+
acc.push({ domain: domain2, wrapper });
|
|
630
630
|
return acc;
|
|
631
631
|
}, []);
|
|
632
|
-
const
|
|
633
|
-
// add in the default (no
|
|
634
|
-
{
|
|
635
|
-
...
|
|
632
|
+
const allDomainRecords = [
|
|
633
|
+
// add in the default (no domain)
|
|
634
|
+
{ domain: void 0, wrapper: this._defaultProvider },
|
|
635
|
+
...unboundProviders
|
|
636
636
|
];
|
|
637
637
|
await Promise.all(
|
|
638
|
-
|
|
639
|
-
(
|
|
638
|
+
allDomainRecords.map(
|
|
639
|
+
(dm) => this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context)
|
|
640
640
|
)
|
|
641
641
|
);
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
|
-
getContext(
|
|
645
|
-
const
|
|
646
|
-
if (
|
|
647
|
-
const context = this.
|
|
644
|
+
getContext(domainOrUndefined) {
|
|
645
|
+
const domain = (0, import_core6.stringOrUndefined)(domainOrUndefined);
|
|
646
|
+
if (domain) {
|
|
647
|
+
const context = this._domainScopedContext.get(domain);
|
|
648
648
|
if (context) {
|
|
649
649
|
return context;
|
|
650
650
|
} else {
|
|
651
|
-
this._logger.debug(`Unable to find context for '${
|
|
651
|
+
this._logger.debug(`Unable to find context for '${domain}'.`);
|
|
652
652
|
}
|
|
653
653
|
}
|
|
654
654
|
return this._context;
|
|
655
655
|
}
|
|
656
|
-
async clearContext(
|
|
657
|
-
const
|
|
658
|
-
if (
|
|
659
|
-
const
|
|
660
|
-
if (
|
|
661
|
-
const oldContext = this.getContext(
|
|
662
|
-
this.
|
|
656
|
+
async clearContext(domainOrUndefined) {
|
|
657
|
+
const domain = (0, import_core6.stringOrUndefined)(domainOrUndefined);
|
|
658
|
+
if (domain) {
|
|
659
|
+
const wrapper = this._domainScopedProviders.get(domain);
|
|
660
|
+
if (wrapper) {
|
|
661
|
+
const oldContext = this.getContext(domain);
|
|
662
|
+
this._domainScopedContext.delete(domain);
|
|
663
663
|
const newContext = this.getContext();
|
|
664
|
-
await this.runProviderContextChangeHandler(
|
|
664
|
+
await this.runProviderContextChangeHandler(domain, wrapper, oldContext, newContext);
|
|
665
665
|
} else {
|
|
666
|
-
this.
|
|
666
|
+
this._domainScopedContext.delete(domain);
|
|
667
667
|
}
|
|
668
668
|
} else {
|
|
669
669
|
return this.setContext({});
|
|
@@ -671,11 +671,11 @@ var OpenFeatureAPI = class extends import_core6.OpenFeatureCommonAPI {
|
|
|
671
671
|
}
|
|
672
672
|
/**
|
|
673
673
|
* Resets the global evaluation context and removes the evaluation context for
|
|
674
|
-
* all
|
|
674
|
+
* all domains.
|
|
675
675
|
*/
|
|
676
676
|
async clearContexts() {
|
|
677
677
|
await this.clearContext();
|
|
678
|
-
await Promise.allSettled(Array.from(this.
|
|
678
|
+
await Promise.allSettled(Array.from(this._domainScopedProviders.keys()).map((domain) => this.clearContext(domain)));
|
|
679
679
|
}
|
|
680
680
|
/**
|
|
681
681
|
* A factory function for creating new named OpenFeature clients. Clients can contain
|
|
@@ -684,18 +684,19 @@ var OpenFeatureAPI = class extends import_core6.OpenFeatureCommonAPI {
|
|
|
684
684
|
*
|
|
685
685
|
* If there is already a provider bound to this name via {@link this.setProvider setProvider}, this provider will be used.
|
|
686
686
|
* Otherwise, the default provider is used until a provider is assigned to that name.
|
|
687
|
-
* @param {string}
|
|
687
|
+
* @param {string} domain An identifier which logically binds clients with providers
|
|
688
688
|
* @param {string} version The version of the client (only used for metadata)
|
|
689
689
|
* @returns {Client} OpenFeature Client
|
|
690
690
|
*/
|
|
691
|
-
getClient(
|
|
691
|
+
getClient(domain, version) {
|
|
692
692
|
return new OpenFeatureClient(
|
|
693
693
|
// functions are passed here to make sure that these values are always up to date,
|
|
694
694
|
// and so we don't have to make these public properties on the API class.
|
|
695
|
-
() => this.getProviderForClient(
|
|
696
|
-
() => this.
|
|
695
|
+
() => this.getProviderForClient(domain),
|
|
696
|
+
() => this.getProviderStatus(domain),
|
|
697
|
+
() => this.buildAndCacheEventEmitterForClient(domain),
|
|
697
698
|
() => this._logger,
|
|
698
|
-
{
|
|
699
|
+
{ domain, version }
|
|
699
700
|
);
|
|
700
701
|
}
|
|
701
702
|
/**
|
|
@@ -704,24 +705,40 @@ var OpenFeatureAPI = class extends import_core6.OpenFeatureCommonAPI {
|
|
|
704
705
|
*/
|
|
705
706
|
async clearProviders() {
|
|
706
707
|
await super.clearProvidersAndSetDefault(NOOP_PROVIDER);
|
|
707
|
-
this.
|
|
708
|
+
this._domainScopedContext.clear();
|
|
708
709
|
}
|
|
709
|
-
async runProviderContextChangeHandler(
|
|
710
|
-
const providerName = provider
|
|
710
|
+
async runProviderContextChangeHandler(domain, wrapper, oldContext, newContext) {
|
|
711
|
+
const providerName = wrapper.provider?.metadata?.name || "unnamed-provider";
|
|
711
712
|
try {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
713
|
+
if (typeof wrapper.provider.onContextChange === "function") {
|
|
714
|
+
wrapper.incrementPendingContextChanges();
|
|
715
|
+
wrapper.status = this._statusEnumType.RECONCILING;
|
|
716
|
+
this.getAssociatedEventEmitters(domain).forEach((emitter) => {
|
|
717
|
+
emitter?.emit(import_core2.ClientProviderEvents.Reconciling, { domain, providerName });
|
|
718
|
+
});
|
|
719
|
+
this._apiEmitter?.emit(import_core2.ClientProviderEvents.Reconciling, { domain, providerName });
|
|
720
|
+
await wrapper.provider.onContextChange(oldContext, newContext);
|
|
721
|
+
wrapper.decrementPendingContextChanges();
|
|
722
|
+
}
|
|
723
|
+
wrapper.status = this._statusEnumType.READY;
|
|
724
|
+
if (wrapper.allContextChangesSettled) {
|
|
725
|
+
this.getAssociatedEventEmitters(domain).forEach((emitter) => {
|
|
726
|
+
emitter?.emit(import_core2.ClientProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
|
|
727
|
+
});
|
|
728
|
+
this._apiEmitter?.emit(import_core2.ClientProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
|
|
729
|
+
}
|
|
717
730
|
} catch (err) {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
731
|
+
wrapper.decrementPendingContextChanges();
|
|
732
|
+
wrapper.status = this._statusEnumType.ERROR;
|
|
733
|
+
if (wrapper.allContextChangesSettled) {
|
|
734
|
+
const error = err;
|
|
735
|
+
const message = `Error running ${providerName}'s context change handler: ${error?.message}`;
|
|
736
|
+
this._logger?.error(`${message}`, err);
|
|
737
|
+
this.getAssociatedEventEmitters(domain).forEach((emitter) => {
|
|
738
|
+
emitter?.emit(import_core2.ClientProviderEvents.Error, { clientName: domain, domain, providerName, message });
|
|
739
|
+
});
|
|
740
|
+
this._apiEmitter?.emit(import_core2.ClientProviderEvents.Error, { clientName: domain, domain, providerName, message });
|
|
741
|
+
}
|
|
725
742
|
}
|
|
726
743
|
}
|
|
727
744
|
};
|
|
@@ -729,8 +746,9 @@ var OpenFeature = OpenFeatureAPI.getInstance();
|
|
|
729
746
|
|
|
730
747
|
// src/client/open-feature-client.ts
|
|
731
748
|
var OpenFeatureClient = class {
|
|
732
|
-
constructor(providerAccessor, emitterAccessor, globalLogger, options) {
|
|
749
|
+
constructor(providerAccessor, providerStatusAccessor, emitterAccessor, globalLogger, options) {
|
|
733
750
|
this.providerAccessor = providerAccessor;
|
|
751
|
+
this.providerStatusAccessor = providerStatusAccessor;
|
|
734
752
|
this.emitterAccessor = emitterAccessor;
|
|
735
753
|
this.globalLogger = globalLogger;
|
|
736
754
|
this.options = options;
|
|
@@ -739,20 +757,26 @@ var OpenFeatureClient = class {
|
|
|
739
757
|
_clientLogger;
|
|
740
758
|
get metadata() {
|
|
741
759
|
return {
|
|
742
|
-
name
|
|
760
|
+
// Use domain if name is not provided
|
|
761
|
+
name: this.options.domain ?? this.options.name,
|
|
762
|
+
domain: this.options.domain ?? this.options.name,
|
|
743
763
|
version: this.options.version,
|
|
744
764
|
providerMetadata: this.providerAccessor().metadata
|
|
745
765
|
};
|
|
746
766
|
}
|
|
747
767
|
get providerStatus() {
|
|
748
|
-
return this.
|
|
768
|
+
return this.providerStatusAccessor();
|
|
749
769
|
}
|
|
750
770
|
addHandler(eventType, handler) {
|
|
751
771
|
this.emitterAccessor().addHandler(eventType, handler);
|
|
752
|
-
const shouldRunNow = (0, import_core7.statusMatchesEvent)(eventType, this.
|
|
772
|
+
const shouldRunNow = (0, import_core7.statusMatchesEvent)(eventType, this.providerStatus);
|
|
753
773
|
if (shouldRunNow) {
|
|
754
774
|
try {
|
|
755
|
-
handler({
|
|
775
|
+
handler({
|
|
776
|
+
clientName: this.metadata.name,
|
|
777
|
+
domain: this.metadata.domain,
|
|
778
|
+
providerName: this._provider.metadata.name
|
|
779
|
+
});
|
|
756
780
|
} catch (err) {
|
|
757
781
|
this._logger?.error("Error running event handler:", err);
|
|
758
782
|
}
|
|
@@ -826,7 +850,7 @@ var OpenFeatureClient = class {
|
|
|
826
850
|
];
|
|
827
851
|
const allHooksReversed = [...allHooks].reverse();
|
|
828
852
|
const context = {
|
|
829
|
-
...OpenFeature.getContext(this?.options?.
|
|
853
|
+
...OpenFeature.getContext(this?.options?.domain)
|
|
830
854
|
};
|
|
831
855
|
const hookContext = {
|
|
832
856
|
flagKey,
|
|
@@ -839,6 +863,11 @@ var OpenFeatureClient = class {
|
|
|
839
863
|
};
|
|
840
864
|
try {
|
|
841
865
|
this.beforeHooks(allHooks, hookContext, options);
|
|
866
|
+
if (this.providerStatus === import_core3.ClientProviderStatus.NOT_READY) {
|
|
867
|
+
throw new import_core7.ProviderNotReadyError("provider has not yet initialized");
|
|
868
|
+
} else if (this.providerStatus === import_core3.ClientProviderStatus.FATAL) {
|
|
869
|
+
throw new import_core7.ProviderFatalError("provider is in an irrecoverable error state");
|
|
870
|
+
}
|
|
842
871
|
const resolution = resolver.call(this._provider, flagKey, defaultValue, context, this._logger);
|
|
843
872
|
const evaluationDetails = {
|
|
844
873
|
...resolution,
|