Repository: open-feature/js-sdk (ts)

Issue:
web-sdk: clientName in EventDetails is overriden
### Observed behavior
When writing a provider for the web-sdk, you can emit events and it is possible to provide `EventDetails` with the event.

```javascript
const events = new OpenFeatureEventEmitter();
events.emit(ProviderEvents.Error, {
    clientName: 'my-client-name',
    message: 'your message here'
});
```

When reading the event, from the handler we can see that the value of the client name is not what was provided in the event.

```javascript
await OpenFeature.setContext(defaultContext);
OpenFeature.setProvider('test-provider', defaultProvider);
const client = await OpenFeature.getClient('test-provider');
client.addHandler(ProviderEvents.Error, (event) => console.log(event));

// Result:
//{
//  clientName: 'test-provider',
//  message: 'Request failed with status code 404'
//}
```
The value of `clientName` is the client's name set in `setProvider` or `undefined` if we have no name for the provider.

### Expected Behavior
Not sure exactly how it should work here, but I see 2 potential solutions:
1. Don't expose `clientName` to providers to avoid confusion for people writing providers.
2. Use the `clientName` provided in the `emit` function and forward it in the event.

Pull request description:
feat: typesafe event emitter
## This PR

- adds type safety to the event emitter

### Related Issues

Fixes #489 

### Notes

The event emitter was split into two classes. `OpenFeatureEventEmitter` is meant to be used by developers working on OpenFeature. `InternalEventEmitter` is meant for use within the SDK. Unfortunately, the internal emitter is currently being exposed publically. This is because both the client and server SDKs expose the entire shared lib.


Patch:
diff --git a/packages/client/src/client/open-feature-client.ts b/packages/client/src/client/open-feature-client.ts
index 6e820276..20442e7d 100644
--- a/packages/client/src/client/open-feature-client.ts
+++ b/packages/client/src/client/open-feature-client.ts
@@ -49,7 +49,7 @@ export class OpenFeatureClient implements Client {
     };
   }
 
-  addHandler(eventType: ProviderEvents, handler: EventHandler): void {
+  addHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void {
     this.emitterAccessor().addHandler(eventType, handler);
     const providerReady = !this._provider.status || this._provider.status === ProviderStatus.READY;
 
@@ -63,7 +63,7 @@ export class OpenFeatureClient implements Client {
     }
   }
 
-  removeHandler(notificationType: ProviderEvents, handler: EventHandler): void {
+  removeHandler<T extends ProviderEvents>(notificationType: T, handler: EventHandler<T>): void {
     this.emitterAccessor().removeHandler(notificationType, handler);
   }
 
@@ -71,12 +71,12 @@ export class OpenFeatureClient implements Client {
     return this.emitterAccessor().getHandlers(eventType);
   }
 
-  setLogger(logger: Logger): OpenFeatureClient {
+  setLogger(logger: Logger): this {
     this._clientLogger = new SafeLogger(logger);
     return this;
   }
 
-  addHooks(...hooks: Hook<FlagValue>[]): OpenFeatureClient {
+  addHooks(...hooks: Hook<FlagValue>[]): this {
     this._hooks = [...this._hooks, ...hooks];
     return this;
   }
@@ -85,7 +85,7 @@ export class OpenFeatureClient implements Client {
     return this._hooks;
   }
 
-  clearHooks(): OpenFeatureClient {
+  clearHooks(): this {
     this._hooks = [];
     return this;
   }
diff --git a/packages/server/src/client/open-feature-client.ts b/packages/server/src/client/open-feature-client.ts
index 4a73caa4..e6331a12 100644
--- a/packages/server/src/client/open-feature-client.ts
+++ b/packages/server/src/client/open-feature-client.ts
@@ -10,7 +10,7 @@ import {
   JsonValue,
   Logger,
   OpenFeatureError,
-  OpenFeatureEventEmitter,
+  InternalEventEmitter,
   ProviderEvents,
   ProviderStatus,
   ResolutionDetails,
@@ -38,7 +38,7 @@ export class OpenFeatureClient implements Client, ManageContext<OpenFeatureClien
     // we always want the client to use the current provider,
     // so pass a function to always access the currently registered one.
     private readonly providerAccessor: () => Provider,
-    private readonly emitterAccessor: () => OpenFeatureEventEmitter,
+    private readonly emitterAccessor: () => InternalEventEmitter,
     private readonly globalLogger: () => Logger,
     private readonly options: OpenFeatureClientOptions,
     context: EvaluationContext = {}
@@ -54,7 +54,7 @@ export class OpenFeatureClient implements Client, ManageContext<OpenFeatureClien
     };
   }
 
-  addHandler(eventType: ProviderEvents, handler: EventHandler): void {
+  addHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void {
     this.emitterAccessor().addHandler(eventType, handler);
     const providerReady = !this._provider.status || this._provider.status === ProviderStatus.READY;
 
@@ -68,7 +68,7 @@ export class OpenFeatureClient implements Client, ManageContext<OpenFeatureClien
     }
   }
 
-  removeHandler(eventType: ProviderEvents, handler: EventHandler) {
+  removeHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>) {
     this.emitterAccessor().removeHandler(eventType, handler);
   }
 
diff --git a/packages/server/src/provider/provider.ts b/packages/server/src/provider/provider.ts
index f3808c26..906d7cb2 100644
--- a/packages/server/src/provider/provider.ts
+++ b/packages/server/src/provider/provider.ts
@@ -1,12 +1,4 @@
-import {
-  CommonProvider,
-  EvaluationContext,
-  HookHints,
-  JsonValue,
-  Logger,
-  ResolutionDetails,
-} from '@openfeature/shared';
-import { Hook } from '@openfeature/shared';
+import { CommonProvider, EvaluationContext, Hook, JsonValue, Logger, ResolutionDetails } from '@openfeature/shared';
 
 /**
  * Interface that providers must implement to resolve flag values for their particular
diff --git a/packages/shared/src/events/eventing.ts b/packages/shared/src/events/eventing.ts
index 93432b90..6f2d62ac 100644
--- a/packages/shared/src/events/eventing.ts
+++ b/packages/shared/src/events/eventing.ts
@@ -4,14 +4,34 @@ export type EventMetadata = {
   [key: string]: string | boolean | number;
 };
 
-export type EventDetails = {
+export type CommonEventDetails = {
   clientName?: string;
+};
+
+type CommonEventProps = {
   message?: string;
-  flagsChanged?: string[];
   metadata?: EventMetadata;
 };
 
-export type EventHandler = (eventDetails?: EventDetails) => Promise<unknown> | unknown;
+export type ReadyEvent = CommonEventProps;
+export type ErrorEvent = CommonEventProps;
+export type StaleEvent = CommonEventProps;
+export type ConfigChangeEvent = CommonEventProps & { flagsChanged?: string[] };
+
+type EventMap = {
+  [ProviderEvents.Ready]: ReadyEvent;
+  [ProviderEvents.Error]: ErrorEvent;
+  [ProviderEvents.Stale]: StaleEvent;
+  [ProviderEvents.ConfigurationChanged]: ConfigChangeEvent;
+};
+
+export type EventContext<
+  T extends ProviderEvents,
+  U extends Record<string, unknown> = Record<string, unknown>
+> = EventMap[T] & U;
+
+export type EventDetails<T extends ProviderEvents> = EventContext<T> & CommonEventDetails;
+export type EventHandler<T extends ProviderEvents> = (eventDetails?: EventDetails<T>) => Promise<unknown> | unknown;
 
 export interface Eventing {
   /**
@@ -20,19 +40,19 @@ export interface Eventing {
    * @param {ProviderEvents} eventType The provider event type to listen to
    * @param {EventHandler} handler The handler to run on occurrence of the event type
    */
-  addHandler(eventType: ProviderEvents, handler: EventHandler): void;
+  addHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void;
 
   /**
    * Removes a handler for the given provider event type.
    * @param {ProviderEvents} eventType The provider event type to remove the listener for
    * @param {EventHandler} handler The handler to remove for the provider event type
    */
-  removeHandler(eventType: ProviderEvents, handler: EventHandler): void;
+  removeHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void;
 
   /**
    * Gets the current handlers for the given provider event type.
    * @param {ProviderEvents} eventType The provider event type to get the current handlers for
    * @returns {EventHandler[]} The handlers currently attached to the given provider event type
    */
-  getHandlers(eventType: ProviderEvents): EventHandler[];
+  getHandlers<T extends ProviderEvents>(eventType: T): EventHandler<T>[];
 }
diff --git a/packages/shared/src/events/open-feature-event-emitter.ts b/packages/shared/src/events/open-feature-event-emitter.ts
index 292087df..a143e220 100644
--- a/packages/shared/src/events/open-feature-event-emitter.ts
+++ b/packages/shared/src/events/open-feature-event-emitter.ts
@@ -1,10 +1,13 @@
 import { Logger, ManageLogger, SafeLogger } from '../logger';
 import EventEmitter from 'events';
 import { ProviderEvents } from './events';
-import { EventDetails, EventHandler } from './eventing';
+import { EventContext, EventDetails, EventHandler, CommonEventDetails } from './eventing';
 
-export class OpenFeatureEventEmitter implements ManageLogger<OpenFeatureEventEmitter> {
-  private readonly _handlers = new WeakMap<EventHandler, EventHandler>();
+abstract class GenericEventEmitter<AdditionalContext extends Record<string, unknown> = Record<string, unknown>>
+  implements ManageLogger<GenericEventEmitter<AdditionalContext>>
+{
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  private readonly _handlers = new WeakMap<EventHandler<any>, EventHandler<any>>();
   private readonly eventEmitter = new EventEmitter({ captureRejections: true });
   private _eventLogger?: Logger;
 
@@ -14,14 +17,14 @@ export class OpenFeatureEventEmitter implements ManageLogger<OpenFeatureEventEmi
     });
   }
 
-  emit(eventType: ProviderEvents, context?: EventDetails): void {
+  emit<T extends ProviderEvents>(eventType: T, context?: EventContext<T, AdditionalContext>): void {
     this.eventEmitter.emit(eventType, context);
   }
 
-  addHandler(eventType: ProviderEvents, handler: EventHandler): void {
+  addHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void {
     // The handlers have to be wrapped with an async function because if a synchronous functions throws an error,
     // the other handlers will not run.
-    const asyncHandler = async (context?: EventDetails) => {
+    const asyncHandler = async (context?: EventDetails<T>) => {
       await handler(context);
     };
     // The async handler has to be written to the map, because we need to get the wrapper function when deleting a listener
@@ -29,9 +32,9 @@ export class OpenFeatureEventEmitter implements ManageLogger<OpenFeatureEventEmi
     this.eventEmitter.on(eventType, asyncHandler);
   }
 
-  removeHandler(eventType: ProviderEvents, handler: EventHandler): void {
+  removeHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void {
     // Get the wrapper function for this handler, to delete it from the event emitter
-    const asyncHandler = this._handlers.get(handler);
+    const asyncHandler = this._handlers.get(handler) as EventHandler<T> | undefined;
 
     if (!asyncHandler) {
       return;
@@ -49,8 +52,8 @@ export class OpenFeatureEventEmitter implements ManageLogger<OpenFeatureEventEmi
     }
   }
 
-  getHandlers(eventType: ProviderEvents): EventHandler[] {
-    return this.eventEmitter.listeners(eventType) as EventHandler[];
+  getHandlers<T extends ProviderEvents>(eventType: T): EventHandler<T>[] {
+    return this.eventEmitter.listeners(eventType) as EventHandler<T>[];
   }
 
   setLogger(logger: Logger): this {
@@ -59,6 +62,22 @@ export class OpenFeatureEventEmitter implements ManageLogger<OpenFeatureEventEmi
   }
 
   private get _logger() {
-    return this._eventLogger || this.globalLogger?.();
+    return this._eventLogger ?? this.globalLogger?.();
   }
 }
+
+/**
+ * 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 {};
+
+/**
+ * The InternalEventEmitter should only be used within the SDK. It extends the
+ * OpenFeatureEventEmitter to include additional properties that can be included
+ * in the event details.
+ */
+export class InternalEventEmitter extends GenericEventEmitter<CommonEventDetails> {};
\ No newline at end of file
diff --git a/packages/shared/src/open-feature.ts b/packages/shared/src/open-feature.ts
index 199320f1..da690eb1 100644
--- a/packages/shared/src/open-feature.ts
+++ b/packages/shared/src/open-feature.ts
@@ -1,13 +1,17 @@
-import { ManageTransactionContextPropagator, NOOP_TRANSACTION_CONTEXT_PROPAGATOR } from './transaction-context';
-import { objectOrUndefined, stringOrUndefined } from './type-guards';
-import { isDefined } from './filter';
-import { DefaultLogger, ManageLogger, SafeLogger } from './logger';
-import { CommonProvider, ProviderMetadata } from './provider';
-import { EventDetails, EventHandler, Eventing, OpenFeatureEventEmitter, ProviderEvents } from './events';
-import { TransactionContext, TransactionContextPropagator } from './transaction-context';
 import { EvaluationContext, FlagValue } from './evaluation';
-import { Logger } from './logger';
+import { EventDetails, EventHandler, Eventing, ProviderEvents } from './events';
+import { InternalEventEmitter } from './events/open-feature-event-emitter';
+import { isDefined } from './filter';
 import { EvaluationLifeCycle, Hook } from './hooks';
+import { DefaultLogger, Logger, ManageLogger, SafeLogger } from './logger';
+import { CommonProvider, ProviderMetadata } from './provider';
+import {
+  ManageTransactionContextPropagator,
+  NOOP_TRANSACTION_CONTEXT_PROPAGATOR,
+  TransactionContext,
+  TransactionContextPropagator,
+} from './transaction-context';
+import { objectOrUndefined, stringOrUndefined } from './type-guards';
 
 export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProvider>
   implements
@@ -23,10 +27,11 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
 
   protected abstract _defaultProvider: P;
 
-  private readonly _events = new OpenFeatureEventEmitter(() => this._logger);
-  private readonly _clientEventHandlers: Map<string | undefined, [ProviderEvents, EventHandler][]> = new Map();
+  private readonly _events = new InternalEventEmitter(() => this._logger);
+  private readonly _clientEventHandlers: Map<string | undefined, [ProviderEvents, EventHandler<ProviderEvents>][]> =
+    new Map();
   protected _clientProviders: Map<string, P> = new Map();
-  protected _clientEvents: Map<string | undefined, OpenFeatureEventEmitter> = new Map();
+  protected _clientEvents: Map<string | undefined, InternalEventEmitter> = new Map();
 
   addHooks(...hooks: Hook<FlagValue>[]): this {
     this._hooks = [...this._hooks, ...hooks];
@@ -62,7 +67,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
    * @param {ProviderEvents} eventType The provider event type to listen to
    * @param {EventHandler} handler The handler to run on occurrence of the event type
    */
-  addHandler(eventType: ProviderEvents, handler: EventHandler): void {
+  addHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void {
     this._events.addHandler(eventType, handler);
   }
 
@@ -71,7 +76,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
    * @param {ProviderEvents} eventType The provider event type to remove the listener for
    * @param {EventHandler} handler The handler to remove for the provider event type
    */
-  removeHandler(eventType: ProviderEvents, handler: EventHandler): void {
+  removeHandler<T extends ProviderEvents>(eventType: T, handler: EventHandler<T>): void {
     this._events.removeHandler(eventType, handler);
   }
 
@@ -80,7 +85,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
    * @param {ProviderEvents} eventType The provider event type to get the current handlers for
    * @returns {EventHandler[]} The handlers currently attached to the given provider event type
    */
-  getHandlers(eventType: ProviderEvents): EventHandler[] {
+  getHandlers<T extends ProviderEvents>(eventType: T): EventHandler<T>[] {
     return this._events.getHandlers(eventType);
   }
 
@@ -166,7 +171,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
     return this._clientProviders.get(name) ?? this._defaultProvider;
   }
 
-  protected getAndCacheEventEmitterForClient(name?: string): OpenFeatureEventEmitter {
+  protected getAndCacheEventEmitterForClient(name?: string): InternalEventEmitter {
     const emitter = this._clientEvents.get(name);
 
     if (emitter) {
@@ -174,12 +179,12 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
     }
 
     // lazily add the event emitters
-    const newEmitter = new OpenFeatureEventEmitter(() => this._logger);
+    const newEmitter = new InternalEventEmitter(() => this._logger);
     this._clientEvents.set(name, newEmitter);
 
     const clientProvider = this.getProviderForClient(name);
     Object.values<ProviderEvents>(ProviderEvents).forEach((eventType) =>
-      clientProvider.events?.addHandler(eventType, async (details?: EventDetails) => {
+      clientProvider.events?.addHandler(eventType, async (details) => {
         newEmitter.emit(eventType, { ...details, clientName: name });
       })
     );
@@ -187,7 +192,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
     return newEmitter;
   }
 
-  private getUnboundEmitters(): OpenFeatureEventEmitter[] {
+  private getUnboundEmitters(): InternalEventEmitter[] {
     const namedProviders = [...this._clientProviders.keys()];
     const eventEmitterNames = [...this._clientEvents.keys()].filter(isDefined);
     const unboundEmitterNames = eventEmitterNames.filter((name) => !namedProviders.includes(name));
@@ -195,7 +200,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
       // all unbound, named emitters
       ...unboundEmitterNames.map((name) => this._clientEvents.get(name)),
       // the default emitter
-      this._clientEvents.get(undefined)
+      this._clientEvents.get(undefined),
     ].filter(isDefined);
   }
 
@@ -203,26 +208,26 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
     oldProvider: P,
     newProvider: P,
     clientName: string | undefined,
-    emitters: (OpenFeatureEventEmitter | undefined)[]
+    emitters: (InternalEventEmitter | undefined)[]
   ) {
     this._clientEventHandlers
       .get(clientName)
       ?.forEach((eventHandler) => oldProvider.events?.removeHandler(...eventHandler));
 
     // iterate over the event types
-    const newClientHandlers = Object.values<ProviderEvents>(ProviderEvents).map<[ProviderEvents, EventHandler]>(
-      (eventType) => {
-        const handler = async (details?: EventDetails) => {
-          // on each event type, fire the associated handlers
-          emitters.forEach((emitter) => {
-            emitter?.emit(eventType, { ...details, clientName });
-          });
-          this._events.emit(eventType, { ...details, clientName });
-        };
+    const newClientHandlers = Object.values<ProviderEvents>(ProviderEvents).map<
+      [ProviderEvents, EventHandler<ProviderEvents>]
+    >((eventType) => {
+      const handler = async (details?: EventDetails<ProviderEvents>) => {
+        // on each event type, fire the associated handlers
+        emitters.forEach((emitter) => {
+          emitter?.emit(eventType, { ...details, clientName });
+        });
+        this._events.emit(eventType, { ...details, clientName });
+      };
 
-        return [eventType, handler];
-      }
-    );
+      return [eventType, handler];
+    });
 
     this._clientEventHandlers.set(clientName, newClientHandlers);
     newClientHandlers.forEach((eventHandler) => newProvider.events?.addHandler(...eventHandler));


Test patch:
diff --git a/packages/client/test/events.spec.ts b/packages/client/test/events.spec.ts
index e8d38a33..fae2813e 100644
--- a/packages/client/test/events.spec.ts
+++ b/packages/client/test/events.spec.ts
@@ -1,5 +1,4 @@
 import {
-  EventDetails,
   JsonValue,
   NOOP_PROVIDER,
   OpenFeature,
@@ -9,6 +8,7 @@ import {
   ProviderMetadata,
   ProviderStatus,
   ResolutionDetails,
+  StaleEvent,
 } from '../src';
 import { v4 as uuid } from 'uuid';
 
@@ -300,7 +300,7 @@ describe('Events', () => {
       const provider = new MockProvider({ failOnInit: true });
       const client = OpenFeature.getClient(clientId);
 
-      client.addHandler(ProviderEvents.Error, (details?: EventDetails) => {
+      client.addHandler(ProviderEvents.Error, (details) => {
         expect(details?.message).toBeDefined();
         done();
       });
@@ -327,7 +327,7 @@ describe('Events', () => {
       const provider = new MockProvider();
       const client = OpenFeature.getClient(clientId);
 
-      client.addHandler(ProviderEvents.Ready, (details?: EventDetails) => {
+      client.addHandler(ProviderEvents.Ready, (details) => {
         expect(details?.clientName).toEqual(clientId);
         done();
       });
@@ -339,7 +339,7 @@ describe('Events', () => {
       const provider = new MockProvider();
       const client = OpenFeature.getClient(clientId);
 
-      client.addHandler(ProviderEvents.Ready, (details?: EventDetails) => {
+      client.addHandler(ProviderEvents.Ready, (details) => {
         expect(details?.clientName).toEqual(clientId);
         done();
       });
@@ -350,11 +350,11 @@ describe('Events', () => {
 
   describe('Requirement 5.2.4', () => {
     it('The handler function accepts a event details parameter.', (done) => {
-      const details: EventDetails = { message: 'message' };
+      const details: StaleEvent = { message: 'message' };
       const provider = new MockProvider();
       const client = OpenFeature.getClient(clientId);
 
-      client.addHandler(ProviderEvents.Stale, (givenDetails?: EventDetails) => {
+      client.addHandler(ProviderEvents.Stale, (givenDetails) => {
         expect(givenDetails?.message).toEqual(details.message);
         done();
       });
diff --git a/packages/server/test/events.spec.ts b/packages/server/test/events.spec.ts
index 055ba1be..25e20cac 100644
--- a/packages/server/test/events.spec.ts
+++ b/packages/server/test/events.spec.ts
@@ -1,5 +1,4 @@
 import {
-  EventDetails,
   JsonValue,
   OpenFeature,
   OpenFeatureEventEmitter,
@@ -8,9 +7,10 @@ import {
   ProviderMetadata,
   ProviderStatus,
   ResolutionDetails,
+  NOOP_PROVIDER,
+  StaleEvent,
 } from '../src';
 import { v4 as uuid } from 'uuid';
-import { NOOP_PROVIDER } from '../src';
 
 class MockProvider implements Provider {
   readonly metadata: ProviderMetadata;
@@ -303,7 +303,7 @@ describe('Events', () => {
       const provider = new MockProvider({ failOnInit: true });
       const client = OpenFeature.getClient(clientId);
 
-      client.addHandler(ProviderEvents.Error, (details?: EventDetails) => {
+      client.addHandler(ProviderEvents.Error, (details) => {
         expect(details?.message).toBeDefined();
         done();
       });
@@ -330,7 +330,7 @@ describe('Events', () => {
       const provider = new MockProvider();
       const client = OpenFeature.getClient(clientId);
 
-      client.addHandler(ProviderEvents.Ready, (details?: EventDetails) => {
+      client.addHandler(ProviderEvents.Ready, (details) => {
         expect(details?.clientName).toEqual(clientId);
         done();
       });
@@ -342,7 +342,7 @@ describe('Events', () => {
       const provider = new MockProvider();
       const client = OpenFeature.getClient(clientId);
 
-      client.addHandler(ProviderEvents.Ready, (details?: EventDetails) => {
+      client.addHandler(ProviderEvents.Ready, (details) => {
         expect(details?.clientName).toEqual(clientId);
         done();
       });
@@ -353,11 +353,11 @@ describe('Events', () => {
 
   describe('Requirement 5.2.4', () => {
     it('The handler function accepts a event details parameter.', (done) => {
-      const details: EventDetails = { message: 'message' };
+      const details: StaleEvent = { message: 'message' };
       const provider = new MockProvider();
       const client = OpenFeature.getClient(clientId);
 
-      client.addHandler(ProviderEvents.Stale, (givenDetails?: EventDetails) => {
+      client.addHandler(ProviderEvents.Stale, (givenDetails) => {
         expect(givenDetails?.message).toEqual(details.message);
         done();
       });
diff --git a/packages/shared/test/events.spec.ts b/packages/shared/test/events.spec.ts
index 3daddccf..40eee52b 100644
--- a/packages/shared/test/events.spec.ts
+++ b/packages/shared/test/events.spec.ts
@@ -1,4 +1,4 @@
-import { EventDetails, OpenFeatureEventEmitter, ProviderEvents, Logger } from '../src';
+import { OpenFeatureEventEmitter, ProviderEvents, Logger, ReadyEvent } from '../src';
 
 describe('OpenFeatureEventEmitter', () => {
   describe('addHandler should', function () {
@@ -60,7 +60,7 @@ describe('OpenFeatureEventEmitter', () => {
     });
 
     it('trigger handler for event type with event data', function () {
-      const event: EventDetails = { message: 'message' };
+      const event: ReadyEvent = { message: 'message' };
       const emitter = new OpenFeatureEventEmitter();
 
       const handler1 = jest.fn();