@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/dist/esm/index.js CHANGED
@@ -399,7 +399,8 @@ var require_events = __commonJS({
399
399
  // src/client/open-feature-client.ts
400
400
  import {
401
401
  ErrorCode as ErrorCode2,
402
- ProviderStatus as ProviderStatus3,
402
+ ProviderFatalError,
403
+ ProviderNotReadyError,
403
404
  SafeLogger,
404
405
  StandardResolutionReasons as StandardResolutionReasons2,
405
406
  statusMatchesEvent
@@ -408,6 +409,7 @@ import {
408
409
  // src/open-feature.ts
409
410
  import {
410
411
  OpenFeatureCommonAPI,
412
+ ProviderWrapper,
411
413
  objectOrUndefined,
412
414
  stringOrUndefined
413
415
  } from "@openfeature/core";
@@ -416,7 +418,7 @@ import {
416
418
  var import_events = __toESM(require_events());
417
419
  import { GenericEventEmitter } from "@openfeature/core";
418
420
  var OpenFeatureEventEmitter = class extends GenericEventEmitter {
419
- eventEmitter = new import_events.default({ captureRejections: true });
421
+ eventEmitter = new import_events.EventEmitter({ captureRejections: true });
420
422
  constructor() {
421
423
  super();
422
424
  this.eventEmitter.on("error", (err) => {
@@ -428,16 +430,15 @@ var OpenFeatureEventEmitter = class extends GenericEventEmitter {
428
430
  // src/events/events.ts
429
431
  import { ClientProviderEvents } from "@openfeature/core";
430
432
 
433
+ // src/provider/provider.ts
434
+ import { ClientProviderStatus } from "@openfeature/core";
435
+
431
436
  // src/provider/no-op-provider.ts
432
- import { ProviderStatus } from "@openfeature/core";
433
437
  var REASON_NO_OP = "No-op";
434
438
  var NoopFeatureProvider = class {
435
439
  metadata = {
436
440
  name: "No-op Provider"
437
441
  };
438
- get status() {
439
- return ProviderStatus.NOT_READY;
440
- }
441
442
  resolveBooleanEvaluation(_, defaultValue) {
442
443
  return this.noOp(defaultValue);
443
444
  }
@@ -465,17 +466,16 @@ import {
465
466
  GeneralError,
466
467
  OpenFeatureError as OpenFeatureError2,
467
468
  StandardResolutionReasons,
468
- TypeMismatchError,
469
- ProviderStatus as ProviderStatus2
469
+ TypeMismatchError
470
470
  } from "@openfeature/core";
471
471
 
472
472
  // src/provider/in-memory-provider/variant-not-found-error.ts
473
473
  import { ErrorCode, OpenFeatureError } from "@openfeature/core";
474
- var VariantNotFoundError = class extends OpenFeatureError {
474
+ var VariantNotFoundError = class _VariantNotFoundError extends OpenFeatureError {
475
475
  code;
476
476
  constructor(message) {
477
477
  super(message);
478
- Object.setPrototypeOf(this, VariantNotFoundError.prototype);
478
+ Object.setPrototypeOf(this, _VariantNotFoundError.prototype);
479
479
  this.name = "VariantNotFoundError";
480
480
  this.code = ErrorCode.GENERAL;
481
481
  }
@@ -485,7 +485,6 @@ var VariantNotFoundError = class extends OpenFeatureError {
485
485
  var InMemoryProvider = class {
486
486
  events = new OpenFeatureEventEmitter();
487
487
  runsOn = "client";
488
- status = ProviderStatus2.NOT_READY;
489
488
  metadata = {
490
489
  name: "in-memory"
491
490
  };
@@ -500,10 +499,8 @@ var InMemoryProvider = class {
500
499
  this.resolveFlagWithReason(key, context);
501
500
  }
502
501
  this._context = context;
503
- this.status = ProviderStatus2.READY;
504
- } catch (error) {
505
- this.status = ProviderStatus2.ERROR;
506
- throw error;
502
+ } catch (err) {
503
+ throw new Error("initialization failure", { cause: err });
507
504
  }
508
505
  }
509
506
  /**
@@ -512,13 +509,10 @@ var InMemoryProvider = class {
512
509
  */
513
510
  async putConfiguration(flagConfiguration) {
514
511
  const flagsChanged = Object.entries(flagConfiguration).filter(([key, value]) => this._flagConfiguration[key] !== value).map(([key]) => key);
515
- this.status = ProviderStatus2.STALE;
516
- this.events.emit(ClientProviderEvents.Stale);
517
512
  this._flagConfiguration = { ...flagConfiguration };
518
- this.events.emit(ClientProviderEvents.ConfigurationChanged, { flagsChanged });
519
513
  try {
520
514
  await this.initialize(this._context);
521
- this.events.emit(ClientProviderEvents.Ready);
515
+ this.events.emit(ClientProviderEvents.ConfigurationChanged, { flagsChanged });
522
516
  } catch (err) {
523
517
  this.events.emit(ClientProviderEvents.Error);
524
518
  throw err;
@@ -572,8 +566,7 @@ var InMemoryProvider = class {
572
566
  const isContextEval = ctx && flagSpec?.contextEvaluator;
573
567
  const variant = isContextEval ? flagSpec.contextEvaluator?.(ctx) : flagSpec.defaultVariant;
574
568
  const value = variant && flagSpec?.variants[variant];
575
- const evalReason = isContextEval ? StandardResolutionReasons.TARGETING_MATCH : StandardResolutionReasons.STATIC;
576
- const reason = this.status === ProviderStatus2.STALE ? StandardResolutionReasons.CACHED : evalReason;
569
+ const reason = isContextEval ? StandardResolutionReasons.TARGETING_MATCH : StandardResolutionReasons.STATIC;
577
570
  return {
578
571
  value,
579
572
  ...variant && { variant },
@@ -585,11 +578,12 @@ var InMemoryProvider = class {
585
578
  // src/open-feature.ts
586
579
  var GLOBAL_OPENFEATURE_API_KEY = Symbol.for("@openfeature/web-sdk/api");
587
580
  var _globalThis = globalThis;
588
- var OpenFeatureAPI = class extends OpenFeatureCommonAPI {
589
- _events = new OpenFeatureEventEmitter();
590
- _defaultProvider = NOOP_PROVIDER;
581
+ var OpenFeatureAPI = class _OpenFeatureAPI extends OpenFeatureCommonAPI {
582
+ _statusEnumType = ClientProviderStatus;
583
+ _apiEmitter = new OpenFeatureEventEmitter();
584
+ _defaultProvider = new ProviderWrapper(NOOP_PROVIDER, ClientProviderStatus.NOT_READY, this._statusEnumType);
585
+ _domainScopedProviders = /* @__PURE__ */ new Map();
591
586
  _createEventEmitter = () => new OpenFeatureEventEmitter();
592
- _namedProviderContext = /* @__PURE__ */ new Map();
593
587
  constructor() {
594
588
  super("client");
595
589
  }
@@ -603,64 +597,70 @@ var OpenFeatureAPI = class extends OpenFeatureCommonAPI {
603
597
  if (globalApi) {
604
598
  return globalApi;
605
599
  }
606
- const instance = new OpenFeatureAPI();
600
+ const instance = new _OpenFeatureAPI();
607
601
  _globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
608
602
  return instance;
609
603
  }
610
- async setContext(nameOrContext, contextOrUndefined) {
611
- const clientName = stringOrUndefined(nameOrContext);
612
- const context = objectOrUndefined(nameOrContext) ?? objectOrUndefined(contextOrUndefined) ?? {};
613
- if (clientName) {
614
- const provider = this._clientProviders.get(clientName);
615
- if (provider) {
616
- const oldContext = this.getContext(clientName);
617
- this._namedProviderContext.set(clientName, context);
618
- await this.runProviderContextChangeHandler(clientName, provider, oldContext, context);
604
+ getProviderStatus(domain) {
605
+ if (!domain) {
606
+ return this._defaultProvider.status;
607
+ }
608
+ return this._domainScopedProviders.get(domain)?.status ?? this._defaultProvider.status;
609
+ }
610
+ async setContext(domainOrContext, contextOrUndefined) {
611
+ const domain = stringOrUndefined(domainOrContext);
612
+ const context = objectOrUndefined(domainOrContext) ?? objectOrUndefined(contextOrUndefined) ?? {};
613
+ if (domain) {
614
+ const wrapper = this._domainScopedProviders.get(domain);
615
+ if (wrapper) {
616
+ const oldContext = this.getContext(domain);
617
+ this._domainScopedContext.set(domain, context);
618
+ await this.runProviderContextChangeHandler(domain, wrapper, oldContext, context);
619
619
  } else {
620
- this._namedProviderContext.set(clientName, context);
620
+ this._domainScopedContext.set(domain, context);
621
621
  }
622
622
  } else {
623
623
  const oldContext = this._context;
624
624
  this._context = context;
625
- const defaultContextNameProviders = Array.from(this._clientProviders.entries()).filter(([name]) => !this._namedProviderContext.has(name)).reduce((acc, [name, provider]) => {
626
- acc.push({ name, provider });
625
+ const unboundProviders = Array.from(this._domainScopedProviders.entries()).filter(([domain2]) => !this._domainScopedContext.has(domain2)).reduce((acc, [domain2, wrapper]) => {
626
+ acc.push({ domain: domain2, wrapper });
627
627
  return acc;
628
628
  }, []);
629
- const allProviders = [
630
- // add in the default (no name)
631
- { name: void 0, provider: this._defaultProvider },
632
- ...defaultContextNameProviders
629
+ const allDomainRecords = [
630
+ // add in the default (no domain)
631
+ { domain: void 0, wrapper: this._defaultProvider },
632
+ ...unboundProviders
633
633
  ];
634
634
  await Promise.all(
635
- allProviders.map(
636
- (tuple) => this.runProviderContextChangeHandler(tuple.name, tuple.provider, oldContext, context)
635
+ allDomainRecords.map(
636
+ (dm) => this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context)
637
637
  )
638
638
  );
639
639
  }
640
640
  }
641
- getContext(nameOrUndefined) {
642
- const clientName = stringOrUndefined(nameOrUndefined);
643
- if (clientName) {
644
- const context = this._namedProviderContext.get(clientName);
641
+ getContext(domainOrUndefined) {
642
+ const domain = stringOrUndefined(domainOrUndefined);
643
+ if (domain) {
644
+ const context = this._domainScopedContext.get(domain);
645
645
  if (context) {
646
646
  return context;
647
647
  } else {
648
- this._logger.debug(`Unable to find context for '${clientName}'.`);
648
+ this._logger.debug(`Unable to find context for '${domain}'.`);
649
649
  }
650
650
  }
651
651
  return this._context;
652
652
  }
653
- async clearContext(nameOrUndefined) {
654
- const clientName = stringOrUndefined(nameOrUndefined);
655
- if (clientName) {
656
- const provider = this._clientProviders.get(clientName);
657
- if (provider) {
658
- const oldContext = this.getContext(clientName);
659
- this._namedProviderContext.delete(clientName);
653
+ async clearContext(domainOrUndefined) {
654
+ const domain = stringOrUndefined(domainOrUndefined);
655
+ if (domain) {
656
+ const wrapper = this._domainScopedProviders.get(domain);
657
+ if (wrapper) {
658
+ const oldContext = this.getContext(domain);
659
+ this._domainScopedContext.delete(domain);
660
660
  const newContext = this.getContext();
661
- await this.runProviderContextChangeHandler(clientName, provider, oldContext, newContext);
661
+ await this.runProviderContextChangeHandler(domain, wrapper, oldContext, newContext);
662
662
  } else {
663
- this._namedProviderContext.delete(clientName);
663
+ this._domainScopedContext.delete(domain);
664
664
  }
665
665
  } else {
666
666
  return this.setContext({});
@@ -668,11 +668,11 @@ var OpenFeatureAPI = class extends OpenFeatureCommonAPI {
668
668
  }
669
669
  /**
670
670
  * Resets the global evaluation context and removes the evaluation context for
671
- * all named clients.
671
+ * all domains.
672
672
  */
673
673
  async clearContexts() {
674
674
  await this.clearContext();
675
- await Promise.allSettled(Array.from(this._clientProviders.keys()).map((name) => this.clearContext(name)));
675
+ await Promise.allSettled(Array.from(this._domainScopedProviders.keys()).map((domain) => this.clearContext(domain)));
676
676
  }
677
677
  /**
678
678
  * A factory function for creating new named OpenFeature clients. Clients can contain
@@ -681,18 +681,19 @@ var OpenFeatureAPI = class extends OpenFeatureCommonAPI {
681
681
  *
682
682
  * If there is already a provider bound to this name via {@link this.setProvider setProvider}, this provider will be used.
683
683
  * Otherwise, the default provider is used until a provider is assigned to that name.
684
- * @param {string} name The name of the client
684
+ * @param {string} domain An identifier which logically binds clients with providers
685
685
  * @param {string} version The version of the client (only used for metadata)
686
686
  * @returns {Client} OpenFeature Client
687
687
  */
688
- getClient(name, version) {
688
+ getClient(domain, version) {
689
689
  return new OpenFeatureClient(
690
690
  // functions are passed here to make sure that these values are always up to date,
691
691
  // and so we don't have to make these public properties on the API class.
692
- () => this.getProviderForClient(name),
693
- () => this.buildAndCacheEventEmitterForClient(name),
692
+ () => this.getProviderForClient(domain),
693
+ () => this.getProviderStatus(domain),
694
+ () => this.buildAndCacheEventEmitterForClient(domain),
694
695
  () => this._logger,
695
- { name, version }
696
+ { domain, version }
696
697
  );
697
698
  }
698
699
  /**
@@ -701,24 +702,40 @@ var OpenFeatureAPI = class extends OpenFeatureCommonAPI {
701
702
  */
702
703
  async clearProviders() {
703
704
  await super.clearProvidersAndSetDefault(NOOP_PROVIDER);
704
- this._namedProviderContext.clear();
705
+ this._domainScopedContext.clear();
705
706
  }
706
- async runProviderContextChangeHandler(clientName, provider, oldContext, newContext) {
707
- const providerName = provider.metadata.name;
707
+ async runProviderContextChangeHandler(domain, wrapper, oldContext, newContext) {
708
+ const providerName = wrapper.provider?.metadata?.name || "unnamed-provider";
708
709
  try {
709
- await provider.onContextChange?.(oldContext, newContext);
710
- this.getAssociatedEventEmitters(clientName).forEach((emitter) => {
711
- emitter?.emit(ClientProviderEvents.ContextChanged, { clientName, providerName });
712
- });
713
- this._events?.emit(ClientProviderEvents.ContextChanged, { clientName, providerName });
710
+ if (typeof wrapper.provider.onContextChange === "function") {
711
+ wrapper.incrementPendingContextChanges();
712
+ wrapper.status = this._statusEnumType.RECONCILING;
713
+ this.getAssociatedEventEmitters(domain).forEach((emitter) => {
714
+ emitter?.emit(ClientProviderEvents.Reconciling, { domain, providerName });
715
+ });
716
+ this._apiEmitter?.emit(ClientProviderEvents.Reconciling, { domain, providerName });
717
+ await wrapper.provider.onContextChange(oldContext, newContext);
718
+ wrapper.decrementPendingContextChanges();
719
+ }
720
+ wrapper.status = this._statusEnumType.READY;
721
+ if (wrapper.allContextChangesSettled) {
722
+ this.getAssociatedEventEmitters(domain).forEach((emitter) => {
723
+ emitter?.emit(ClientProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
724
+ });
725
+ this._apiEmitter?.emit(ClientProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
726
+ }
714
727
  } catch (err) {
715
- const error = err;
716
- const message = `Error running ${provider?.metadata?.name}'s context change handler: ${error?.message}`;
717
- this._logger?.error(`${message}`, err);
718
- this.getAssociatedEventEmitters(clientName).forEach((emitter) => {
719
- emitter?.emit(ClientProviderEvents.Error, { clientName, providerName, message });
720
- });
721
- this._events?.emit(ClientProviderEvents.Error, { clientName, providerName, message });
728
+ wrapper.decrementPendingContextChanges();
729
+ wrapper.status = this._statusEnumType.ERROR;
730
+ if (wrapper.allContextChangesSettled) {
731
+ const error = err;
732
+ const message = `Error running ${providerName}'s context change handler: ${error?.message}`;
733
+ this._logger?.error(`${message}`, err);
734
+ this.getAssociatedEventEmitters(domain).forEach((emitter) => {
735
+ emitter?.emit(ClientProviderEvents.Error, { clientName: domain, domain, providerName, message });
736
+ });
737
+ this._apiEmitter?.emit(ClientProviderEvents.Error, { clientName: domain, domain, providerName, message });
738
+ }
722
739
  }
723
740
  }
724
741
  };
@@ -726,8 +743,9 @@ var OpenFeature = OpenFeatureAPI.getInstance();
726
743
 
727
744
  // src/client/open-feature-client.ts
728
745
  var OpenFeatureClient = class {
729
- constructor(providerAccessor, emitterAccessor, globalLogger, options) {
746
+ constructor(providerAccessor, providerStatusAccessor, emitterAccessor, globalLogger, options) {
730
747
  this.providerAccessor = providerAccessor;
748
+ this.providerStatusAccessor = providerStatusAccessor;
731
749
  this.emitterAccessor = emitterAccessor;
732
750
  this.globalLogger = globalLogger;
733
751
  this.options = options;
@@ -736,20 +754,26 @@ var OpenFeatureClient = class {
736
754
  _clientLogger;
737
755
  get metadata() {
738
756
  return {
739
- name: this.options.name,
757
+ // Use domain if name is not provided
758
+ name: this.options.domain ?? this.options.name,
759
+ domain: this.options.domain ?? this.options.name,
740
760
  version: this.options.version,
741
761
  providerMetadata: this.providerAccessor().metadata
742
762
  };
743
763
  }
744
764
  get providerStatus() {
745
- return this.providerAccessor()?.status || ProviderStatus3.READY;
765
+ return this.providerStatusAccessor();
746
766
  }
747
767
  addHandler(eventType, handler) {
748
768
  this.emitterAccessor().addHandler(eventType, handler);
749
- const shouldRunNow = statusMatchesEvent(eventType, this._provider.status);
769
+ const shouldRunNow = statusMatchesEvent(eventType, this.providerStatus);
750
770
  if (shouldRunNow) {
751
771
  try {
752
- handler({ clientName: this.metadata.name, providerName: this._provider.metadata.name });
772
+ handler({
773
+ clientName: this.metadata.name,
774
+ domain: this.metadata.domain,
775
+ providerName: this._provider.metadata.name
776
+ });
753
777
  } catch (err) {
754
778
  this._logger?.error("Error running event handler:", err);
755
779
  }
@@ -823,7 +847,7 @@ var OpenFeatureClient = class {
823
847
  ];
824
848
  const allHooksReversed = [...allHooks].reverse();
825
849
  const context = {
826
- ...OpenFeature.getContext(this?.options?.name)
850
+ ...OpenFeature.getContext(this?.options?.domain)
827
851
  };
828
852
  const hookContext = {
829
853
  flagKey,
@@ -836,6 +860,11 @@ var OpenFeatureClient = class {
836
860
  };
837
861
  try {
838
862
  this.beforeHooks(allHooks, hookContext, options);
863
+ if (this.providerStatus === ClientProviderStatus.NOT_READY) {
864
+ throw new ProviderNotReadyError("provider has not yet initialized");
865
+ } else if (this.providerStatus === ClientProviderStatus.FATAL) {
866
+ throw new ProviderFatalError("provider is in an irrecoverable error state");
867
+ }
839
868
  const resolution = resolver.call(this._provider, flagKey, defaultValue, context, this._logger);
840
869
  const evaluationDetails = {
841
870
  ...resolution,
@@ -915,6 +944,7 @@ export {
915
944
  OpenFeatureAPI,
916
945
  OpenFeatureClient,
917
946
  OpenFeatureEventEmitter,
918
- ClientProviderEvents as ProviderEvents
947
+ ClientProviderEvents as ProviderEvents,
948
+ ClientProviderStatus as ProviderStatus
919
949
  };
920
950
  //# sourceMappingURL=index.js.map