@openfeature/web-sdk 1.0.2 → 1.1.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/dist/esm/index.js CHANGED
@@ -206,41 +206,6 @@ var require_eventemitter3 = __commonJS({
206
206
  }
207
207
  });
208
208
 
209
- // src/client/open-feature-client.ts
210
- import {
211
- ErrorCode as ErrorCode2,
212
- ProviderFatalError,
213
- ProviderNotReadyError,
214
- SafeLogger,
215
- StandardResolutionReasons as StandardResolutionReasons2,
216
- statusMatchesEvent
217
- } from "@openfeature/core";
218
-
219
- // src/open-feature.ts
220
- import {
221
- OpenFeatureCommonAPI,
222
- ProviderWrapper,
223
- objectOrUndefined,
224
- stringOrUndefined
225
- } from "@openfeature/core";
226
-
227
- // src/events/open-feature-event-emitter.ts
228
- import { GenericEventEmitter } from "@openfeature/core";
229
-
230
- // ../../node_modules/eventemitter3/index.mjs
231
- var import_index = __toESM(require_eventemitter3(), 1);
232
-
233
- // src/events/open-feature-event-emitter.ts
234
- var OpenFeatureEventEmitter = class extends GenericEventEmitter {
235
- eventEmitter = new import_index.default();
236
- constructor() {
237
- super();
238
- }
239
- };
240
-
241
- // src/events/events.ts
242
- import { ClientProviderEvents } from "@openfeature/core";
243
-
244
209
  // src/provider/provider.ts
245
210
  import { ClientProviderStatus } from "@openfeature/core";
246
211
 
@@ -280,6 +245,23 @@ import {
280
245
  TypeMismatchError
281
246
  } from "@openfeature/core";
282
247
 
248
+ // src/events/open-feature-event-emitter.ts
249
+ import { GenericEventEmitter } from "@openfeature/core";
250
+
251
+ // ../../node_modules/eventemitter3/index.mjs
252
+ var import_index = __toESM(require_eventemitter3(), 1);
253
+
254
+ // src/events/open-feature-event-emitter.ts
255
+ var OpenFeatureEventEmitter = class extends GenericEventEmitter {
256
+ eventEmitter = new import_index.default();
257
+ constructor() {
258
+ super();
259
+ }
260
+ };
261
+
262
+ // src/events/events.ts
263
+ import { ClientProviderEvents } from "@openfeature/core";
264
+
283
265
  // src/provider/in-memory-provider/variant-not-found-error.ts
284
266
  import { ErrorCode, OpenFeatureError } from "@openfeature/core";
285
267
  var VariantNotFoundError = class _VariantNotFoundError extends OpenFeatureError {
@@ -387,172 +369,23 @@ var InMemoryProvider = class {
387
369
  };
388
370
 
389
371
  // src/open-feature.ts
390
- var GLOBAL_OPENFEATURE_API_KEY = Symbol.for("@openfeature/web-sdk/api");
391
- var _globalThis = globalThis;
392
- var OpenFeatureAPI = class _OpenFeatureAPI extends OpenFeatureCommonAPI {
393
- _statusEnumType = ClientProviderStatus;
394
- _apiEmitter = new OpenFeatureEventEmitter();
395
- _defaultProvider = new ProviderWrapper(NOOP_PROVIDER, ClientProviderStatus.NOT_READY, this._statusEnumType);
396
- _domainScopedProviders = /* @__PURE__ */ new Map();
397
- _createEventEmitter = () => new OpenFeatureEventEmitter();
398
- constructor() {
399
- super("client");
400
- }
401
- /**
402
- * Gets a singleton instance of the OpenFeature API.
403
- * @ignore
404
- * @returns {OpenFeatureAPI} OpenFeature API
405
- */
406
- static getInstance() {
407
- const globalApi = _globalThis[GLOBAL_OPENFEATURE_API_KEY];
408
- if (globalApi) {
409
- return globalApi;
410
- }
411
- const instance = new _OpenFeatureAPI();
412
- _globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
413
- return instance;
414
- }
415
- getProviderStatus(domain) {
416
- if (!domain) {
417
- return this._defaultProvider.status;
418
- }
419
- return this._domainScopedProviders.get(domain)?.status ?? this._defaultProvider.status;
420
- }
421
- async setContext(domainOrContext, contextOrUndefined) {
422
- const domain = stringOrUndefined(domainOrContext);
423
- const context = objectOrUndefined(domainOrContext) ?? objectOrUndefined(contextOrUndefined) ?? {};
424
- if (domain) {
425
- const wrapper = this._domainScopedProviders.get(domain);
426
- if (wrapper) {
427
- const oldContext = this.getContext(domain);
428
- this._domainScopedContext.set(domain, context);
429
- await this.runProviderContextChangeHandler(domain, wrapper, oldContext, context);
430
- } else {
431
- this._domainScopedContext.set(domain, context);
432
- }
433
- } else {
434
- const oldContext = this._context;
435
- this._context = context;
436
- const unboundProviders = Array.from(this._domainScopedProviders.entries()).filter(([domain2]) => !this._domainScopedContext.has(domain2)).reduce((acc, [domain2, wrapper]) => {
437
- acc.push({ domain: domain2, wrapper });
438
- return acc;
439
- }, []);
440
- const allDomainRecords = [
441
- // add in the default (no domain)
442
- { domain: void 0, wrapper: this._defaultProvider },
443
- ...unboundProviders
444
- ];
445
- await Promise.all(
446
- allDomainRecords.map(
447
- (dm) => this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context)
448
- )
449
- );
450
- }
451
- }
452
- getContext(domainOrUndefined) {
453
- const domain = stringOrUndefined(domainOrUndefined);
454
- if (domain) {
455
- const context = this._domainScopedContext.get(domain);
456
- if (context) {
457
- return context;
458
- } else {
459
- this._logger.debug(`Unable to find context for '${domain}'.`);
460
- }
461
- }
462
- return this._context;
463
- }
464
- async clearContext(domainOrUndefined) {
465
- const domain = stringOrUndefined(domainOrUndefined);
466
- if (domain) {
467
- const wrapper = this._domainScopedProviders.get(domain);
468
- if (wrapper) {
469
- const oldContext = this.getContext(domain);
470
- this._domainScopedContext.delete(domain);
471
- const newContext = this.getContext();
472
- await this.runProviderContextChangeHandler(domain, wrapper, oldContext, newContext);
473
- } else {
474
- this._domainScopedContext.delete(domain);
475
- }
476
- } else {
477
- return this.setContext({});
478
- }
479
- }
480
- /**
481
- * Resets the global evaluation context and removes the evaluation context for
482
- * all domains.
483
- */
484
- async clearContexts() {
485
- await this.clearContext();
486
- await Promise.allSettled(Array.from(this._domainScopedProviders.keys()).map((domain) => this.clearContext(domain)));
487
- }
488
- /**
489
- * A factory function for creating new named OpenFeature clients. Clients can contain
490
- * their own state (e.g. logger, hook, context). Multiple clients can be used
491
- * to segment feature flag configuration.
492
- *
493
- * If there is already a provider bound to this name via {@link this.setProvider setProvider}, this provider will be used.
494
- * Otherwise, the default provider is used until a provider is assigned to that name.
495
- * @param {string} domain An identifier which logically binds clients with providers
496
- * @param {string} version The version of the client (only used for metadata)
497
- * @returns {Client} OpenFeature Client
498
- */
499
- getClient(domain, version) {
500
- return new OpenFeatureClient(
501
- // functions are passed here to make sure that these values are always up to date,
502
- // and so we don't have to make these public properties on the API class.
503
- () => this.getProviderForClient(domain),
504
- () => this.getProviderStatus(domain),
505
- () => this.buildAndCacheEventEmitterForClient(domain),
506
- () => this._logger,
507
- { domain, version }
508
- );
509
- }
510
- /**
511
- * Clears all registered providers and resets the default provider.
512
- * @returns {Promise<void>}
513
- */
514
- async clearProviders() {
515
- await super.clearProvidersAndSetDefault(NOOP_PROVIDER);
516
- this._domainScopedContext.clear();
517
- }
518
- async runProviderContextChangeHandler(domain, wrapper, oldContext, newContext) {
519
- const providerName = wrapper.provider?.metadata?.name || "unnamed-provider";
520
- try {
521
- if (typeof wrapper.provider.onContextChange === "function") {
522
- wrapper.incrementPendingContextChanges();
523
- wrapper.status = this._statusEnumType.RECONCILING;
524
- this.getAssociatedEventEmitters(domain).forEach((emitter) => {
525
- emitter?.emit(ClientProviderEvents.Reconciling, { domain, providerName });
526
- });
527
- this._apiEmitter?.emit(ClientProviderEvents.Reconciling, { domain, providerName });
528
- await wrapper.provider.onContextChange(oldContext, newContext);
529
- wrapper.decrementPendingContextChanges();
530
- }
531
- wrapper.status = this._statusEnumType.READY;
532
- if (wrapper.allContextChangesSettled) {
533
- this.getAssociatedEventEmitters(domain).forEach((emitter) => {
534
- emitter?.emit(ClientProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
535
- });
536
- this._apiEmitter?.emit(ClientProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
537
- }
538
- } catch (err) {
539
- wrapper.decrementPendingContextChanges();
540
- wrapper.status = this._statusEnumType.ERROR;
541
- if (wrapper.allContextChangesSettled) {
542
- const error = err;
543
- const message = `Error running ${providerName}'s context change handler: ${error?.message}`;
544
- this._logger?.error(`${message}`, err);
545
- this.getAssociatedEventEmitters(domain).forEach((emitter) => {
546
- emitter?.emit(ClientProviderEvents.Error, { clientName: domain, domain, providerName, message });
547
- });
548
- this._apiEmitter?.emit(ClientProviderEvents.Error, { clientName: domain, domain, providerName, message });
549
- }
550
- }
551
- }
552
- };
553
- var OpenFeature = OpenFeatureAPI.getInstance();
372
+ import {
373
+ OpenFeatureCommonAPI,
374
+ ProviderWrapper,
375
+ objectOrUndefined,
376
+ stringOrUndefined
377
+ } from "@openfeature/core";
554
378
 
555
- // src/client/open-feature-client.ts
379
+ // src/client/internal/open-feature-client.ts
380
+ import {
381
+ ErrorCode as ErrorCode2,
382
+ ProviderFatalError,
383
+ ProviderNotReadyError,
384
+ SafeLogger,
385
+ StandardResolutionReasons as StandardResolutionReasons2,
386
+ instantiateErrorByErrorCode,
387
+ statusMatchesEvent
388
+ } from "@openfeature/core";
556
389
  var OpenFeatureClient = class {
557
390
  constructor(providerAccessor, providerStatusAccessor, emitterAccessor, globalLogger, options) {
558
391
  this.providerAccessor = providerAccessor;
@@ -682,6 +515,9 @@ var OpenFeatureClient = class {
682
515
  flagMetadata: Object.freeze(resolution.flagMetadata ?? {}),
683
516
  flagKey
684
517
  };
518
+ if (evaluationDetails.errorCode) {
519
+ throw instantiateErrorByErrorCode(evaluationDetails.errorCode);
520
+ }
685
521
  this.afterHooks(allHooksReversed, hookContext, evaluationDetails, options);
686
522
  return evaluationDetails;
687
523
  } catch (err) {
@@ -746,6 +582,207 @@ var OpenFeatureClient = class {
746
582
  }
747
583
  };
748
584
 
585
+ // src/open-feature.ts
586
+ var GLOBAL_OPENFEATURE_API_KEY = Symbol.for("@openfeature/web-sdk/api");
587
+ var _globalThis = globalThis;
588
+ var OpenFeatureAPI = class _OpenFeatureAPI extends OpenFeatureCommonAPI {
589
+ _statusEnumType = ClientProviderStatus;
590
+ _apiEmitter = new OpenFeatureEventEmitter();
591
+ _defaultProvider = new ProviderWrapper(
592
+ NOOP_PROVIDER,
593
+ ClientProviderStatus.NOT_READY,
594
+ this._statusEnumType
595
+ );
596
+ _domainScopedProviders = /* @__PURE__ */ new Map();
597
+ _createEventEmitter = () => new OpenFeatureEventEmitter();
598
+ constructor() {
599
+ super("client");
600
+ }
601
+ /**
602
+ * Gets a singleton instance of the OpenFeature API.
603
+ * @ignore
604
+ * @returns {OpenFeatureAPI} OpenFeature API
605
+ */
606
+ static getInstance() {
607
+ const globalApi = _globalThis[GLOBAL_OPENFEATURE_API_KEY];
608
+ if (globalApi) {
609
+ return globalApi;
610
+ }
611
+ const instance = new _OpenFeatureAPI();
612
+ _globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
613
+ return instance;
614
+ }
615
+ getProviderStatus(domain) {
616
+ if (!domain) {
617
+ return this._defaultProvider.status;
618
+ }
619
+ return this._domainScopedProviders.get(domain)?.status ?? this._defaultProvider.status;
620
+ }
621
+ async setProviderAndWait(clientOrProvider, providerContextOrUndefined, contextOrUndefined) {
622
+ const domain = stringOrUndefined(clientOrProvider);
623
+ const provider = domain ? objectOrUndefined(providerContextOrUndefined) : objectOrUndefined(clientOrProvider);
624
+ const context = domain ? objectOrUndefined(contextOrUndefined) : objectOrUndefined(providerContextOrUndefined);
625
+ if (context) {
626
+ if (domain) {
627
+ this._domainScopedContext.set(domain, context);
628
+ } else {
629
+ this._context = context;
630
+ }
631
+ }
632
+ await this.setAwaitableProvider(domain, provider);
633
+ }
634
+ setProvider(domainOrProvider, providerContextOrUndefined, contextOrUndefined) {
635
+ const domain = stringOrUndefined(domainOrProvider);
636
+ const provider = domain ? objectOrUndefined(providerContextOrUndefined) : objectOrUndefined(domainOrProvider);
637
+ const context = domain ? objectOrUndefined(contextOrUndefined) : objectOrUndefined(providerContextOrUndefined);
638
+ if (context) {
639
+ if (domain) {
640
+ this._domainScopedContext.set(domain, context);
641
+ } else {
642
+ this._context = context;
643
+ }
644
+ }
645
+ const maybePromise = this.setAwaitableProvider(domain, provider);
646
+ Promise.resolve(maybePromise).catch((err) => {
647
+ this._logger.error("Error during provider initialization:", err);
648
+ });
649
+ return this;
650
+ }
651
+ async setContext(domainOrContext, contextOrUndefined) {
652
+ const domain = stringOrUndefined(domainOrContext);
653
+ const context = objectOrUndefined(domainOrContext) ?? objectOrUndefined(contextOrUndefined) ?? {};
654
+ if (domain) {
655
+ const wrapper = this._domainScopedProviders.get(domain);
656
+ if (wrapper) {
657
+ const oldContext = this.getContext(domain);
658
+ this._domainScopedContext.set(domain, context);
659
+ await this.runProviderContextChangeHandler(domain, wrapper, oldContext, context);
660
+ } else {
661
+ this._domainScopedContext.set(domain, context);
662
+ }
663
+ } else {
664
+ const oldContext = this._context;
665
+ this._context = context;
666
+ const unboundProviders = Array.from(this._domainScopedProviders.entries()).filter(([domain2]) => !this._domainScopedContext.has(domain2)).reduce((acc, [domain2, wrapper]) => {
667
+ acc.push({ domain: domain2, wrapper });
668
+ return acc;
669
+ }, []);
670
+ const allDomainRecords = [
671
+ // add in the default (no domain)
672
+ { domain: void 0, wrapper: this._defaultProvider },
673
+ ...unboundProviders
674
+ ];
675
+ await Promise.all(
676
+ allDomainRecords.map((dm) => this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context))
677
+ );
678
+ }
679
+ }
680
+ getContext(domainOrUndefined) {
681
+ const domain = stringOrUndefined(domainOrUndefined);
682
+ if (domain) {
683
+ const context = this._domainScopedContext.get(domain);
684
+ if (context) {
685
+ return context;
686
+ } else {
687
+ this._logger.debug(`Unable to find context for '${domain}'.`);
688
+ }
689
+ }
690
+ return this._context;
691
+ }
692
+ async clearContext(domainOrUndefined) {
693
+ const domain = stringOrUndefined(domainOrUndefined);
694
+ if (domain) {
695
+ const wrapper = this._domainScopedProviders.get(domain);
696
+ if (wrapper) {
697
+ const oldContext = this.getContext(domain);
698
+ this._domainScopedContext.delete(domain);
699
+ const newContext = this.getContext();
700
+ await this.runProviderContextChangeHandler(domain, wrapper, oldContext, newContext);
701
+ } else {
702
+ this._domainScopedContext.delete(domain);
703
+ }
704
+ } else {
705
+ return this.setContext({});
706
+ }
707
+ }
708
+ /**
709
+ * Resets the global evaluation context and removes the evaluation context for
710
+ * all domains.
711
+ */
712
+ async clearContexts() {
713
+ await this.clearContext();
714
+ await Promise.allSettled(Array.from(this._domainScopedProviders.keys()).map((domain) => this.clearContext(domain)));
715
+ }
716
+ /**
717
+ * A factory function for creating new named OpenFeature clients. Clients can contain
718
+ * their own state (e.g. logger, hook, context). Multiple clients can be used
719
+ * to segment feature flag configuration.
720
+ *
721
+ * If there is already a provider bound to this name via {@link this.setProvider setProvider}, this provider will be used.
722
+ * Otherwise, the default provider is used until a provider is assigned to that name.
723
+ * @param {string} domain An identifier which logically binds clients with providers
724
+ * @param {string} version The version of the client (only used for metadata)
725
+ * @returns {Client} OpenFeature Client
726
+ */
727
+ getClient(domain, version) {
728
+ return new OpenFeatureClient(
729
+ // functions are passed here to make sure that these values are always up to date,
730
+ // and so we don't have to make these public properties on the API class.
731
+ () => this.getProviderForClient(domain),
732
+ () => this.getProviderStatus(domain),
733
+ () => this.buildAndCacheEventEmitterForClient(domain),
734
+ () => this._logger,
735
+ { domain, version }
736
+ );
737
+ }
738
+ /**
739
+ * Clears all registered providers and resets the default provider.
740
+ * @returns {Promise<void>}
741
+ */
742
+ async clearProviders() {
743
+ await super.clearProvidersAndSetDefault(NOOP_PROVIDER);
744
+ this._domainScopedContext.clear();
745
+ }
746
+ async runProviderContextChangeHandler(domain, wrapper, oldContext, newContext) {
747
+ const providerName = wrapper.provider?.metadata?.name || "unnamed-provider";
748
+ try {
749
+ if (typeof wrapper.provider.onContextChange === "function") {
750
+ const maybePromise = wrapper.provider.onContextChange(oldContext, newContext);
751
+ if (typeof maybePromise?.then === "function") {
752
+ wrapper.incrementPendingContextChanges();
753
+ wrapper.status = this._statusEnumType.RECONCILING;
754
+ this.getAssociatedEventEmitters(domain).forEach((emitter) => {
755
+ emitter?.emit(ClientProviderEvents.Reconciling, { domain, providerName });
756
+ });
757
+ this._apiEmitter?.emit(ClientProviderEvents.Reconciling, { domain, providerName });
758
+ await maybePromise;
759
+ wrapper.decrementPendingContextChanges();
760
+ }
761
+ }
762
+ wrapper.status = this._statusEnumType.READY;
763
+ if (wrapper.allContextChangesSettled) {
764
+ this.getAssociatedEventEmitters(domain).forEach((emitter) => {
765
+ emitter?.emit(ClientProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
766
+ });
767
+ this._apiEmitter?.emit(ClientProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
768
+ }
769
+ } catch (err) {
770
+ wrapper.decrementPendingContextChanges();
771
+ wrapper.status = this._statusEnumType.ERROR;
772
+ if (wrapper.allContextChangesSettled) {
773
+ const error = err;
774
+ const message = `Error running ${providerName}'s context change handler: ${error?.message}`;
775
+ this._logger?.error(`${message}`, err);
776
+ this.getAssociatedEventEmitters(domain).forEach((emitter) => {
777
+ emitter?.emit(ClientProviderEvents.Error, { clientName: domain, domain, providerName, message });
778
+ });
779
+ this._apiEmitter?.emit(ClientProviderEvents.Error, { clientName: domain, domain, providerName, message });
780
+ }
781
+ }
782
+ }
783
+ };
784
+ var OpenFeature = OpenFeatureAPI.getInstance();
785
+
749
786
  // src/index.ts
750
787
  export * from "@openfeature/core";
751
788
  export {
@@ -753,7 +790,6 @@ export {
753
790
  NOOP_PROVIDER,
754
791
  OpenFeature,
755
792
  OpenFeatureAPI,
756
- OpenFeatureClient,
757
793
  OpenFeatureEventEmitter,
758
794
  ClientProviderEvents as ProviderEvents,
759
795
  ClientProviderStatus as ProviderStatus