{"_id":"@asaidimu/events","_rev":"4-ff5dc6be24ead47466943b4e0f951026","name":"@asaidimu/events","dist-tags":{"latest":"1.1.2"},"versions":{"1.0.0":{"name":"@asaidimu/events","version":"1.0.0","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/events@1.0.0","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/events#readme","bugs":{"url":"https://github.com/asaidimu/events/issues"},"dist":{"shasum":"875672b067c02a78377a23abd4a5cbdec0586731","tarball":"https://registry.npmjs.org/@asaidimu/events/-/events-1.0.0.tgz","fileCount":7,"integrity":"sha512-q5/3BAte1OSQcZ2c7vzxcBDbgQBvXxy54FYRXPMETVuIhrB/jFb2kQUKCCDGfA+mR6vJXuqkswQjFezgOPlYTA==","signatures":[{"sig":"MEUCIQC4qEqIL/pgy3d5/dpDZYTkfSa+9dfOLVHoIlZGene7ogIgC6v2k/fd6/vi3RYvL84eC5b4zRII1F66e9VTc5uugSY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14387},"main":"index.js","types":"index.d.ts","gitHead":"705a1fd878e1fd9bb413fce71f2abd0c9927cc25","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/events.git","type":"git"},"_npmVersion":"10.8.2","description":"A lightweight, type-safe event bus implementation for TypeScript applications with zero dependencies.","directories":{},"_nodeVersion":"20.18.0","dependencies":{},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/events_1.0.0_1738138148468_0.6766038408249022","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@asaidimu/events","version":"1.1.0","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/events@1.1.0","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/events#readme","bugs":{"url":"https://github.com/asaidimu/events/issues"},"dist":{"shasum":"cc5d24f7845e7bdbdce847a5bff4c1538b5193d6","tarball":"https://registry.npmjs.org/@asaidimu/events/-/events-1.1.0.tgz","fileCount":7,"integrity":"sha512-v59ROuOVgVQrrjzVB6NKueYK0pJffjafjL0TpzsanDNREuxlCXyNrXuW98VVjwd1GjCrM2wlGcBaYWU6z1ILRQ==","signatures":[{"sig":"MEUCICq4wu1pQbL/W+7AU+r6T7nf5axLJJC/WNQuZzM/3rI0AiEA4p3UxXAZ3ZJReeCHT8bMvtf5JGoWumbSK7OObwvvxXk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15598},"main":"index.js","types":"index.d.ts","gitHead":"06d3762ea20d2fc3c86e85b4240b2d3a12bc9679","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/events.git","type":"git"},"_npmVersion":"10.8.2","description":"A lightweight, type-safe event bus implementation for TypeScript applications with zero dependencies.","directories":{},"_nodeVersion":"20.18.0","dependencies":{},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/events_1.1.0_1740324091167_0.11692173062779654","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@asaidimu/events","version":"1.1.1","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/events@1.1.1","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/events#readme","bugs":{"url":"https://github.com/asaidimu/events/issues"},"dist":{"shasum":"1fc67104eb6f9ebd17d594dadbeb9be1634d2c41","tarball":"https://registry.npmjs.org/@asaidimu/events/-/events-1.1.1.tgz","fileCount":7,"integrity":"sha512-L3GNT8V32Rp12lsIN3Pm0IAYqMP6aYaxmapmcmL0+4g4MXBFzxxLfGfcliLx4U5RhnG/cJl6wXJhbeLgidD7vQ==","signatures":[{"sig":"MEQCIDKzsZHArr4rdCWqI+8M1ACQ+5EVuQr8q9V8Iz6RDVQYAiBRzxsoRg59ErPLPH3Q5EyfOeatMg3adwU6ORSbJKb2pQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20072},"main":"index.js","types":"index.d.ts","gitHead":"91c68bcaa4cc72dbb581ffe8957f04fda2e73e7a","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/events.git","type":"git"},"_npmVersion":"10.8.2","description":"A lightweight, type-safe event bus implementation for TypeScript applications with zero dependencies.","directories":{},"_nodeVersion":"20.18.0","dependencies":{},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/events_1.1.1_1740336019553_0.4660789198485846","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@asaidimu/events","version":"1.1.2","description":"A lightweight, type-safe event bus implementation for TypeScript applications with zero dependencies.","main":"index.js","types":"index.d.ts","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/asaidimu/events.git"},"bugs":{"url":"https://github.com/asaidimu/events/issues"},"homepage":"https://github.com/asaidimu/events#readme","publishConfig":{"registry":"https://registry.npmjs.org/","tag":"latest","access":"public"},"dependencies":{},"_id":"@asaidimu/events@1.1.2","gitHead":"474e34c8d972c6ebd546e994762c1c0dfd26348c","_nodeVersion":"20.19.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-cf7GQgabNY4/UwYNGlOMqAHvMu1osNV/T24RPCKxF0XAvU42YWT3/ACXETC9knOVlEpoKKK4SShoBOKkS7eylw==","shasum":"9066c75481228537f9b5471c23f57a946f4daa94","tarball":"https://registry.npmjs.org/@asaidimu/events/-/events-1.1.2.tgz","fileCount":18,"unpackedSize":121538,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCtE56u2CdYokyU6zmmDb2d0pTNsAZEHntHW+F9lVzL1gIgVC8NSi7WR4k/KDXxwGkhY7l5JsWCanMu9gnJeMIzskI="}]},"_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"directories":{},"maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/events_1.1.2_1749817020511_0.869525199913403"},"_hasShrinkwrap":false}},"time":{"created":"2025-01-29T08:09:08.326Z","modified":"2025-06-13T12:17:00.866Z","1.0.0":"2025-01-29T08:09:08.659Z","1.1.0":"2025-02-23T15:21:31.364Z","1.1.1":"2025-02-23T18:40:19.733Z","1.1.2":"2025-06-13T12:17:00.698Z"},"bugs":{"url":"https://github.com/asaidimu/events/issues"},"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","homepage":"https://github.com/asaidimu/events#readme","keywords":["typescript"],"repository":{"type":"git","url":"git+https://github.com/asaidimu/events.git"},"description":"A lightweight, type-safe event bus implementation for TypeScript applications with zero dependencies.","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"readme":"# @asaidimu/events\n\nA lightweight, type-safe event bus implementation for TypeScript applications with zero dependencies.\n\n[![npm version](https://img.shields.io/npm/v/@asaidimu/events.svg?style=flat-square)](https://www.npmjs.com/package/@asaidimu/events)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/asaidimu/events/blob/main/LICENSE.md)\n[![Build Status](https://github.com/asaidimu/events/workflows/CI/badge.svg)](https://github.com/asaidimu/events/actions?query=workflow%3ACI)\n[![Tests](https://img.shields.io/github/actions/workflow/status/asaidimu/events/test.yml?branch=main&label=Tests&style=flat-square)](https://github.com/asaidimu/events/actions?query=workflow%3ATests)\n\n---\n\n### 📚 Table of Contents\n- [Overview & Features](#overview--features)\n- [Installation & Setup](#installation--setup)\n- [Usage Documentation](#usage-documentation)\n  - [Basic Usage](#basic-usage)\n  - [Advanced Configuration](#advanced-configuration)\n  - [Cross-Tab Notifications](#cross-tab-notifications)\n  - [Performance Monitoring](#performance-monitoring)\n  - [Memory Management](#memory-management)\n  - [Custom Error Handling](#custom-error-handling)\n- [API Reference](#api-reference)\n- [Performance Tips](#performance-tips)\n- [Project Architecture](#project-architecture)\n- [Development & Contributing](#development--contributing)\n  - [Development Setup](#development-setup)\n  - [Available Scripts](#available-scripts)\n  - [Testing](#testing)\n  - [Contributing Guidelines](#contributing-guidelines)\n  - [Issue Reporting](#issue-reporting)\n- [Additional Information](#additional-information)\n  - [Troubleshooting](#troubleshooting)\n  - [FAQ](#faq)\n  - [Changelog](#changelog)\n  - [License](#license)\n  - [Acknowledgments](#acknowledgments)\n\n---\n\n## Overview & Features\n\n`@asaidimu/events` provides a robust, highly performant, and fully type-safe event bus solution for modern TypeScript applications. Designed with a minimalist philosophy, it boasts zero external runtime dependencies, ensuring a lean footprint and maximum compatibility. This library is ideal for managing application-wide communication, decoupling components, and facilitating seamless data flow in both single-page applications and multi-tab browser environments.\n\nBeyond basic publish-subscribe patterns, `@asaidimu/events` offers advanced features like asynchronous event processing with configurable batching, built-in performance metrics to monitor event dispatch and listener execution, and robust cross-tab communication leveraging the `BroadcastChannel` API. It empowers developers to build scalable and responsive applications by providing a centralized, efficient, and predictable messaging system.\n\n### Key Features\n- 🎯 **Fully Type-Safe**: Define event names and their corresponding payload types for compile-time safety and autocompletion.\n- ⚡ **High-Performance**: Optimized for fast event emission and subscription, with internal caching of subscribers for efficiency.\n- 🎮 **Async & Batched Processing**: Supports asynchronous event processing with configurable batch size and delay, preventing UI freezes and optimizing resource usage for high-frequency events.\n- 📊 **Built-in Performance Monitoring**: Provides real-time metrics on total events emitted, active subscriptions, event-specific counts, and average emission duration.\n- 🧹 **Automatic Memory Management**: Returns unsubscribe functions for easy cleanup, helping prevent memory leaks in long-running applications or dynamic components.\n- 🌐 **Cross-Tab Notifications**: Seamlessly broadcast events across multiple browser tabs or windows using the `BroadcastChannel` API.\n- 🚨 **Custom Error Handling**: Integrate your own error logging and recovery logic for event processing failures.\n- 0️⃣ **Zero Dependencies**: A truly lightweight solution, ensuring minimal bundle size and no dependency conflicts.\n\n## Installation & Setup\n\n### Prerequisites\n- Node.js (v18 or higher recommended) or Bun (v1.0.0 or higher recommended)\n- TypeScript (v5.0.3 or higher recommended)\n\n### Installation Steps\nInstall `@asaidimu/events` using your preferred package manager:\n\n```bash\n# npm\nnpm install @asaidimu/events\n\n# yarn\nyarn add @asaidimu/events\n\n# pnpm\npnpm add @asaidimu/events\n\n# bun\nbun add @asaidimu/events\n```\n\n### Configuration\nThe `createEventBus` function accepts an optional `options` object to customize its behavior.\n\n```typescript\nimport { createEventBus } from '@asaidimu/events';\n\ninterface AppEvents {\n  'user:loggedIn': { userId: string };\n  'data:fetched': { data: any[] };\n}\n\nconst defaultOptions = {\n  async: false,             // Process events synchronously by default\n  batchSize: 1000,          // Max events to process in one async batch\n  batchDelay: 16,           // Delay in milliseconds before processing a batch\n  errorHandler: (error: EventError) => console.error('EventBus Error:', error), // Default error handler\n  crossTab: false,          // Disable cross-tab communication by default\n  channelName: 'event-bus-channel' // Default channel name for BroadcastChannel\n};\n\n// Example with custom options\nconst bus = createEventBus<AppEvents>({\n  async: true,\n  batchSize: 500,\n  batchDelay: 32,\n  errorHandler: (err) => {\n    console.error('Caught EventBus error:', err.eventName, err.payload, err.message);\n  },\n  crossTab: true,\n  channelName: 'my-custom-app-channel'\n});\n```\n\n### Verification\nTo verify the installation, create a simple TypeScript file (e.g., `test.ts`) and run it:\n\n```typescript\n// test.ts\nimport { createEventBus } from '@asaidimu/events';\n\ninterface MyEvents {\n  'ping': string;\n  'pong': number;\n}\n\nconst bus = createEventBus<MyEvents>();\n\nconst unsubscribePing = bus.subscribe('ping', (message) => {\n  console.log(`Received ping: ${message}`);\n});\n\nbus.emit({ name: 'ping', payload: 'hello' });\nbus.emit({ name: 'pong', payload: 123 }); // This will not trigger a listener as nothing is subscribed to 'pong'\n\n// Expected output: Received ping: hello\n\nunsubscribePing();\nconsole.log('Ping subscription unsubscribed.');\n\n// No output after this:\nbus.emit({ name: 'ping', payload: 'another ping' });\n```\n\nRun with `ts-node` or compile and run:\n```bash\n# Using ts-node\nnpx ts-node test.ts\n\n# Or compile and run\nnpx tsc test.ts\nnode test.js\n```\n\n## Usage Documentation\n\n### Basic Usage\n\nDefine your event map interface, then create a type-safe event bus instance.\n\n```typescript\nimport { createEventBus } from '@asaidimu/events';\n\n// 1. Define your application's event types\ninterface AppEvents {\n  'user:created': { id: string; name: string; email: string };\n  'order:placed': { orderId: string; userId: string; amount: number };\n  'notification:sent': { type: 'email' | 'sms'; recipient: string; message: string };\n}\n\n// 2. Create a type-safe event bus instance\nconst appBus = createEventBus<AppEvents>();\n\n// 3. Subscribe to events\nconst unsubscribeUserCreated = appBus.subscribe('user:created', (user) => {\n  console.log(`[Event: user:created] New user registered: ${user.name} (${user.email})`);\n});\n\nconst unsubscribeOrderPlaced = appBus.subscribe('order:placed', (order) => {\n  console.log(`[Event: order:placed] Order ${order.orderId} placed by user ${order.userId} for $${order.amount}`);\n});\n\n// A single event can have multiple listeners\nappBus.subscribe('user:created', (user) => {\n  console.log(`[Secondary Listener] Sending welcome email to: ${user.email}`);\n  appBus.emit({ name: 'notification:sent', payload: { type: 'email', recipient: user.email, message: 'Welcome!' } });\n});\n\nappBus.subscribe('notification:sent', (notification) => {\n  console.log(`[Event: notification:sent] ${notification.type} to ${notification.recipient}: \"${notification.message}\"`);\n});\n\n// 4. Emit events with their corresponding payloads\nappBus.emit({\n  name: 'user:created',\n  payload: { id: 'usr-123', name: 'Alice Smith', email: 'alice@example.com' }\n});\n\nappBus.emit({\n  name: 'order:placed',\n  payload: { orderId: 'ord-456', userId: 'usr-123', amount: 99.99 }\n});\n\n// Example of incorrect type usage (will cause a TypeScript error)\n// appBus.emit({ name: 'user:created', payload: { userId: 'abc' } }); // Error: 'userId' does not exist in type '{ id: string; name: string; email: string; }'\n\n// 5. Unsubscribe when listeners are no longer needed\n// This is crucial for preventing memory leaks in SPAs or dynamic components.\nunsubscribeUserCreated();\nconsole.log('Unsubscribed from user:created events.');\n\n// This event will not be logged by the first 'user:created' listener, but the secondary one will still fire.\nappBus.emit({\n  name: 'user:created',\n  payload: { id: 'usr-456', name: 'Bob Johnson', email: 'bob@example.com' }\n});\n\n// To clear all subscriptions and reset metrics (useful for testing or full app shutdown)\nappBus.clear();\nconsole.log('Event bus cleared. All subscriptions removed.');\n```\n\n### Advanced Configuration\n\nThe `createEventBus` function accepts an optional configuration object to tailor its behavior.\n\n```typescript\nimport { createEventBus, EventError } from '@asaidimu/events';\n\ninterface HighFrequencyEvents {\n  'mouse:move': { x: number; y: number };\n  'sensor:data': { sensorId: string; value: number };\n}\n\nconst highLoadBus = createEventBus<HighFrequencyEvents>({\n  async: true,              // Process events asynchronously to avoid blocking the main thread\n  batchSize: 500,          // Process up to 500 events in a single batch\n  batchDelay: 50,          // Wait 50ms before processing a batch (if batchSize not met)\n  errorHandler: (error: EventError) => { // Custom error handling for failed listener executions\n    console.error(`[Custom Error Handler] Event '${error.eventName}' failed:`, error.message, 'Payload:', error.payload);\n    // You might send this error to a logging service, e.g., Sentry, Bugsnag\n  },\n  crossTab: true,          // Enable cross-tab notifications (events will be broadcast to other tabs)\n  channelName: 'my-app-events-channel' // Unique channel name for BroadcastChannel\n});\n\n// Example usage with high frequency events\nhighLoadBus.subscribe('mouse:move', (coords) => {\n  // console.log(`Mouse moved to (${coords.x}, ${coords.y})`); // Too noisy for console\n});\n\nfor (let i = 0; i < 10000; i++) {\n  highLoadBus.emit({\n    name: 'mouse:move',\n    payload: { x: Math.random() * 1000, y: Math.random() * 800 }\n  });\n}\n\n// Emitting an event that might cause an error in a listener\nhighLoadBus.subscribe('sensor:data', (data) => {\n  if (data.value < 0) {\n    throw new Error('Negative sensor value detected!');\n  }\n  console.log(`Sensor ${data.sensorId} reading: ${data.value}`);\n});\n\nhighLoadBus.emit({ name: 'sensor:data', payload: { sensorId: 'temp-001', value: 25.5 } });\nhighLoadBus.emit({ name: 'sensor:data', payload: { sensorId: 'temp-002', value: -5.0 } }); // This will trigger the errorHandler\n```\n\n### Cross-Tab Notifications\n\nWhen `crossTab` is enabled, events emitted in one browser tab are automatically broadcasted to other tabs with the same event bus configuration (`channelName`). This feature leverages the browser's `BroadcastChannel` API.\n\n```typescript\n// --- In Browser Tab 1 (e.g., index.html) ---\nimport { createEventBus } from '@asaidimu/events';\n\ninterface ChatEvents {\n  'chat:message': { sender: string; text: string; timestamp: number };\n  'user:typing': { username: string };\n}\n\n// Create a bus instance with crossTab enabled and a unique channel name\nconst chatBus = createEventBus<ChatEvents>({\n  crossTab: true,\n  channelName: 'my-chat-app-channel'\n});\n\n// Tab 1 subscribes to messages\nchatBus.subscribe('chat:message', (msg) => {\n  console.log(`[Tab 1] Received message from ${msg.sender}: \"${msg.text}\" at ${new Date(msg.timestamp).toLocaleTimeString()}`);\n});\n\nchatBus.subscribe('user:typing', (data) => {\n  console.log(`[Tab 1] ${data.username} is typing...`);\n});\n\nconsole.log('[Tab 1] Chat bus initialized, waiting for messages...');\n\n// Tab 1 emits a message after a delay\nsetTimeout(() => {\n  chatBus.emit({\n    name: 'chat:message',\n    payload: { sender: 'Alice', text: 'Hello everyone!', timestamp: Date.now() }\n  });\n  console.log('[Tab 1] Emitted: Hello everyone!');\n}, 2000);\n\n// --- In Browser Tab 2 (e.g., another_page.html) ---\n// (Imagine this code runs in a separate browser tab/window)\n\nimport { createEventBus } from '@asaidimu/events';\n\ninterface ChatEvents { // Must use the same event map interface\n  'chat:message': { sender: string; text: string; timestamp: number };\n  'user:typing': { username: string };\n}\n\nconst chatBus2 = createEventBus<ChatEvents>({\n  crossTab: true,\n  channelName: 'my-chat-app-channel' // Crucially, use the same channel name\n});\n\n// Tab 2 also subscribes to messages\nchatBus2.subscribe('chat:message', (msg) => {\n  console.log(`[Tab 2] Received message from ${msg.sender}: \"${msg.text}\" at ${new Date(msg.timestamp).toLocaleTimeString()}`);\n});\n\nchatBus2.subscribe('user:typing', (data) => {\n  console.log(`[Tab 2] ${data.username} is typing...`);\n});\n\nconsole.log('[Tab 2] Chat bus initialized, waiting for messages...');\n\n// Tab 2 emits a typing event\nsetTimeout(() => {\n  chatBus2.emit({\n    name: 'user:typing',\n    payload: { username: 'Bob' }\n  });\n  console.log('[Tab 2] Emitted: Bob is typing...');\n}, 4000);\n\n// Expected Output (in both tabs, potentially with slight timing differences):\n// [Tab 1] Chat bus initialized, waiting for messages...\n// [Tab 2] Chat bus initialized, waiting for messages...\n// ... (2 seconds later) ...\n// [Tab 1] Emitted: Hello everyone!\n// [Tab 1] Received message from Alice: \"Hello everyone!\" at [time]\n// [Tab 2] Received message from Alice: \"Hello everyone!\" at [time]\n// ... (2 seconds later) ...\n// [Tab 2] Emitted: Bob is typing...\n// [Tab 1] Bob is typing...\n// [Tab 2] Bob is typing...\n```\n**Note**: If `BroadcastChannel` is not supported by the browser environment, a warning will be logged, and cross-tab functionality will be disabled gracefully.\n\n### Performance Monitoring\n\nAccess real-time metrics about your event bus's activity and performance using the `getMetrics()` method.\n\n```typescript\nimport { createEventBus } from '@asaidimu/events';\n\ninterface MetricsEvents {\n  'data:processed': { count: number };\n  'api:request': { endpoint: string };\n}\n\nconst perfBus = createEventBus<MetricsEvents>();\n\nperfBus.subscribe('data:processed', (data) => {\n  // Simulate some work\n  for (let i = 0; i < 10000; i++) Math.sqrt(i);\n  console.log(`Processed ${data.count} items.`);\n});\n\nperfBus.subscribe('api:request', (req) => {\n  console.log(`API request to ${req.endpoint}.`);\n});\n\nperfBus.emit({ name: 'data:processed', payload: { count: 10 } });\nperfBus.emit({ name: 'api:request', payload: { endpoint: '/users' } });\nperfBus.emit({ name: 'data:processed', payload: { count: 25 } });\n\n// After some operations, retrieve metrics\nconst metrics = perfBus.getMetrics();\n\nconsole.log('\\n--- Event Bus Metrics ---');\nconsole.log(`Total Events Emitted: ${metrics.totalEvents}`);         // Total count of all emitted events\nconsole.log(`Active Subscriptions: ${metrics.activeSubscriptions}`); // Total number of currently active subscriptions\nconsole.log('Event Emission Counts:');\nmetrics.eventCounts.forEach((count, eventName) => {\n  console.log(`  - ${eventName}: ${count} times`); // How many times each specific event was emitted\n});\nconsole.log(`Average Emit Duration: ${metrics.averageEmitDuration.toFixed(2)} ms`); // Average time taken to emit an event and execute its listeners\nconsole.log('-------------------------');\n\n/* Expected example output:\n--- Event Bus Metrics ---\nTotal Events Emitted: 3\nActive Subscriptions: 2\nEvent Emission Counts:\n  - data:processed: 2 times\n  - api:request: 1 times\nAverage Emit Duration: XX.XX ms (will vary based on system performance)\n-------------------------\n*/\n```\n\n### Memory Management\n\nAlways remember to unsubscribe from events when the listener is no longer needed (e.g., when a component unmounts in a UI framework). The `subscribe` method returns an `unsubscribe` function for this purpose.\n\n```typescript\nimport { createEventBus } from '@asaidimu/events';\n\ninterface ComponentEvents {\n  'component:mounted': void;\n  'component:unmounted': void;\n  'data:updated': { value: number };\n}\n\nconst componentBus = createEventBus<ComponentEvents>();\n\nclass MyComponent {\n  private name: string;\n  private unsubscribes: Array<() => void> = [];\n\n  constructor(name: string) {\n    this.name = name;\n  }\n\n  mount() {\n    console.log(`[${this.name}] Component mounted.`);\n    // Store the unsubscribe function\n    this.unsubscribes.push(\n      componentBus.subscribe('data:updated', this.handleDataUpdate)\n    );\n    // Subscribe to a general event, but ensure it's cleaned up\n    this.unsubscribes.push(\n      componentBus.subscribe('component:unmounted', this.cleanupInternalState)\n    );\n    componentBus.emit({ name: 'component:mounted', payload: undefined });\n  }\n\n  private handleDataUpdate = (data: { value: number }) => {\n    console.log(`[${this.name}] Data updated: ${data.value}`);\n  };\n\n  private cleanupInternalState = () => {\n    console.log(`[${this.name}] Internal state cleaned up.`);\n    // Perform any other cleanup specific to this component instance\n  }\n\n  unmount() {\n    console.log(`[${this.name}] Component unmounting. Cleaning up subscriptions.`);\n    // Call all stored unsubscribe functions\n    this.unsubscribes.forEach(unsub => unsub());\n    this.unsubscribes = []; // Clear the array\n    componentBus.emit({ name: 'component:unmounted', payload: undefined });\n  }\n}\n\nconst componentA = new MyComponent('ComponentA');\nconst componentB = new MyComponent('ComponentB');\n\ncomponentA.mount();\ncomponentB.mount();\n\ncomponentBus.emit({ name: 'data:updated', payload: { value: 100 } }); // Both components will receive\n\ncomponentA.unmount(); // ComponentA's subscriptions are removed\n\ncomponentBus.emit({ name: 'data:updated', payload: { value: 200 } }); // Only ComponentB will receive\n\ncomponentB.unmount(); // ComponentB's subscriptions are removed\n\n// No output after this:\ncomponentBus.emit({ name: 'data:updated', payload: { value: 300 } });\n```\n\n### Custom Error Handling\n\nYou can provide a custom `errorHandler` function in the `createEventBus` options. This function will be called if any subscriber callback throws an uncaught error during synchronous event emission.\n\n```typescript\nimport { createEventBus, EventError } from '@asaidimu/events';\n\ninterface ErrorProneEvents {\n  'process:item': { itemId: string; data: any };\n}\n\nconst errorBus = createEventBus<ErrorProneEvents>({\n  errorHandler: (error: EventError) => {\n    console.error('--- Custom Error Handler ---');\n    console.error(`Error processing event: \"${error.eventName}\"`);\n    console.error(`Payload was:`, JSON.stringify(error.payload, null, 2));\n    console.error(`Original error: ${error.message}`);\n    if (error.stack) {\n      console.error('Stack trace:', error.stack);\n    }\n    console.error('--------------------------');\n    // Here, you would typically log to an external service or display a user notification\n  }\n});\n\nerrorBus.subscribe('process:item', (item) => {\n  if (!item.data || item.data.value === undefined) {\n    throw new Error(`Invalid data for item ${item.itemId}: 'value' property missing.`);\n  }\n  if (item.data.value < 0) {\n    throw new Error(`Negative value not allowed for item ${item.itemId}.`);\n  }\n  console.log(`Successfully processed item ${item.itemId} with value ${item.data.value}`);\n});\n\n// This will be processed successfully\nerrorBus.emit({ name: 'process:item', payload: { itemId: 'A1', data: { value: 42 } } });\n\n// This will trigger the custom error handler due to missing 'value'\nerrorBus.emit({ name: 'process:item', payload: { itemId: 'B2', data: { status: 'incomplete' } } });\n\n// This will trigger the custom error handler due to negative value\nerrorBus.emit({ name: 'process:item', payload: { itemId: 'C3', data: { value: -10 } } });\n\nconsole.log('Events emitted. Check console for custom error handling output.');\n```\n\n## API Reference\n\n### `createEventBus<TEventMap extends Record<string, any>>(options?: EventBusOptions)`\n\nA factory function that creates and returns a new event bus instance.\nThe `TEventMap` generic type argument is crucial for providing type-safety to your events.\n\n#### `EventBusOptions` (Type Definition)\n```typescript\ninterface EventBusOptions {\n  /**\n   * Whether events should be processed asynchronously in batches.\n   * If false, events are processed synchronously.\n   * @default false\n   */\n  async?: boolean;\n  /**\n   * The maximum number of events to batch together before processing.\n   * Only applicable when `async` is true.\n   * @default 1000\n   */\n  batchSize?: number;\n  /**\n   * The delay in milliseconds before processing a batch of events.\n   * Events are also processed immediately if `batchSize` is reached.\n   * Only applicable when `async` is true.\n   * @default 16\n   */\n  batchDelay?: number;\n  /**\n   * A custom error handler function to be called if an error occurs\n   * within a subscriber callback during event emission.\n   * @default (error) => console.error('EventBus Error:', error)\n   */\n  errorHandler?: (error: EventError) => void;\n  /**\n   * Whether to enable cross-tab communication using `BroadcastChannel`.\n   * Events emitted will be broadcast to other browser tabs/windows\n   * initialized with the same `channelName`.\n   * @default false\n   */\n  crossTab?: boolean;\n  /**\n   * A unique name for the `BroadcastChannel`.\n   * Essential for isolating event buses across different parts of an application\n   * or different applications running on the same domain.\n   * Only applicable when `crossTab` is true.\n   * @default 'event-bus-channel'\n   */\n  channelName?: string;\n}\n```\n\n#### Returns\nAn `EventBus<TEventMap>` instance with the following methods:\n\n-   `subscribe<TEventName extends keyof TEventMap>(eventName: TEventName, callback: (payload: TEventMap[TEventName]) => void): () => void`\n    *   Subscribes a `callback` function to a specific `eventName`.\n    *   The `callback` receives the event's payload, correctly typed according to `TEventMap`.\n    *   **Returns**: An `unsubscribe` function. Call this function to remove the subscription.\n\n-   `emit<TEventName extends keyof TEventMap>(event: { name: TEventName; payload: TEventMap[TEventName] }): void`\n    *   Emits an event with the specified `name` and `payload`.\n    *   The `payload` must match the type defined for `TEventName` in `TEventMap`.\n    *   All subscribed listeners for that event will be invoked (either synchronously or asynchronously based on configuration).\n\n-   `getMetrics(): EventMetrics`\n    *   Retrieves various performance and usage metrics of the event bus.\n    *   **Returns**: An `EventMetrics` object.\n\n-   `clear(): void`\n    *   Removes all active subscriptions from the event bus.\n    *   Resets all internal metrics (`totalEvents`, `eventCounts`, etc.) to zero.\n    *   Closes the `BroadcastChannel` if `crossTab` was enabled.\n\n#### `EventMetrics` (Type Definition)\n```typescript\ninterface EventMetrics {\n  /** Total number of events emitted since initialization or last clear. */\n  totalEvents: number;\n  /** Number of active subscriptions. */\n  activeSubscriptions: number;\n  /** Map of event names to their emission counts. */\n  eventCounts: Map<string, number>;\n  /** Average duration of event emission in milliseconds. */\n  averageEmitDuration: number;\n}\n```\n\n#### `EventError` (Type Definition)\n```typescript\ninterface EventError extends Error {\n  /** Optional name of the event that caused the error. */\n  eventName?: string;\n  /** Optional payload that caused the error. */\n  payload?: unknown;\n}\n```\n\n## Performance Tips\n\n1.  **Clean up subscriptions**: Always call the `unsubscribe` function returned by `subscribe` when a listener is no longer needed. This prevents memory leaks, especially in single-page applications with dynamic components.\n2.  **Batch processing**: For scenarios involving frequent event emissions (e.g., mouse movements, sensor data), enable `async: true` and adjust `batchSize` and `batchDelay` to optimize performance and prevent blocking the main thread.\n3.  **Error handling**: Provide a custom `errorHandler` in production environments to gracefully handle exceptions in subscriber callbacks and integrate with your application's logging or monitoring systems.\n4.  **Monitor performance**: Regularly use `getMetrics()` to understand the event bus's performance characteristics in your application, identify bottlenecks, or detect unexpected behavior.\n5.  **Cross-tab uniqueness**: If you have multiple distinct event buses within the same application or different applications on the same domain, ensure they use unique `channelName` options to prevent unintended cross-communication.\n6.  **Payload Size**: While the event bus itself is fast, large or complex payloads can still impact performance due to serialization/deserialization (especially with `BroadcastChannel`) or heavy processing by listeners. Keep payloads concise and relevant.\n\n## Project Architecture\n\nThe library is structured for clarity, maintainability, and optimal performance.\n\n```\n.\n├── dist/                # Compiled output (JavaScript, Declaration Files, and assets for NPM)\n├── src/\n│   ├── index.ts         # Core EventBus implementation (createEventBus function, logic)\n│   └── types.ts         # TypeScript type definitions for EventBus interfaces and options\n├── index.ts             # Main entry point for the package (re-exports from src/)\n├── package.json         # Project metadata, dependencies, scripts, and build configurations\n├── tsconfig.json        # TypeScript compiler configuration\n├── vitest.config.ts     # Vitest testing framework configuration (including browser tests)\n├── CHANGELOG.md         # Automatically generated project changelog\n├── LICENSE.md           # MIT License details\n└── README.md            # This documentation file\n```\n\n### Core Components\n-   **`createEventBus` Function**: The central factory function responsible for instantiating the event bus. It encapsulates the core logic, including subscriber management, event queuing, and metric tracking.\n-   **`EventBus<TEventMap>` Interface**: Defines the public API contract for the event bus instance, ensuring strict type-checking for `subscribe`, `emit`, `getMetrics`, and `clear` methods.\n-   **`EventMetrics` Interface**: Specifies the structure of performance data returned by `getMetrics`, allowing consistent monitoring.\n-   **`subscribers` Map**: An internal `Map` that stores `Set`s of callback functions, efficiently mapping event names to their registered listeners.\n-   **`eventQueue` Array**: Used when `async` processing is enabled, this array temporarily holds events before they are processed in batches.\n-   **`BroadcastChannel` API**: Leveraged for enabling communication between different browser tabs or windows, allowing events to propagate across the user's open sessions.\n\n### Data Flow\n1.  **Initialization**: `createEventBus` is called, setting up internal maps, queues, and (optionally) a `BroadcastChannel`.\n2.  **Subscription**: When `subscribe(eventName, callback)` is called, the `callback` is added to a `Set` associated with `eventName` in the `subscribers` map. An internal cached array of callbacks is updated for quick iteration.\n3.  **Emission (Synchronous)**: When `emit({ name, payload })` is called with `async: false` (default), the associated callbacks from the `cachedArrays` are immediately invoked. Metrics are updated. If `crossTab` is enabled, the event is also posted to `BroadcastChannel`.\n4.  **Emission (Asynchronous)**: When `emit({ name, payload })` is called with `async: true`, the event is pushed into `eventQueue`. A debounced function (`debouncedProcess`) schedules a batch processing. If `batchSize` is met, `processBatch` is called immediately. The event is also immediately posted to `BroadcastChannel` if `crossTab` is enabled.\n5.  **Batch Processing**: `processBatch` takes events from `eventQueue`, iterates through them, and invokes their respective listeners. Metrics are updated after each event.\n6.  **Cross-Tab Reception**: If `crossTab` is enabled, any event posted to the `BroadcastChannel` by another tab is received, and its listeners are invoked on the receiving bus instance.\n7.  **Unsubscription**: Calling the function returned by `subscribe` removes the specific callback from its `Set`, and the `cachedArrays` are updated accordingly. If no callbacks remain for an event, its entry is removed from the `subscribers` map.\n8.  **Clearing**: `clear()` empties all subscriber maps, resets metrics, and closes the `BroadcastChannel`.\n\n## Development & Contributing\n\nContributions are highly welcome! Whether it's bug reports, feature requests, or code contributions, your help makes this project better.\n\n### Development Setup\nTo set up the project for local development:\n\n1.  **Clone the repository:**\n    ```bash\n    git clone https://github.com/asaidimu/events.git\n    cd events\n    ```\n2.  **Install dependencies:**\n    This project uses `bun` as its package manager, but `npm`, `yarn`, or `pnpm` should also work.\n    ```bash\n    bun install\n    # or\n    # npm install\n    # yarn install\n    # pnpm install\n    ```\n3.  **Build the project:**\n    ```bash\n    bun run build\n    ```\n\n### Available Scripts\nThe `package.json` defines several scripts for common development tasks:\n\n-   `bun run ci`: Installs project dependencies.\n-   `bun run clean`: Removes the `dist/` build directory.\n-   `bun run prebuild`: Executes `clean` and a custom script (`.sync-package.ts`) before building.\n-   `bun run build`: Compiles TypeScript source files into CommonJS and ES modules, and generates TypeScript declaration files (`.d.ts`). Uses `tsup`.\n-   `bun run postbuild`: Copies `README.md`, `LICENSE.md`, and `dist.package.json` into the `dist/` folder, preparing for publishing.\n-   `bun run test`: Runs unit and integration tests using `vitest`.\n-   `bun run test:ci`: Runs tests in a CI environment (headless).\n-   `bun run test:browser`: Runs tests specifically in a browser environment using Playwright.\n\n### Testing\nTests are written with `Vitest` and can be run from the command line. The project also supports browser testing via Playwright.\n\nTo run all tests:\n```bash\nbun test\n```\nTo run tests in watch mode during development:\n```bash\nbun test --watch\n```\nTo run tests specifically in a browser environment (requires Playwright setup):\n```bash\nbun test:browser\n```\n\n### Contributing Guidelines\nPlease follow these guidelines when contributing:\n1.  **Fork** the repository and **clone** it locally.\n2.  Create a new **branch** for your feature or bug fix: `git checkout -b feature/your-feature-name` or `fix/issue-description`.\n3.  Make your changes.\n4.  Ensure your code adheres to the project's **coding standards** (linting and formatting are enforced).\n5.  Write **tests** for your changes to ensure functionality and prevent regressions.\n6.  Ensure all existing tests pass.\n7.  Write a **clear and concise commit message** following [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specifications (e.g., `feat: add new feature`, `fix: resolve bug`). This helps with automated changelog generation and semantic versioning.\n8.  **Push** your branch to your fork.\n9.  Open a **Pull Request** to the `main` branch of the original repository. Provide a detailed description of your changes.\n\n### Issue Reporting\nIf you find a bug or have a feature request, please open an issue on the [GitHub Issues page](https://github.com/asaidimu/events/issues).\nWhen reporting a bug, please include:\n-   A clear and concise description of the issue.\n-   Steps to reproduce the behavior.\n-   Expected behavior.\n-   Actual behavior.\n-   Any relevant code snippets or screenshots.\n-   Your environment details (Node.js version, browser, OS).\n\n## Additional Information\n\n### Troubleshooting\n\n-   **`BroadcastChannel is not supported` warning**: If you see this warning, it means your current JavaScript environment (e.g., an older browser, Node.js without a polyfill, or certain test environments like JSDOM without specific configurations) does not support the `BroadcastChannel` API. Cross-tab communication will automatically be disabled in this case, but the event bus will function normally within the single environment.\n-   **Events not firing in async mode**: Ensure your application's event loop has time to process batches. If you're emitting very few events or your `batchDelay` is too high, you might not see immediate results. Check your `batchSize` and `batchDelay` configurations.\n-   **Memory leaks**: If your application experiences increasing memory usage, double-check that you are calling the `unsubscribe()` function for every `subscribe()` call when listeners are no longer needed, especially in dynamic UI components.\n-   **TypeScript errors**: If you encounter TypeScript errors related to event names or payloads, verify that your `TEventMap` interface correctly defines all expected event names and their corresponding payload types. The type-safety is strict by design.\n\n### FAQ\n\n**Q: What is an event bus?**\nA: An event bus is a pattern that enables different parts of your application to communicate with each other without direct dependencies. It allows components to \"publish\" (emit) events and other components to \"subscribe\" (listen) to those events, promoting a decoupled and modular architecture.\n\n**Q: Why is `@asaidimu/events` type-safe?**\nA: By defining an `EventMap` interface, TypeScript can enforce that event names used in `emit` and `subscribe` calls are valid, and that the `payload` matches the expected type for that specific event. This eliminates common runtime errors related to misspelled event names or incorrect data structures, improving development experience and code reliability.\n\n**Q: Why \"zero dependencies\"?**\nA: \"Zero dependencies\" means the published library (`dist/package.json`) does not require any other NPM packages to function at runtime. This keeps the bundle size minimal, reduces potential dependency conflicts, and simplifies maintenance. Development dependencies (like `typescript`, `tsup`, `vitest`) are only needed for building and testing the library itself.\n\n**Q: How does cross-tab communication work?**\nA: `@asaidimu/events` uses the browser's native `BroadcastChannel` API. When `crossTab: true` is set, events emitted by one instance of the event bus in a browser tab are automatically sent through a shared channel to all other instances of the event bus initialized with the same `channelName` in other tabs or windows of the same origin.\n\n**Q: Can I use this in Node.js?**\nA: Yes, the core event bus functionality (subscribe, emit, metrics, clear) works perfectly in Node.js environments. The `crossTab` feature, however, relies on `BroadcastChannel`, which is a browser API. While some Node.js environments might have polyfills, it's primarily designed for browser-based cross-tab communication.\n\n### Changelog\nFor a detailed history of changes, new features, and bug fixes, please refer to the [CHANGELOG.md](https://github.com/asaidimu/events/blob/main/CHANGELOG.md) file.\n\n### License\nThis project is licensed under the MIT License. See the [LICENSE.md](https://github.com/asaidimu/events/blob/main/LICENSE.md) file for full details.\n\n### Acknowledgments\nThis project is developed and maintained by Saidimu.\nSpecial thanks to the open-source community for the inspiration and tools that make such libraries possible.\nBuilt with ❤️ and TypeScript.\n","readmeFilename":"README.md"}