Repository: maxnowack/signaldb
Pull request #1287 (2025-01-10 10:50:13)

refactor: replace EventEmitter with custom class based on EventTarget

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/maxnowack/signaldb/pull/1287?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`c6a3d2a`)](https://app.codecov.io/gh/maxnowack/signaldb/commit/c6a3d2aca97a4b889356ad8797308a79afa430c0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack) to head [(`be07def`)](https://app.codecov.io/gh/maxnowack/signaldb/commit/be07def04b31f3317fd0c9d3b87384edc6e1d83e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main     #1287   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           55        55           
  Lines         1629      1684   +55     
  Branches       364       375   +11     
=========================================
+ Hits          1629      1684   +55     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/maxnowack/signaldb/pull/1287?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack).


Patch:
diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml
index 3305472..6360689 100644
--- a/.github/workflows/checks.yaml
+++ b/.github/workflows/checks.yaml
@@ -16,7 +16,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        node-version: [18.x, 20.x, 22.x, 23.x]
+        node-version: [20.x, 22.x, 23.x]
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-node@v4
@@ -88,7 +88,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        node-version: [18.x, 20.x, 22.x, 23.x]
+        node-version: [20.x, 22.x, 23.x]
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-node@v4
@@ -118,7 +118,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        node-version: [18.x, 20.x, 22.x, 23.x]
+        node-version: [20.x, 22.x, 23.x]
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-node@v4
diff --git a/docs/sync/index.md b/docs/sync/index.md
index da0f47d..fa45213 100644
--- a/docs/sync/index.md
+++ b/docs/sync/index.md
@@ -309,8 +309,7 @@ const syncManager = new SyncManager({
 Below is an example implementation of a simple REST API.
 
 ```js
-import { EventEmitter } from 'events'
-import { Collection, SyncManager } from '@signaldb/core'
+import { Collection, SyncManager, EventEmitter } from '@signaldb/core'
 
 const Authors = new Collection()
 const Posts = new Collection()
diff --git a/packages/base/core/CHANGELOG.md b/packages/base/core/CHANGELOG.md
index 53fc667..b7a0371 100644
--- a/packages/base/core/CHANGELOG.md
+++ b/packages/base/core/CHANGELOG.md
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Changed
+* Switched from native EventEmitter to custom class that is based on EventTarget
+
 ## [1.0.0] - 2024-12-16
 
 ### Added
diff --git a/packages/base/core/src/Collection/index.ts b/packages/base/core/src/Collection/index.ts
index 94364c0..4765125 100644
--- a/packages/base/core/src/Collection/index.ts
+++ b/packages/base/core/src/Collection/index.ts
@@ -1,7 +1,7 @@
 import type MemoryAdapter from '../types/MemoryAdapter'
 import type ReactivityAdapter from '../types/ReactivityAdapter'
 import type PersistenceAdapter from '../types/PersistenceAdapter'
-import EventEmitter from '../types/EventEmitter'
+import EventEmitter from '../utils/EventEmitter'
 import type Selector from '../types/Selector'
 import type Modifier from '../types/Modifier'
 import type IndexProvider from '../types/IndexProvider'
diff --git a/packages/base/core/src/index.ts b/packages/base/core/src/index.ts
index 5bc9654..976a90c 100644
--- a/packages/base/core/src/index.ts
+++ b/packages/base/core/src/index.ts
@@ -28,3 +28,4 @@ export { default as createReactivityAdapter } from './createReactivityAdapter'
 export { default as isEqual } from './utils/isEqual'
 export { default as modify } from './utils/modify'
 export { default as randomId } from './utils/randomId'
+export { default as EventEmitter } from './utils/EventEmitter'
diff --git a/packages/base/core/src/types/EventEmitter.ts b/packages/base/core/src/types/EventEmitter.ts
deleted file mode 100644
index 2c8bb19..0000000
--- a/packages/base/core/src/types/EventEmitter.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { EventEmitter as BaseEventEmitter } from 'events'
-
-/**
- * Extends the `EventEmitter` class from Node.js to provide a strongly-typed
- * event emitter that enforces type safety for events and their corresponding listeners.
- * @template Events - A record where keys represent event names (string or symbol),
- *   and values represent the listener function types for those events.
- */
-export default class EventEmitter<
-  Events extends Record<string | symbol, any>,
-> extends BaseEventEmitter {
-  /**
-   * Registers a listener for the specified event.
-   * @template K - The key of the event in the `Events` record.
-   * @param event - The name of the event to listen for.
-   * @param listener - The listener function to execute when the event is emitted.
-   * @returns The `EventEmitter` instance, allowing for method chaining.
-   */
-  on<K extends keyof Events>(event: K, listener: Events[K]) {
-    super.on(event as string, listener as (...args: any[]) => void)
-    return this
-  }
-
-  /**
-   * Emits the specified event, triggering all registered listeners for that event.
-   * @template K - The key of the event in the `Events` record.
-   * @param event - The name of the event to emit.
-   * @param args - The arguments to pass to the event listeners.
-   * @returns A boolean indicating whether any listeners were triggered.
-   */
-  emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>) {
-    return super.emit(event as string, ...args)
-  }
-}
diff --git a/packages/base/core/src/utils/EventEmitter.spec.ts b/packages/base/core/src/utils/EventEmitter.spec.ts
new file mode 100644
index 0000000..120c3fc
--- /dev/null
+++ b/packages/base/core/src/utils/EventEmitter.spec.ts
@@ -0,0 +1,224 @@
+/**
+ * Example test suite for the EventEmitter class using Vitest.
+ *
+ * IMPORTANT: This is a single example test file. In your project,
+ * make sure to organize tests according to your conventions.
+ *
+ * Usage:
+ *   1. Install vitest (npm install -D vitest).
+ *   2. Create a file named "EventEmitter.test.ts" (or similar).
+ *   3. Paste this code into that file.
+ *   4. Run tests with "npx vitest" or "npm run test" (depending on your setup).
+ */
+
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import EventEmitter from './EventEmitter'
+
+// Example event shape:
+interface MyEvents {
+  hello: (name: string) => void,
+  goodbye: (reason: string) => void,
+  noArgsEvent: () => void,
+}
+
+describe('EventEmitter', () => {
+  let emitter: EventEmitter<MyEvents>
+
+  beforeEach(() => {
+    emitter = new EventEmitter<MyEvents>()
+  })
+
+  it('should subscribe to an event using on() and call listeners with correct args', () => {
+    const helloListener = vi.fn()
+    emitter.on('hello', helloListener)
+
+    emitter.emit('hello', 'Alice')
+    expect(helloListener).toHaveBeenCalledTimes(1)
+    expect(helloListener).toHaveBeenCalledWith('Alice')
+  })
+
+  it('should subscribe to an event using addListener() (alias of on())', () => {
+    const goodbyeListener = vi.fn()
+    emitter.addListener('goodbye', goodbyeListener)
+
+    emitter.emit('goodbye', 'lack of coffee')
+    expect(goodbyeListener).toHaveBeenCalledTimes(1)
+    expect(goodbyeListener).toHaveBeenCalledWith('lack of coffee')
+  })
+
+  it('should handle once() correctly (listener fires only once)', () => {
+    const onceListener = vi.fn()
+    emitter.once('hello', onceListener)
+
+    emitter.emit('hello', 'Bob')
+    emitter.emit('hello', 'Charlie')
+
+    expect(onceListener).toHaveBeenCalledTimes(1)
+    expect(onceListener).toHaveBeenCalledWith('Bob')
+  })
+
+  it('should be able to remove a listener with off()', () => {
+    const helloListener = vi.fn()
+    emitter.on('hello', helloListener)
+    emitter.emit('hello', 'David')
+    expect(helloListener).toHaveBeenCalledTimes(1)
+
+    emitter.off('hello', helloListener)
+    // Emitting again should have no effect now.
+    emitter.emit('hello', 'Eve')
+    expect(helloListener).toHaveBeenCalledTimes(1)
+  })
+
+  it('should do nothing if off() is called with an event that has no listeners', () => {
+    // Just ensure it does not throw
+    expect(() => {
+      emitter.off('hello', vi.fn())
+    }).not.toThrow()
+  })
+
+  it('should do nothing if off() is called on a listener that is not in the store', () => {
+    const helloListener = vi.fn()
+    // Add a listener
+    emitter.on('hello', helloListener)
+    // Attempt to remove a different function
+    const differentListener = vi.fn()
+
+    expect(() => {
+      emitter.off('hello', differentListener)
+    }).not.toThrow()
+
+    // Original listener is still in place
+    emitter.emit('hello', 'Frank')
+    expect(helloListener).toHaveBeenCalledTimes(1)
+  })
+
+  it('should remove a listener using removeListener() (alias of off())', () => {
+    const goodbyeListener = vi.fn()
+    emitter.addListener('goodbye', goodbyeListener)
+    emitter.emit('goodbye', 'lack of coffee')
+    expect(goodbyeListener).toHaveBeenCalledTimes(1)
+
+    emitter.removeListener('goodbye', goodbyeListener)
+    emitter.emit('goodbye', 'lack of coffee again')
+    expect(goodbyeListener).toHaveBeenCalledTimes(1)
+  })
+
+  it('should return true/false from emit() based on preventDefault()', () => {
+    // By default, events are not cancelable, but CustomEvent is by default set to be cancelable = false
+    // If you want to test cancellation logic, you can set "cancelable: true" in the event init
+    // For demonstration, let's do a small patch:
+
+    const mockDispatchEvent = vi.spyOn(emitter, 'dispatchEvent')
+      .mockImplementation((event: Event) => {
+        // We can simulate a cancellation condition here
+        // e.g., some listeners might call evt.preventDefault() if it's cancelable
+        if (event.cancelable) {
+          event.preventDefault()
+          return !event.defaultPrevented // false
+        }
+        // If it's not cancelable, it returns true
+        return true
+      })
+
+    // By default, our CustomEvent in emit() is not specifying "cancelable: true",
+    // so this should return true
+    const result = emitter.emit('hello', 'CancelTest')
+    expect(result).toBe(true)
+
+    mockDispatchEvent.mockRestore()
+  })
+
+  it('should return the array of listener functions with listeners()', () => {
+    const fn1 = vi.fn()
+    const fn2 = vi.fn()
+    emitter.on('hello', fn1)
+    emitter.on('hello', fn2)
+
+    const allListeners = emitter.listeners('hello')
+    expect(allListeners).toContain(fn1)
+    expect(allListeners).toContain(fn2)
+    expect(allListeners.length).toBe(2)
+  })
+
+  it('should return an empty array if no listeners exist for a given event', () => {
+    const noListeners = emitter.listeners('goodbye')
+    expect(noListeners).toEqual([])
+  })
+
+  it('should return the correct listenerCount()', () => {
+    expect(emitter.listenerCount('hello')).toBe(0)
+
+    const fn = vi.fn()
+    emitter.on('hello', fn)
+    expect(emitter.listenerCount('hello')).toBe(1)
+
+    emitter.off('hello', fn)
+    expect(emitter.listenerCount('hello')).toBe(0)
+  })
+
+  it('should remove all listeners for a specific event', () => {
+    const fn1 = vi.fn()
+    const fn2 = vi.fn()
+    emitter.on('hello', fn1).on('hello', fn2)
+    expect(emitter.listenerCount('hello')).toBe(2)
+
+    emitter.removeAllListeners('hello')
+    expect(emitter.listenerCount('hello')).toBe(0)
+  })
+
+  it('should remove all listeners for all events when removeAllListeners() is called without an event name', () => {
+    const helloListener = vi.fn()
+    const goodbyeListener = vi.fn()
+    emitter.on('hello', helloListener)
+    emitter.on('goodbye', goodbyeListener)
+    expect(emitter.listenerCount('hello')).toBe(1)
+    expect(emitter.listenerCount('goodbye')).toBe(1)
+
+    emitter.removeAllListeners()
+    expect(emitter.listenerCount('hello')).toBe(0)
+    expect(emitter.listenerCount('goodbye')).toBe(0)
+  })
+
+  it('should emit an event that has no listeners without error', () => {
+    // No listeners for "noArgsEvent"
+    expect(() => {
+      emitter.emit('noArgsEvent')
+    }).not.toThrow()
+  })
+
+  it('should set the maximum number of listeners using setMaxListeners()', () => {
+    emitter.setMaxListeners(10)
+
+    const maxListenersCount = (emitter as any)._maxListeners
+    expect(maxListenersCount).toBe(10)
+  })
+
+  it('should warn if listeners exceed the default limit of 100', () => {
+    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+
+    // Add 101 listeners to the same event
+    for (let i = 0; i <= 100; i++) {
+      emitter.on('hello', () => {})
+    }
+
+    expect(warnSpy).toHaveBeenCalledOnce()
+    warnSpy.mockRestore()
+  })
+
+  it('should respect the custom limit set by setMaxListeners()', () => {
+    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+
+    emitter.setMaxListeners(3)
+    emitter.on('hello', () => {})
+    emitter.on('hello', () => {})
+    emitter.on('hello', () => {})
+    // We are at the limit, so no warning yet
+    expect(warnSpy).not.toHaveBeenCalled()
+
+    // One more listener triggers a warning
+    emitter.on('hello', () => {})
+    expect(warnSpy).toHaveBeenCalledOnce()
+
+    warnSpy.mockRestore()
+  })
+})
diff --git a/packages/base/core/src/utils/EventEmitter.ts b/packages/base/core/src/utils/EventEmitter.ts
new file mode 100644
index 0000000..cab53d3
--- /dev/null
+++ b/packages/base/core/src/utils/EventEmitter.ts
@@ -0,0 +1,202 @@
+/**
+ * A strongly‑typed EventEmitter using the native EventTarget under the hood.
+ */
+export default class EventEmitter<
+  Events extends Record<string | symbol, any>,
+> extends EventTarget {
+  private _maxListeners = 100
+
+  /**
+   * We store a map of:
+   *   eventName => (originalListener => wrappedListener)
+   *
+   * The "wrappedListener" is the actual function passed to `addEventListener()`.
+   */
+  private _listenerStore = new Map<
+    keyof Events,
+    Map<Events[keyof Events], EventListener>
+  >()
+
+  public setMaxListeners(max: number): this {
+    this._maxListeners = max
+    return this
+  }
+
+  /**
+   * Subscribe to an event with a listener function.
+   * @param eventName The event name (key of E).
+   * @param listener  A function that receives the emitted arguments.
+   * @returns         The emitter instance (for chaining).
+   */
+  public on<K extends keyof Events>(
+    eventName: K,
+    listener: Events[K],
+  ): this {
+    // Get or create the Map for this particular event name.
+    let listenersMap = this._listenerStore.get(eventName)
+    if (!listenersMap) {
+      listenersMap = new Map()
+      this._listenerStore.set(eventName, listenersMap)
+    }
+
+    // Wrap the user-supplied listener in an EventListener that
+    // unpacks the `detail` array from the CustomEvent.
+    const wrappedListener: EventListener = (event: Event) => {
+      const customEvent = event as CustomEvent<Parameters<Events[K]>>
+      // “detail” is an array of arguments for this event.
+      listener(...(customEvent.detail ?? []))
+    }
+
+    // Store the mapping of original => wrapped.
+    listenersMap.set(listener, wrappedListener)
+
+    // Actually subscribe using the native EventTarget API.
+    this.addEventListener(eventName as string, wrappedListener)
+
+    if (listenersMap.size > this._maxListeners) {
+      // eslint-disable-next-line no-console
+      console.warn(
+        `Possible EventEmitter memory leak detected. ${listenersMap.size} ${String(eventName)} listeners added. Use emitter.setMaxListeners() to increase limit.`,
+      )
+    }
+
+    return this
+  }
+
+  /**
+   * Subscribe to an event with a listener function.
+   * @param eventName The event name (key of E).
+   * @param listener  A function that receives the emitted arguments.
+   * @returns         The emitter instance (for chaining).
+   */
+  public addListener<K extends keyof Events>(
+    eventName: K,
+    listener: Events[K],
+  ) {
+    return this.on(eventName, listener)
+  }
+
+  /**
+   * Subscribe to an event, handling it only once. Automatically removes
+   * the listener after it fires the first time.
+   * @param eventName The event name (key of E).
+   * @param listener  A function that receives the emitted arguments.
+   * @returns         The emitter instance (for chaining).
+   */
+  public once<K extends keyof Events>(
+    eventName: K,
+    listener: Events[K],
+  ): this {
+    // We define a wrapper that calls the listener once, then unsubscribes itself.
+    const onceWrapper = ((...args: Parameters<Events[K]>) => {
+      listener(...args)
+      this.off(eventName, onceWrapper)
+    }) as Events[K]
+
+    // Important: explicitly specify <K> to ensure TS sees the same type param
+    return this.on<K>(eventName, onceWrapper)
+  }
+
+  /**
+   * Unsubscribe a previously subscribed listener.
+   * @param eventName The event name (key of E).
+   * @param listener  The original function passed to `on` or `once`.
+   * @returns         The emitter instance (for chaining).
+   */
+  public off<K extends keyof Events>(
+    eventName: K,
+    listener: Events[K],
+  ): this {
+    const listenersMap = this._listenerStore.get(eventName)
+    if (!listenersMap) return this
+
+    const wrappedListener = listenersMap.get(listener)
+    if (wrappedListener) {
+      this.removeEventListener(eventName as string, wrappedListener)
+      listenersMap.delete(listener)
+    }
+
+    // Clean up if there are no more listeners for that event.
+    if (listenersMap.size === 0) {
+      this._listenerStore.delete(eventName)
+    }
+
+    return this
+  }
+
+  /**
+   * Unsubscribe a previously subscribed listener.
+   * @param eventName The event name (key of E).
+   * @param listener  The original function passed to `on` or `once`.
+   * @returns         The emitter instance (for chaining).
+   */
+  public removeListener<K extends keyof Events>(
+    eventName: K,
+    listener: Events[K],
+  ) {
+    return this.off(eventName, listener)
+  }
+
+  /**
+   * Emit (dispatch) an event with a variable number of arguments.
+   * @param eventName The event name (key of E).
+   * @param args      The arguments to pass to subscribed listeners.
+   * @returns         A boolean indicating if the event was not cancelled.
+   */
+  public emit<K extends keyof Events>(eventName: K, ...args: Parameters<Events[K]>): boolean {
+    const event = new CustomEvent<Parameters<Events[K]>>(eventName as string, { detail: args })
+    // dispatchEvent returns false if an event was cancelled by
+    // a listener calling evt.preventDefault().
+    return this.dispatchEvent(event)
+  }
+
+  /**
+   * Returns the array of listener functions currently registered for a given event.
+   * @param eventName The event name (key of E).
+   * @returns         An array of listener functions.
+   */
+  public listeners<K extends keyof Events>(
+    eventName: K,
+  ): Array<(...args: Parameters<Events[K]>) => void> {
+    const listenersMap = this._listenerStore.get(eventName)
+    if (!listenersMap) return []
+    return [...listenersMap.keys()]
+  }
+
+  /**
+   * Returns the number of listeners for a given event.
+   * @param eventName The event name (key of E).
+   * @returns         The number of listeners.
+   */
+  public listenerCount<K extends keyof Events>(eventName: K): number {
+    const listenersMap = this._listenerStore.get(eventName)
+    return listenersMap ? listenersMap.size : 0
+  }
+
+  /**
+   * Removes all listeners for a given event, or all events if none is specified.
+   * @param eventName Optional. If omitted, clears all events’ listeners.
+   * @returns         The emitter instance (for chaining).
+   */
+  public removeAllListeners<K extends keyof Events>(eventName?: K): this {
+    if (eventName === undefined) {
+      // Remove listeners for all events
+      for (const [eventName_, listenersMap] of this._listenerStore.entries()) {
+        for (const wrappedListener of listenersMap.values()) {
+          this.removeEventListener(eventName_ as string, wrappedListener)
+        }
+      }
+      this._listenerStore.clear()
+    } else {
+      const listenersMap = this._listenerStore.get(eventName)
+      if (listenersMap) {
+        for (const wrappedListener of listenersMap.values()) {
+          this.removeEventListener(eventName as string, wrappedListener)
+        }
+        this._listenerStore.delete(eventName)
+      }
+    }
+
+    return this
+  }
+}
diff --git a/packages/base/core/vite.config.mts b/packages/base/core/vite.config.mts
index 10783cc..9f7d85f 100644
--- a/packages/base/core/vite.config.mts
+++ b/packages/base/core/vite.config.mts
@@ -31,7 +31,6 @@ export default defineConfig({
         'fast-sort',
         'mingo',
         'mingo/updater',
-        'events',
       ],
       plugins: [
         typescriptPaths({


Test patch:
diff --git a/packages/base/core/__tests__/helpers/waitForEvent.spec.ts b/packages/base/core/__tests__/helpers/waitForEvent.spec.ts
index 7c430b4..98c9958 100644
--- a/packages/base/core/__tests__/helpers/waitForEvent.spec.ts
+++ b/packages/base/core/__tests__/helpers/waitForEvent.spec.ts
@@ -1,5 +1,5 @@
-import EventEmitter from 'events'
 import { it, expect, describe } from 'vitest'
+import { EventEmitter } from '../../src'
 import waitForEvent from './waitForEvent'
 
 describe('waitForEvent', () => {
diff --git a/packages/base/core/__tests__/helpers/waitForEvent.ts b/packages/base/core/__tests__/helpers/waitForEvent.ts
index 0110323..0a3a9d4 100644
--- a/packages/base/core/__tests__/helpers/waitForEvent.ts
+++ b/packages/base/core/__tests__/helpers/waitForEvent.ts
@@ -1,8 +1,8 @@
 /* eslint-disable @typescript-eslint/no-shadow */
 /* eslint-disable @typescript-eslint/no-unused-vars */
-import type EventEmitter from 'events'
+import type { EventEmitter } from '../../src'
 
-type EventNames<E extends EventEmitter> = E extends {
+type EventNames<E extends EventEmitter<any>> = E extends {
   on(event: infer E, listener: (...args: any[]) => void): any,
 } ? E : never
 
@@ -16,7 +16,7 @@ type EventNames<E extends EventEmitter> = E extends {
  * @param timeout - An optional timeout in milliseconds. If specified, the promise will reject if the event is not emitted within the timeout period.
  * @returns A promise that resolves with the event data when the event is emitted.
  */
-export default async function waitForEvent<E extends EventEmitter, T>(
+export default async function waitForEvent<E extends EventEmitter<any>, T>(
   emitter: E,
   event: EventNames<E>,
   timeout?: number,
diff --git a/packages/persistence-adapters/fs/__tests__/adapter.spec.ts b/packages/persistence-adapters/fs/__tests__/adapter.spec.ts
index 64012c4..04c124b 100644
--- a/packages/persistence-adapters/fs/__tests__/adapter.spec.ts
+++ b/packages/persistence-adapters/fs/__tests__/adapter.spec.ts
@@ -1,5 +1,5 @@
 import fs from 'fs/promises'
-import type EventEmitter from 'events'
+import type { EventEmitter } from '@signaldb/core'
 import { it, expect } from 'vitest'
 import { Collection } from '@signaldb/core'
 import createFilesystemAdapter from '../src'
diff --git a/packages/persistence-adapters/indexeddb/__tests__/adapter.spec.ts b/packages/persistence-adapters/indexeddb/__tests__/adapter.spec.ts
index c3923da..559e4fa 100644
--- a/packages/persistence-adapters/indexeddb/__tests__/adapter.spec.ts
+++ b/packages/persistence-adapters/indexeddb/__tests__/adapter.spec.ts
@@ -1,5 +1,5 @@
 // @vitest-environment happy-dom
-import type EventEmitter from 'events'
+import type { EventEmitter } from '@signaldb/core'
 import { describe, it, expect } from 'vitest'
 import { Collection } from '@signaldb/core'
 import 'fake-indexeddb/auto'
@@ -13,7 +13,7 @@ import createIndexedDBAdapter from '../src'
  * @returns A promise that resolves with the event value.
  */
 async function waitForEvent<T>(
-  emitter: EventEmitter,
+  emitter: EventEmitter<any>,
   event: string,
   timeout?: number,
 ): Promise<T> {
diff --git a/packages/persistence-adapters/localstorage/__tests__/adapter.spec.ts b/packages/persistence-adapters/localstorage/__tests__/adapter.spec.ts
index 8623940..7101a2b 100644
--- a/packages/persistence-adapters/localstorage/__tests__/adapter.spec.ts
+++ b/packages/persistence-adapters/localstorage/__tests__/adapter.spec.ts
@@ -1,5 +1,5 @@
 // @vitest-environment happy-dom
-import type EventEmitter from 'events'
+import type { EventEmitter } from '@signaldb/core'
 import { describe, it, expect } from 'vitest'
 import { Collection } from '@signaldb/core'
 import createLocalStorageAdapter from '../src'
@@ -12,7 +12,7 @@ import createLocalStorageAdapter from '../src'
  * @returns A promise that resolves with the event value.
  */
 async function waitForEvent<T>(
-  emitter: EventEmitter,
+  emitter: EventEmitter<any>,
   event: string,
   timeout?: number,
 ): Promise<T> {
diff --git a/packages/persistence-adapters/opfs/__tests__/adapter.spec.ts b/packages/persistence-adapters/opfs/__tests__/adapter.spec.ts
index ac8aec3..143824f 100644
--- a/packages/persistence-adapters/opfs/__tests__/adapter.spec.ts
+++ b/packages/persistence-adapters/opfs/__tests__/adapter.spec.ts
@@ -1,5 +1,5 @@
 // @vitest-environment happy-dom
-import type EventEmitter from 'events'
+import type { EventEmitter } from '@signaldb/core'
 import { describe, it, expect } from 'vitest'
 import { Collection } from '@signaldb/core'
 import createOPFSAdapter from '../src'
@@ -12,7 +12,7 @@ import createOPFSAdapter from '../src'
  * @returns A promise that resolves with the event value.
  */
 async function waitForEvent<T>(
-  emitter: EventEmitter,
+  emitter: EventEmitter<any>,
   event: string,
   timeout?: number,
 ): Promise<T> {