@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/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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
504
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
590
|
-
|
|
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
|
|
600
|
+
const instance = new _OpenFeatureAPI();
|
|
607
601
|
_globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
|
|
608
602
|
return instance;
|
|
609
603
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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.
|
|
620
|
+
this._domainScopedContext.set(domain, context);
|
|
621
621
|
}
|
|
622
622
|
} else {
|
|
623
623
|
const oldContext = this._context;
|
|
624
624
|
this._context = context;
|
|
625
|
-
const
|
|
626
|
-
acc.push({
|
|
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
|
|
630
|
-
// add in the default (no
|
|
631
|
-
{
|
|
632
|
-
...
|
|
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
|
-
|
|
636
|
-
(
|
|
635
|
+
allDomainRecords.map(
|
|
636
|
+
(dm) => this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context)
|
|
637
637
|
)
|
|
638
638
|
);
|
|
639
639
|
}
|
|
640
640
|
}
|
|
641
|
-
getContext(
|
|
642
|
-
const
|
|
643
|
-
if (
|
|
644
|
-
const context = this.
|
|
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 '${
|
|
648
|
+
this._logger.debug(`Unable to find context for '${domain}'.`);
|
|
649
649
|
}
|
|
650
650
|
}
|
|
651
651
|
return this._context;
|
|
652
652
|
}
|
|
653
|
-
async clearContext(
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
const
|
|
657
|
-
if (
|
|
658
|
-
const oldContext = this.getContext(
|
|
659
|
-
this.
|
|
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(
|
|
661
|
+
await this.runProviderContextChangeHandler(domain, wrapper, oldContext, newContext);
|
|
662
662
|
} else {
|
|
663
|
-
this.
|
|
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
|
|
671
|
+
* all domains.
|
|
672
672
|
*/
|
|
673
673
|
async clearContexts() {
|
|
674
674
|
await this.clearContext();
|
|
675
|
-
await Promise.allSettled(Array.from(this.
|
|
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}
|
|
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(
|
|
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(
|
|
693
|
-
() => this.
|
|
692
|
+
() => this.getProviderForClient(domain),
|
|
693
|
+
() => this.getProviderStatus(domain),
|
|
694
|
+
() => this.buildAndCacheEventEmitterForClient(domain),
|
|
694
695
|
() => this._logger,
|
|
695
|
-
{
|
|
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.
|
|
705
|
+
this._domainScopedContext.clear();
|
|
705
706
|
}
|
|
706
|
-
async runProviderContextChangeHandler(
|
|
707
|
-
const providerName = provider
|
|
707
|
+
async runProviderContextChangeHandler(domain, wrapper, oldContext, newContext) {
|
|
708
|
+
const providerName = wrapper.provider?.metadata?.name || "unnamed-provider";
|
|
708
709
|
try {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
|
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.
|
|
765
|
+
return this.providerStatusAccessor();
|
|
746
766
|
}
|
|
747
767
|
addHandler(eventType, handler) {
|
|
748
768
|
this.emitterAccessor().addHandler(eventType, handler);
|
|
749
|
-
const shouldRunNow = statusMatchesEvent(eventType, this.
|
|
769
|
+
const shouldRunNow = statusMatchesEvent(eventType, this.providerStatus);
|
|
750
770
|
if (shouldRunNow) {
|
|
751
771
|
try {
|
|
752
|
-
handler({
|
|
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?.
|
|
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
|