Skip to content

Commit

Permalink
feat: add PROVIDER_CONTEXT_CHANGED events
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <[email protected]>
  • Loading branch information
toddbaert committed Dec 14, 2023
1 parent 23d14aa commit a40bb4e
Show file tree
Hide file tree
Showing 23 changed files with 306 additions and 145 deletions.
6 changes: 3 additions & 3 deletions packages/client/src/client/open-feature-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import {
JsonValue,
Logger,
OpenFeatureError,
ProviderEvents,
ProviderStatus,
ResolutionDetails,
SafeLogger,
StandardResolutionReasons,
statusMatchesEvent
} from '@openfeature/core';
import { FlagEvaluationOptions } from '../evaluation';
import { ProviderEvents } from '../events';
import { InternalEventEmitter } from '../events/internal/internal-event-emitter';
import { Hook } from '../hooks';
import { OpenFeature } from '../open-feature';
Expand Down Expand Up @@ -54,7 +54,7 @@ export class OpenFeatureClient implements Client {
return this.providerAccessor()?.status || ProviderStatus.READY;
}

addHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void {
addHandler(eventType: ProviderEvents, handler: EventHandler): void {
this.emitterAccessor().addHandler(eventType, handler);
const shouldRunNow = statusMatchesEvent(eventType, this._provider.status);

Expand All @@ -68,7 +68,7 @@ export class OpenFeatureClient implements Client {
}
}

removeHandler<T extends ProviderEvents>(notificationType: T, handler: EventHandler<T>): void {
removeHandler(notificationType: ProviderEvents, handler: EventHandler): void {
this.emitterAccessor().removeHandler(notificationType, handler);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/events/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ClientProviderEvents } from '@openfeature/core';

export { ClientProviderEvents as ProviderEvents};
export type ProviderEmittableEvents = Exclude<ClientProviderEvents, ClientProviderEvents.ContextChanged>;
3 changes: 2 additions & 1 deletion packages/client/src/events/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './open-feature-event-emitter';
export * from './open-feature-event-emitter';
export * from './events';
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CommonEventDetails, GenericEventEmitter } from '@openfeature/core';
import { ProviderEvents } from '../events';

/**
* The InternalEventEmitter is not exported publicly and should only be used within the SDK. It extends the
* OpenFeatureEventEmitter to include additional properties that can be included
* in the event details.
*/
export abstract class InternalEventEmitter extends GenericEventEmitter<CommonEventDetails> {};
export abstract class InternalEventEmitter extends GenericEventEmitter<ProviderEvents, CommonEventDetails> {};
4 changes: 2 additions & 2 deletions packages/client/src/events/open-feature-event-emitter.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { GenericEventEmitter } from '@openfeature/core';
import EventEmitter from 'events';

import { ProviderEmittableEvents } from './events';
/**
* The OpenFeatureEventEmitter can be used by provider developers to emit
* events at various parts of the provider lifecycle.
*
* NOTE: Ready and error events are automatically emitted by the SDK based on
* the result of the initialize method.
*/
export class OpenFeatureEventEmitter extends GenericEventEmitter {
export class OpenFeatureEventEmitter extends GenericEventEmitter<ProviderEmittableEvents> {
protected readonly eventEmitter = new EventEmitter({ captureRejections: true });

constructor() {
Expand Down
31 changes: 21 additions & 10 deletions packages/client/src/open-feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
} from '@openfeature/core';
import { Client, OpenFeatureClient } from './client';
import { NOOP_PROVIDER, Provider } from './provider';
import { OpenFeatureEventEmitter } from './events';
import { OpenFeatureEventEmitter, ProviderEvents } from './events';
import { Hook } from './hooks';
import { GenericEventEmitter } from '@openfeature/core';

// use a symbol as a key for the global singleton
const GLOBAL_OPENFEATURE_API_KEY = Symbol.for('@openfeature/web-sdk/api');
Expand All @@ -19,7 +20,7 @@ type OpenFeatureGlobal = {
const _globalThis = globalThis as OpenFeatureGlobal;

export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> implements ManageContext<Promise<void>> {
protected _events = new OpenFeatureEventEmitter();
protected _events: GenericEventEmitter<ProviderEvents> = new OpenFeatureEventEmitter();
protected _defaultProvider: Provider = NOOP_PROVIDER;
protected _createEventEmitter = () => new OpenFeatureEventEmitter();
protected _namedProviderContext: Map<string, EvaluationContext> = new Map();
Expand Down Expand Up @@ -72,7 +73,7 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
if (provider) {
const oldContext = this.getContext(clientName);
this._namedProviderContext.set(clientName, context);
await this.runProviderContextChangeHandler(provider, oldContext, context);
await this.runProviderContextChangeHandler(clientName, provider, oldContext, context);
} else {
this._namedProviderContext.set(clientName, context);
}
Expand All @@ -89,7 +90,7 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme

const allProviders = [this._defaultProvider, ...providersWithoutContextOverride];
await Promise.all(
allProviders.map((provider) => this.runProviderContextChangeHandler(provider, oldContext, context)),
allProviders.map((provider) => this.runProviderContextChangeHandler(undefined, provider, oldContext, context)),
);
}
}
Expand Down Expand Up @@ -136,7 +137,7 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
const oldContext = this.getContext(clientName);
this._namedProviderContext.delete(clientName);
const newContext = this.getContext();
await this.runProviderContextChangeHandler(provider, oldContext, newContext);
await this.runProviderContextChangeHandler(clientName, provider, oldContext, newContext);
} else {
this._namedProviderContext.delete(clientName);
}
Expand Down Expand Up @@ -190,15 +191,25 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
}

private async runProviderContextChangeHandler(
clientName: string | undefined,
provider: Provider,
oldContext: EvaluationContext,
newContext: EvaluationContext,
): Promise<void> {
try {
return await provider.onContextChange?.(oldContext, newContext);
} catch (err) {
this._logger?.error(`Error running ${provider.metadata.name}'s context change handler:`, err);
}
const providerName = provider.metadata.name;
return await provider.onContextChange?.(oldContext, newContext).then(() => {
this.getAssociatedEventEmitters(clientName).forEach((emitter) => {
emitter?.emit(ProviderEvents.ContextChanged, { clientName, providerName });
});
this._events?.emit(ProviderEvents.ContextChanged, { clientName, providerName });
}).catch((err) => {
this._logger?.error(`Error running ${provider.metadata.name}'s context change handler:`, err);
this.getAssociatedEventEmitters(clientName).forEach((emitter) => {
emitter?.emit(ProviderEvents.Error, { clientName, providerName });
});
this._events?.emit(ProviderEvents.Error, { clientName, providerName });
});

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import {
JsonValue,
Logger,
OpenFeatureError,
ProviderEvents,
ResolutionDetails,
StandardResolutionReasons,
TypeMismatchError,
ProviderStatus,
} from '@openfeature/core';
import { Provider } from '../provider';
import { OpenFeatureEventEmitter } from '../../events';
import { OpenFeatureEventEmitter, ProviderEvents } from '../../events';
import { FlagConfiguration, Flag } from './flag-configuration';
import { VariantNotFoundError } from './variant-not-found-error';

Expand Down Expand Up @@ -63,6 +62,8 @@ export class InMemoryProvider implements Provider {
this.status = ProviderStatus.STALE;
this.events.emit(ProviderEvents.Stale);

// this.events.emit(ProviderEvents.ContextChanged)

this._flagConfiguration = { ...flagConfiguration };
this.events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged });

Expand Down
Loading

0 comments on commit a40bb4e

Please sign in to comment.