@@ -16,6 +16,7 @@ import type { BaseHook, EvaluationLifeCycle } from './hooks';
1616import type { Logger , ManageLogger } from './logger' ;
1717import { DefaultLogger , SafeLogger } from './logger' ;
1818import type { ClientProviderStatus , CommonProvider , ProviderMetadata , ServerProviderStatus } from './provider' ;
19+ import { isStateManagingProvider } from './provider' ;
1920import { objectOrUndefined , stringOrUndefined } from './type-guards' ;
2021import type { Paradigm } from './types' ;
2122
@@ -27,28 +28,31 @@ type AnyProviderStatus = ClientProviderStatus | ServerProviderStatus;
2728 */
2829export class ProviderWrapper < P extends CommonProvider < AnyProviderStatus > , S extends AnyProviderStatus > {
2930 private _pendingContextChanges = 0 ;
31+ private readonly _delegateManagesState : boolean ;
3032
3133 constructor (
3234 private _provider : P ,
3335 private _status : S ,
3436 _statusEnumType : typeof ClientProviderStatus | typeof ServerProviderStatus ,
3537 ) {
36- // update the providers status with events
37- _provider . events ?. addHandler ( AllProviderEvents . Ready , ( ) => {
38- // These casts are due to the face we don't "know" what status enum we are dealing with here (client or server).
39- // We could abstract this an implement it in the client/server libs to fix this, but the value is low.
40- this . _status = _statusEnumType . READY as S ;
41- } ) ;
42- _provider . events ?. addHandler ( AllProviderEvents . Stale , ( ) => {
43- this . _status = _statusEnumType . STALE as S ;
44- } ) ;
45- _provider . events ?. addHandler ( AllProviderEvents . Error , ( details ) => {
46- if ( details ?. errorCode === ErrorCode . PROVIDER_FATAL ) {
47- this . _status = _statusEnumType . FATAL as S ;
48- } else {
49- this . _status = _statusEnumType . ERROR as S ;
50- }
51- } ) ;
38+ this . _delegateManagesState = isStateManagingProvider ( _provider ) ;
39+
40+ // For legacy providers, update status from events. State-managing providers own their own status.
41+ if ( ! this . _delegateManagesState ) {
42+ _provider . events ?. addHandler ( AllProviderEvents . Ready , ( ) => {
43+ this . _status = _statusEnumType . READY as S ;
44+ } ) ;
45+ _provider . events ?. addHandler ( AllProviderEvents . Stale , ( ) => {
46+ this . _status = _statusEnumType . STALE as S ;
47+ } ) ;
48+ _provider . events ?. addHandler ( AllProviderEvents . Error , ( details ) => {
49+ if ( details ?. errorCode === ErrorCode . PROVIDER_FATAL ) {
50+ this . _status = _statusEnumType . FATAL as S ;
51+ } else {
52+ this . _status = _statusEnumType . ERROR as S ;
53+ }
54+ } ) ;
55+ }
5256 }
5357
5458 get provider ( ) : P {
@@ -60,11 +64,21 @@ export class ProviderWrapper<P extends CommonProvider<AnyProviderStatus>, S exte
6064 }
6165
6266 get status ( ) : S {
67+ if ( this . _delegateManagesState ) {
68+ return this . _provider . status as S ;
69+ }
6370 return this . _status ;
6471 }
6572
6673 set status ( status : S ) {
67- this . _status = status ;
74+ // No-op for state-managing providers — they own their own status.
75+ if ( ! this . _delegateManagesState ) {
76+ this . _status = status ;
77+ }
78+ }
79+
80+ get delegateManagesState ( ) : boolean {
81+ return this . _delegateManagesState ;
6882 }
6983
7084 get allContextChangesSettled ( ) {
@@ -256,11 +270,14 @@ export abstract class OpenFeatureCommonAPI<
256270 . initialize ?.( domain ? ( this . _domainScopedContext . get ( domain ) ?? this . _context ) : this . _context )
257271 ?. then ( ( ) => {
258272 wrappedProvider . status = this . _statusEnumType . READY ;
259- // fetch the most recent event emitters, some may have been added during init
260- this . getAssociatedEventEmitters ( domain ) . forEach ( ( emitter ) => {
261- emitter ?. emit ( AllProviderEvents . Ready , { clientName : domain , domain, providerName } ) ;
262- } ) ;
263- this . _apiEmitter ?. emit ( AllProviderEvents . Ready , { clientName : domain , domain, providerName } ) ;
273+ // State-managing providers emit their own events; skip SDK-side emission.
274+ if ( ! wrappedProvider . delegateManagesState ) {
275+ // fetch the most recent event emitters, some may have been added during init
276+ this . getAssociatedEventEmitters ( domain ) . forEach ( ( emitter ) => {
277+ emitter ?. emit ( AllProviderEvents . Ready , { clientName : domain , domain, providerName } ) ;
278+ } ) ;
279+ this . _apiEmitter ?. emit ( AllProviderEvents . Ready , { clientName : domain , domain, providerName } ) ;
280+ }
264281 } )
265282 ?. catch ( ( error ) => {
266283 // if this is a fatal error, transition to FATAL status
@@ -269,29 +286,35 @@ export abstract class OpenFeatureCommonAPI<
269286 } else {
270287 wrappedProvider . status = this . _statusEnumType . ERROR ;
271288 }
272- this . getAssociatedEventEmitters ( domain ) . forEach ( ( emitter ) => {
273- emitter ?. emit ( AllProviderEvents . Error , {
289+ // State-managing providers emit their own events; skip SDK-side emission.
290+ if ( ! wrappedProvider . delegateManagesState ) {
291+ this . getAssociatedEventEmitters ( domain ) . forEach ( ( emitter ) => {
292+ emitter ?. emit ( AllProviderEvents . Error , {
293+ clientName : domain ,
294+ domain,
295+ providerName,
296+ message : error ?. message ,
297+ } ) ;
298+ } ) ;
299+ this . _apiEmitter ?. emit ( AllProviderEvents . Error , {
274300 clientName : domain ,
275301 domain,
276302 providerName,
277303 message : error ?. message ,
278304 } ) ;
279- } ) ;
280- this . _apiEmitter ?. emit ( AllProviderEvents . Error , {
281- clientName : domain ,
282- domain,
283- providerName,
284- message : error ?. message ,
285- } ) ;
305+ }
286306 // rethrow after emitting error events, so that public methods can control error handling
287307 throw error ;
288308 } ) ;
289309 } else {
290310 wrappedProvider . status = this . _statusEnumType . READY ;
291- emitters . forEach ( ( emitter ) => {
292- emitter ?. emit ( AllProviderEvents . Ready , { clientName : domain , domain, providerName } ) ;
293- } ) ;
294- this . _apiEmitter ?. emit ( AllProviderEvents . Ready , { clientName : domain , domain, providerName } ) ;
311+ // State-managing providers emit their own events; skip SDK-side emission.
312+ if ( ! wrappedProvider . delegateManagesState ) {
313+ emitters . forEach ( ( emitter ) => {
314+ emitter ?. emit ( AllProviderEvents . Ready , { clientName : domain , domain, providerName } ) ;
315+ } ) ;
316+ this . _apiEmitter ?. emit ( AllProviderEvents . Ready , { clientName : domain , domain, providerName } ) ;
317+ }
295318 }
296319
297320 if ( domain ) {
0 commit comments