@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 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.12">
20
- <img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.4.12&color=blue&style=for-the-badge" />
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 | 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
- | ✅ | [Named clients](#named-clients) | Utilize multiple providers in a single application. |
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. |
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 [named clients](#named-clients), which is covered in more detail below.
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 context and the newer one.
163
- This method is used internally by the provider to detect if, given the context change, the flags values cached on client side are invalid. If needed a request will be made to the provider with the new context in order to get the correct flags values.
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 JS SDK will log warnings and errors to the console by default.
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
- ### Named clients
208
+ ### Domains
208
209
 
209
- Clients can be given a name.
210
- A name is a logical identifier that can be used to associate clients with a particular provider.
211
- If a name has no associated provider, the global provider is used.
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(NewLocalProvider());
218
- // Registering a named provider
219
- OpenFeature.setProvider("clientForCache", new NewCachedProvider());
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 backed by default provider
222
+ // A Client bound to the default provider
222
223
  const clientWithDefault = OpenFeature.getClient();
223
- // A Client backed by NewCachedProvider
224
- const clientForCache = OpenFeature.getClient("clientForCache");
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 = 'server';
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.default({ captureRejections: true });
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/no-op-provider.ts
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, VariantNotFoundError.prototype);
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
- this.status = import_core5.ProviderStatus.READY;
507
- } catch (error) {
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.Ready);
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 evalReason = isContextEval ? import_core5.StandardResolutionReasons.TARGETING_MATCH : import_core5.StandardResolutionReasons.STATIC;
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
- _events = new OpenFeatureEventEmitter();
593
- _defaultProvider = NOOP_PROVIDER;
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 OpenFeatureAPI();
603
+ const instance = new _OpenFeatureAPI();
610
604
  _globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
611
605
  return instance;
612
606
  }
613
- async setContext(nameOrContext, contextOrUndefined) {
614
- const clientName = (0, import_core6.stringOrUndefined)(nameOrContext);
615
- const context = (0, import_core6.objectOrUndefined)(nameOrContext) ?? (0, import_core6.objectOrUndefined)(contextOrUndefined) ?? {};
616
- if (clientName) {
617
- const provider = this._clientProviders.get(clientName);
618
- if (provider) {
619
- const oldContext = this.getContext(clientName);
620
- this._namedProviderContext.set(clientName, context);
621
- await this.runProviderContextChangeHandler(clientName, provider, oldContext, context);
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._namedProviderContext.set(clientName, context);
623
+ this._domainScopedContext.set(domain, context);
624
624
  }
625
625
  } else {
626
626
  const oldContext = this._context;
627
627
  this._context = context;
628
- const defaultContextNameProviders = Array.from(this._clientProviders.entries()).filter(([name]) => !this._namedProviderContext.has(name)).reduce((acc, [name, provider]) => {
629
- acc.push({ name, provider });
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 allProviders = [
633
- // add in the default (no name)
634
- { name: void 0, provider: this._defaultProvider },
635
- ...defaultContextNameProviders
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
- allProviders.map(
639
- (tuple) => this.runProviderContextChangeHandler(tuple.name, tuple.provider, oldContext, context)
638
+ allDomainRecords.map(
639
+ (dm) => this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context)
640
640
  )
641
641
  );
642
642
  }
643
643
  }
644
- getContext(nameOrUndefined) {
645
- const clientName = (0, import_core6.stringOrUndefined)(nameOrUndefined);
646
- if (clientName) {
647
- const context = this._namedProviderContext.get(clientName);
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 '${clientName}'.`);
651
+ this._logger.debug(`Unable to find context for '${domain}'.`);
652
652
  }
653
653
  }
654
654
  return this._context;
655
655
  }
656
- async clearContext(nameOrUndefined) {
657
- const clientName = (0, import_core6.stringOrUndefined)(nameOrUndefined);
658
- if (clientName) {
659
- const provider = this._clientProviders.get(clientName);
660
- if (provider) {
661
- const oldContext = this.getContext(clientName);
662
- this._namedProviderContext.delete(clientName);
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(clientName, provider, oldContext, newContext);
664
+ await this.runProviderContextChangeHandler(domain, wrapper, oldContext, newContext);
665
665
  } else {
666
- this._namedProviderContext.delete(clientName);
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 named clients.
674
+ * all domains.
675
675
  */
676
676
  async clearContexts() {
677
677
  await this.clearContext();
678
- await Promise.allSettled(Array.from(this._clientProviders.keys()).map((name) => this.clearContext(name)));
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} name The name of the client
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(name, version) {
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(name),
696
- () => this.buildAndCacheEventEmitterForClient(name),
695
+ () => this.getProviderForClient(domain),
696
+ () => this.getProviderStatus(domain),
697
+ () => this.buildAndCacheEventEmitterForClient(domain),
697
698
  () => this._logger,
698
- { name, version }
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._namedProviderContext.clear();
708
+ this._domainScopedContext.clear();
708
709
  }
709
- async runProviderContextChangeHandler(clientName, provider, oldContext, newContext) {
710
- const providerName = provider.metadata.name;
710
+ async runProviderContextChangeHandler(domain, wrapper, oldContext, newContext) {
711
+ const providerName = wrapper.provider?.metadata?.name || "unnamed-provider";
711
712
  try {
712
- await provider.onContextChange?.(oldContext, newContext);
713
- this.getAssociatedEventEmitters(clientName).forEach((emitter) => {
714
- emitter?.emit(import_core2.ClientProviderEvents.ContextChanged, { clientName, providerName });
715
- });
716
- this._events?.emit(import_core2.ClientProviderEvents.ContextChanged, { clientName, providerName });
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
- const error = err;
719
- const message = `Error running ${provider?.metadata?.name}'s context change handler: ${error?.message}`;
720
- this._logger?.error(`${message}`, err);
721
- this.getAssociatedEventEmitters(clientName).forEach((emitter) => {
722
- emitter?.emit(import_core2.ClientProviderEvents.Error, { clientName, providerName, message });
723
- });
724
- this._events?.emit(import_core2.ClientProviderEvents.Error, { clientName, providerName, message });
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: this.options.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.providerAccessor()?.status || import_core7.ProviderStatus.READY;
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._provider.status);
772
+ const shouldRunNow = (0, import_core7.statusMatchesEvent)(eventType, this.providerStatus);
753
773
  if (shouldRunNow) {
754
774
  try {
755
- handler({ clientName: this.metadata.name, providerName: this._provider.metadata.name });
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?.name)
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,