{"id":22595149,"url":"https://github.com/constantiner/zohar","last_synced_at":"2025-04-11T00:21:01.030Z","repository":{"id":255223138,"uuid":"829457535","full_name":"Constantiner/zohar","owner":"Constantiner","description":"A functional type-safe event emitter library with zero dependencies","archived":false,"fork":false,"pushed_at":"2025-03-28T21:15:36.000Z","size":3417,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-09T21:52:14.349Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Constantiner.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-16T13:16:02.000Z","updated_at":"2025-04-04T10:10:31.000Z","dependencies_parsed_at":"2024-08-28T18:38:34.895Z","dependency_job_id":null,"html_url":"https://github.com/Constantiner/zohar","commit_stats":null,"previous_names":["constantiner/zohar"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fzohar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fzohar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fzohar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fzohar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Constantiner","download_url":"https://codeload.github.com/Constantiner/zohar/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119402,"owners_count":21050754,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-08T10:09:10.017Z","updated_at":"2025-04-11T00:21:01.018Z","avatar_url":"https://github.com/Constantiner.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zohar\u003c!-- omit in toc --\u003e\n\n[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) [![NPM Version](https://img.shields.io/npm/v/zohar.svg)](https://www.npmjs.com/package/zohar) [![codecov](https://codecov.io/github/Constantiner/zohar/graph/badge.svg?token=3Z4EGIP41Y)](https://codecov.io/github/Constantiner/zohar)\n\nA functional type-safe event emitter library with zero dependencies.\n\n![Zohar Logo generated by ChatGPT 4o](zohar.jpg)\n\n**Zohar** draws its name from both the Xeno series, where it represents a powerful and mysterious source of energy facilitating connections between different realms, and from Kabbalistic tradition, where it symbolizes \"Splendor\" or \"Radiance,\" offering deep insight into the hidden workings of the universe. Just as the **Zohar** illuminates and connects various aspects of reality, the `Zohar` library provides a versatile, type-safe mechanism for transmitting events across different parts of your application, ensuring seamless communication and coordination.\n\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage)\n\t- [1. Defining Events](#1-defining-events)\n\t\t- [Generic Events with Any Data Type](#generic-events-with-any-data-type)\n\t\t- [Different Events with the Same Data Type](#different-events-with-the-same-data-type)\n\t\t- [Different Events with Different Data Types](#different-events-with-different-data-types)\n\t- [2. Subscribing to Events](#2-subscribing-to-events)\n\t\t- [What is a Predicate?](#what-is-a-predicate)\n\t\t- [Using a Predicate](#using-a-predicate)\n\t- [3. Unsubscribing from Events](#3-unsubscribing-from-events)\n\t- [4. Subscribing Once to an Event](#4-subscribing-once-to-an-event)\n\t\t- [Using `once`](#using-once)\n\t- [5. Waiting for an Event with a Promise](#5-waiting-for-an-event-with-a-promise)\n\t\t- [Using `awaited`](#using-awaited)\n\t- [Commonalities Between `once` and `awaited`](#commonalities-between-once-and-awaited)\n\t- [6. Creating Specialized Subscription Functions with `subscriber`](#6-creating-specialized-subscription-functions-with-subscriber)\n\t\t- [Using `subscriber`](#using-subscriber)\n\t- [7. Creating Specialized Emit Functions with `emitter`](#7-creating-specialized-emit-functions-with-emitter)\n\t\t- [Using `emitter`](#using-emitter)\n\t- [Benefits of Using `subscriber` and `emitter`](#benefits-of-using-subscriber-and-emitter)\n\t- [Common Use Cases](#common-use-cases)\n- [API Reference](#api-reference)\n- [Comparison: `zohar` vs. Node.js EventEmitter API](#comparison-zohar-vs-nodejs-eventemitter-api)\n\t- [Overview](#overview)\n\t- [Usage Comparison](#usage-comparison)\n\t\t- [Example: Using `zohar`](#example-using-zohar)\n\t\t- [Example: Using Node.js EventEmitter](#example-using-nodejs-eventemitter)\n\t- [Type Safety and Unsubscription Perspective](#type-safety-and-unsubscription-perspective)\n\t- [Pros and Cons](#pros-and-cons)\n\t\t- [`zohar`](#zohar-1)\n\t\t- [Node.js EventEmitter](#nodejs-eventemitter)\n\t- [Conclusion](#conclusion)\n- [Usage Examples](#usage-examples)\n\t- [Chat Module Usage](#chat-module-usage)\n\t\t- [Chat Module Implementation](#chat-module-implementation)\n\t\t- [Subscribing to Chat Events](#subscribing-to-chat-events)\n\t\t\t- [Available Events](#available-events)\n\t\t\t- [Example: Subscribing to Events](#example-subscribing-to-events)\n\t\t- [Unsubscribing from Chat Events](#unsubscribing-from-chat-events)\n\t\t\t- [Example: Unsubscribing from Events](#example-unsubscribing-from-events)\n\t\t- [Internal Logic](#internal-logic)\n\t\t- [Chat Module Summary](#chat-module-summary)\n\t- [One-Time Login Event Handling with `awaited` and `once`](#one-time-login-event-handling-with-awaited-and-once)\n\t\t- [Login Module Implementation](#login-module-implementation)\n\t\t- [Consumer Usage Example with `awaited`](#consumer-usage-example-with-awaited)\n\t\t- [Consumer Usage Example with `once`](#consumer-usage-example-with-once)\n\t\t- [Explanation](#explanation)\n\t\t- [Login Module Summary](#login-module-summary)\n- [License](#license)\n\n## Features\n\n- **Type-safe**: Ensures that emitted events match the expected data types.\n- **Zero dependencies**: Lightweight and easy to integrate.\n- **Flexible event definitions**: Supports different events with different or the same data types.\n- **Intuitive API**: Easy-to-use methods for subscribing, emitting, and unsubscribing from events.\n\n## Installation\n\nInstall the library using npm or yarn:\n\n```bash\nnpm install zohar\n```\n\nor\n\n```bash\nyarn add zohar\n```\n\n## Usage\n\n### 1. Defining Events\n\n#### Generic Events with Any Data Type\n\nFor scenarios where events have various data types, you can define a generic event description:\n\n```typescript\nimport { EventDescription, createEventEmitter } from 'zohar';\n\n// Define a generic event description\ntype GenericEvents = EventDescription\u003cstring, any\u003e;\n\n// Create an event emitter\nconst [subscribe, emit, unsubscribeAll] = createEventEmitter\u003cGenericEvents\u003e();\n\n// Subscribe to any event with string-based event names\nsubscribe('someEvent', (eventName, data) =\u003e {\n    console.log(`Received ${eventName} with data:`, data);\n});\n\n// Emit the event\nemit('someEvent', { message: 'Hello World!' });\n```\n\n#### Different Events with the Same Data Type\n\nIn cases where different events share the same data structure, you can define the event description accordingly:\n\n```typescript\nimport { EventDescription, createEventEmitter } from 'zohar';\n\n// Define event descriptions for user events\ntype UserEvents = EventDescription\u003c'userLogin' | 'userLogout', { userId: string; timestamp: Date }\u003e;\n\n// Create an event emitter\nconst [subscribe, emit, unsubscribeAll] = createEventEmitter\u003cUserEvents\u003e();\n\n// Subscribe to 'userLogin' event\nsubscribe('userLogin', (eventName, data) =\u003e {\n    console.log(`User ${data.userId} logged in at ${data.timestamp}`);\n});\n\n// Subscribe to 'userLogout' event\nsubscribe('userLogout', (eventName, data) =\u003e {\n    console.log(`User ${data.userId} logged out at ${data.timestamp}`);\n});\n\n// Emit the events\nemit('userLogin', { userId: 'user123', timestamp: new Date() });\nemit('userLogout', { userId: 'user123', timestamp: new Date() });\n```\n\n#### Different Events with Different Data Types\n\nIf you have events with different data structures, you can combine event descriptions:\n\n```typescript\nimport { EventDescription, createEventEmitter } from 'zohar';\n\n// Define event descriptions with varying data types\ntype UserEvents = EventDescription\u003c'userLogin' | 'userLogout', { userId: string; timestamp: Date }\u003e\n    \u0026 EventDescription\u003c'userRegister', { email: string; password: string; timestamp: Date }\u003e;\n\n// Create an event emitter\nconst [subscribe, emit, unsubscribeAll] = createEventEmitter\u003cUserEvents\u003e();\n\n// Subscribe to 'userLogin' event\nsubscribe('userLogin', (eventName, data) =\u003e {\n    console.log(`User ${data.userId} logged in at ${data.timestamp}`);\n});\n\n// Subscribe to 'userLogout' event\nsubscribe('userLogout', (eventName, data) =\u003e {\n    console.log(`User ${data.userId} logged out at ${data.timestamp}`);\n});\n\n// Subscribe to 'userRegister' event\nsubscribe('userRegister', (eventName, data) =\u003e {\n    console.log(`User registered with email ${data.email} at ${data.timestamp}`);\n});\n\n// Emit the events\nemit('userLogin', { userId: 'user123', timestamp: new Date() });\nemit('userLogout', { userId: 'user123', timestamp: new Date() });\nemit('userRegister', { email: 'user@example.com', password: 'securePassword', timestamp: new Date() });\n```\n\n### 2. Subscribing to Events\n\nYou can subscribe to events with or without predicates.\n\n#### What is a Predicate?\n\nA **predicate** is a function that returns a boolean value (`true` or `false`) based on the evaluation of some condition on the event data. When you subscribe to an event with a predicate, the event will only trigger the listener if the predicate function returns `true` for the given event data.\n\nThis feature allows you to filter events, ensuring that only specific events that meet certain criteria trigger the listener.\n\n#### Using a Predicate\n\nFor example, you might want to listen to a `userLogin` event only if the `userId` matches a specific value:\n\n```typescript\nimport { EventDescription, createEventEmitter } from 'zohar';\n\n// Define event descriptions for user events\ntype UserEvents = EventDescription\u003c'userLogin' | 'userLogout', { userId: string; timestamp: Date }\u003e;\n\n// Create an event emitter\nconst [subscribe, emit, unsubscribeAll] = createEventEmitter\u003cUserEvents\u003e();\n\n// Predicate function to filter for a specific user ID\nconst predicate = (data: { userId: string }) =\u003e data.userId === 'user123';\n\n// Subscribe to the 'userLogin' event with the predicate\nsubscribe('userLogin', (eventName, data) =\u003e {\n    console.log(`Specific user ${data.userId} logged in at ${data.timestamp}`);\n}, predicate);\n\n// Emit events\nemit('userLogin', { userId: 'user123', timestamp: new Date() }); // This will trigger the listener\nemit('userLogin', { userId: 'anotherUser', timestamp: new Date() }); // This will not trigger the listener\n```\n\n### 3. Unsubscribing from Events\n\nYou can unsubscribe individual listeners or all listeners for a specific event or all events:\n\n```typescript\n// Unsubscribe from a specific listener\nconst unsubscribe = subscribe('userLogin', (eventName, data) =\u003e {\n    console.log(`User ${data.userId} logged in`);\n});\nunsubscribe(); // This removes the listener\n\n// Unsubscribe all listeners for 'userLogin'\nunsubscribeAll('userLogin');\n\n// Unsubscribe all listeners for all events\nunsubscribeAll();\n```\n\n### 4. Subscribing Once to an Event\n\nThe `once` utility function allows you to subscribe to an event and automatically unsubscribe after the event is triggered for the first time. This is useful when you only need to handle an event a single time.\n\n#### Using `once`\n\n```typescript\nimport { EventDescription, createEventEmitter, once } from 'zohar';\n\n// Define event descriptions for user events\ntype UserEvents = EventDescription\u003c'userLogin', { userId: string; timestamp: Date }\u003e;\n\n// Create an event emitter\nconst [subscribe, emit] = createEventEmitter\u003cUserEvents\u003e();\n\n// Subscribe to the 'userLogin' event only once\nconst onceSubscribe = once(subscribe);\n\nonceSubscribe('userLogin', (eventName, data) =\u003e {\n    console.log(`User ${data.userId} logged in at ${data.timestamp}`);\n});\n\n// Emit the event twice\nemit('userLogin', { userId: 'user123', timestamp: new Date() });\nemit('userLogin', { userId: 'user123', timestamp: new Date() }); // This will not trigger the listener\n```\n\n### 5. Waiting for an Event with a Promise\n\nThe `awaited` utility function allows you to subscribe to an event and return a promise that resolves when the event is triggered. This is useful for handling asynchronous events in a promise-based workflow.\n\n#### Using `awaited`\n\n```typescript\nimport { EventDescription, createEventEmitter, awaited } from 'zohar';\n\n// Define event descriptions for user events\ntype UserEvents = EventDescription\u003c'userLogin', { userId: string; timestamp: Date }\u003e;\n\n// Create an event emitter\nconst [subscribe, emit] = createEventEmitter\u003cUserEvents\u003e();\n\n// Subscribe to the 'userLogin' event and return a promise\nconst awaitedSubscribe = awaited(subscribe);\n\nawaitedSubscribe('userLogin').then((data) =\u003e {\n    console.log(`User ${data.userId} logged in at ${data.timestamp}`);\n});\n\n// Emit the event\nemit('userLogin', { userId: 'user123', timestamp: new Date() });\n```\n\n### Commonalities Between `once` and `awaited`\n\nBoth `once` and `awaited` utilities are designed to handle a single occurrence of an event, and they automatically unsubscribe after the event is triggered.\n\n- **Type Safety**: Both `once` and `awaited` are type-safe, ensuring that the event data adheres to the defined types, reducing the risk of runtime errors.\n- **Automatic Unsubscription**: After the event is triggered once, both utilities automatically unsubscribe, preventing any further triggers of the event.\n  \n**Key Difference**:\n\n- **Callback vs. Promise**:\n  - `once` is callback-based, allowing you to pass a function that will be invoked when the event occurs.\n  - `awaited` utilizes the Promise API, returning a promise that resolves with the event data, making it ideal for asynchronous workflows.\n\nBy understanding these commonalities and differences, you can choose the right tool based on your specific use case and coding style preferences.\n\n### 6. Creating Specialized Subscription Functions with `subscriber`\n\nThe `subscriber` helper function creates specialized subscription functions for specific event types. This makes the API more ergonomic by removing the need to specify the event name in the listener function.\n\n#### Using `subscriber`\n\n```typescript\nimport { EventDescription, createEventEmitter, subscriber } from 'zohar';\n\n// Define event descriptions with different data types using intersection types\ntype MyEvents = EventDescription\u003c'userConnected', { userId: string; timestamp: Date }\u003e\n  \u0026 EventDescription\u003c'userDisconnected', { userId: string; timestamp: Date; reason: 'timeout' | 'manual' | 'error' }\u003e;\n\n// Create an event emitter\nconst [subscribe, emit] = createEventEmitter\u003cMyEvents\u003e();\n\n// Create a factory function for specialized subscribers\nconst createSubscriber = subscriber(subscribe);\n\n// Create specialized subscription functions for different events\nconst onUserConnected = createSubscriber('userConnected');\nconst onUserDisconnected = createSubscriber('userDisconnected');\n\n// Use the specialized subscription functions\nonUserConnected((data) =\u003e {\n  console.log(`User ${data.userId} connected at ${data.timestamp}`);\n});\n\nonUserDisconnected((data) =\u003e {\n  console.log(`User ${data.userId} disconnected at ${data.timestamp} due to ${data.reason}`);\n});\n\n// Emit events\nemit('userConnected', { userId: 'user123', timestamp: new Date() });\nemit('userDisconnected', {\n  userId: 'user123',\n  timestamp: new Date(),\n  reason: 'timeout' // TypeScript ensures this is one of: 'timeout' | 'manual' | 'error'\n});\n```\n\n### 7. Creating Specialized Emit Functions with `emitter`\n\nThe `emitter` helper function creates specialized emit functions for specific event types. This makes the API more ergonomic by removing the need to specify the event name when emitting events.\n\n#### Using `emitter`\n\n```typescript\nimport { EventDescription, createEventEmitter, subscriber, emitter } from 'zohar';\n\n// Define event descriptions with different data types using intersection types\ntype MyEvents = EventDescription\u003c'userConnected', { userId: string; timestamp: Date }\u003e\n  \u0026 EventDescription\u003c'userDisconnected', { userId: string; timestamp: Date; reason: 'timeout' | 'manual' | 'error' }\u003e;\n\n// Create an event emitter\nconst [subscribe, emit] = createEventEmitter\u003cMyEvents\u003e();\n\n// Create factory functions for specialized subscribers and emitters\nconst createSubscriber = subscriber(subscribe);\nconst createEmitter = emitter(emit);\n\n// Create specialized subscription and emit functions for different events\nconst onUserConnected = createSubscriber('userConnected');\nconst emitUserConnected = createEmitter('userConnected');\n\nconst onUserDisconnected = createSubscriber('userDisconnected');\nconst emitUserDisconnected = createEmitter('userDisconnected');\n\n// Use the specialized subscription functions\nonUserConnected((data) =\u003e {\n  console.log(`User ${data.userId} connected at ${data.timestamp}`);\n});\n\nonUserDisconnected((data) =\u003e {\n  console.log(`User ${data.userId} disconnected at ${data.timestamp} due to ${data.reason}`);\n});\n\n// Use the specialized emit functions\nemitUserConnected({ userId: 'user123', timestamp: new Date() });\nemitUserDisconnected({\n  userId: 'user123',\n  timestamp: new Date(),\n  reason: 'timeout' // TypeScript ensures this is one of: 'timeout' | 'manual' | 'error'\n});\n```\n\n### Benefits of Using `subscriber` and `emitter`\n\n1. **Type Safety**: Both helpers maintain full type safety, ensuring that event data matches the expected types.\n2. **Ergonomic API**: Removes the need to specify event names in listeners and when emitting events.\n3. **Better IDE Support**: Provides better autocompletion and type inference.\n4. **Modular Design**: Allows creating specialized functions for specific event types.\n5. **Reusability**: The factory functions can be used to create specialized functions for any event type.\n\n### Common Use Cases\n\n1. **Component-Specific Event Handling**:\n\n```typescript\n// In a user profile component\nconst onUserProfileUpdate = createSubscriber('userProfileUpdate');\nconst emitUserProfileUpdate = createEmitter('userProfileUpdate');\n\n// Subscribe to profile updates\nonUserProfileUpdate((data) =\u003e {\n  // Handle profile update\n  console.log(`Profile updated: ${data.name}`);\n});\n\n// Emit profile updates\nemitUserProfileUpdate({ name: 'John Doe', age: 30 });\n```\n\n2. **Module-Specific Event Management**:\n\n```typescript\n// In a chat module\nconst onMessageReceived = createSubscriber('messageReceived');\nconst emitMessageReceived = createEmitter('messageReceived');\n\n// Handle incoming messages\nonMessageReceived((data) =\u003e {\n  // Process message\n  console.log(`New message from ${data.sender}: ${data.content}`);\n});\n\n// Send messages\nemitMessageReceived({\n  sender: 'user123',\n  content: 'Hello, world!',\n  timestamp: new Date()\n});\n```\n\n3. **Complex Event Types**:\n\n```typescript\ntype ChatEvents = \n  EventDescription\u003c'messageReceived', { sender: string; content: string; timestamp: Date }\u003e\n  \u0026 EventDescription\u003c'userJoined', { userId: string; username: string; timestamp: Date }\u003e\n  \u0026 EventDescription\u003c'userLeft', { userId: string; reason: 'disconnected' | 'kicked' | 'banned'; timestamp: Date }\u003e;\n\nconst [subscribe, emit] = createEventEmitter\u003cChatEvents\u003e();\nconst createSubscriber = subscriber(subscribe);\nconst createEmitter = emitter(emit);\n\n// Create specialized functions for each event type\nconst onMessageReceived = createSubscriber('messageReceived');\nconst onUserJoined = createSubscriber('userJoined');\nconst onUserLeft = createSubscriber('userLeft');\n\nconst emitMessageReceived = createEmitter('messageReceived');\nconst emitUserJoined = createEmitter('userJoined');\nconst emitUserLeft = createEmitter('userLeft');\n\n// Use the specialized functions\nonMessageReceived((data) =\u003e {\n  console.log(`${data.sender}: ${data.content}`);\n});\n\nonUserJoined((data) =\u003e {\n  console.log(`${data.username} joined the chat`);\n});\n\nonUserLeft((data) =\u003e {\n  console.log(`User left: ${data.reason}`);\n});\n\n// Emit events\nemitMessageReceived({\n  sender: 'user123',\n  content: 'Hello!',\n  timestamp: new Date()\n});\n\nemitUserJoined({\n  userId: 'user123',\n  username: 'John',\n  timestamp: new Date()\n});\n\nemitUserLeft({\n  userId: 'user123',\n  reason: 'disconnected',\n  timestamp: new Date()\n});\n```\n\n## API Reference\n\n- **`EventDescription\u003cEventType extends string, EventDataType = void\u003e`**: Describes an event mapping where each event type is associated with a data type.\n- **`createEventEmitter\u003cEvent extends EventDescription\u003cstring, any\u003e = EventDescription\u003cstring, any\u003e\u003e()`**: Creates an event emitter providing `subscribe`, `emit`, and `unsubscribeAll` functions.\n- **`SubscribeEvent\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e`**: Function type to subscribe to an event, optionally with a predicate.\n- **`EmitEvent\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e`**: Function type to emit an event with the associated data.\n- **`UnsubscribeEvent`**: Function type to unsubscribe a specific event listener.\n- **`UnsubscribeAllEvents\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e`**: Function type to unsubscribe all listeners for a specific event or all events.\n- **`SubscribeOnce\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e`**: Function type to subscribe to an event that automatically unsubscribes after being triggered once.\n- **`once\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e(subscribe: SubscribeEvent\u003cEvent\u003e): SubscribeOnce\u003cEvent\u003e`**: Utility function to create a subscription that triggers only once and then automatically unsubscribes.\n- **`SubscribeAwaited\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e`**: Function type to subscribe to an event and return a promise that resolves when the event is triggered.\n- **`awaited\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e(subscribe: SubscribeEvent\u003cEvent\u003e): SubscribeAwaited\u003cEvent\u003e`**: Utility function that returns a promise that resolves when the specified event is triggered, automatically unsubscribing afterward.\n- **`SpecializedEventListener\u003cEvent extends EventDescription\u003cstring, any\u003e, EventType extends keyof Event \u0026 string\u003e`**: Type for a specialized event listener that doesn't include the event name parameter.\n- **`EventSubscriber\u003cEvent extends EventDescription\u003cstring, any\u003e, EventType extends keyof Event \u0026 string\u003e`**: Type for a specialized subscription function for a specific event type.\n- **`EventSubscriberFactory\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e`**: Type for a factory function that creates specialized subscription functions for specific event types.\n- **`subscriber\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e(subscribe: SubscribeEvent\u003cEvent\u003e): EventSubscriberFactory\u003cEvent\u003e`**: Creates a factory function that can create specialized subscription functions for any event type.\n- **`EventEmitter\u003cEvent extends EventDescription\u003cstring, any\u003e, EventType extends keyof Event \u0026 string\u003e`**: Type for a specialized emit function for a specific event type.\n- **`EventEmitterFactory\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e`**: Type for a factory function that creates specialized emit functions for specific event types.\n- **`emitter\u003cEvent extends EventDescription\u003cstring, any\u003e\u003e(emit: EmitEvent\u003cEvent\u003e): EventEmitterFactory\u003cEvent\u003e`**: Creates a factory function that can create specialized emit functions for any event type.\n\n## Comparison: `zohar` vs. Node.js EventEmitter API\n\nThe `zohar` library and Node.js's built-in `EventEmitter` API serve similar purposes: both are used to emit and listen to events in an application. However, they differ significantly in their design, especially regarding type safety and ease of use when unsubscribing from events.\n\n### Overview\n\n- **`zohar`**: A functional, type-safe event emitter library designed for TypeScript. It emphasizes type safety, ensuring that emitted events and their associated data types are correctly managed. It also simplifies subscription management by returning an `unsubscribe` function.\n- **Node.js EventEmitter**: A widely-used, built-in event emitter API in Node.js. It is flexible but lacks native type safety, which can lead to runtime errors if not used carefully. Managing event unsubscription can be more cumbersome.\n\n### Usage Comparison\n\n#### Example: Using `zohar`\n\nHere's how you would use `zohar` for type-safe event handling:\n\n```typescript\nimport { EventDescription, createEventEmitter } from 'zohar';\n\n// Define event descriptions with specific types\ntype MyEvents = \n    EventDescription\u003c'eventA', { message: string }\u003e \u0026\n    EventDescription\u003c'eventB', { value: number }\u003e;\n\n// Create an event emitter\nconst [subscribe, emit] = createEventEmitter\u003cMyEvents\u003e();\n\n// Subscribe to events\nconst unsubscribeEventA = subscribe('eventA', (eventName, data) =\u003e {\n    console.log(`Event A: ${data.message}`);\n});\n\nconst unsubscribeEventB = subscribe('eventB', (eventName, data) =\u003e {\n    console.log(`Event B: ${data.value}`);\n});\n\n// Emit events\nemit('eventA', { message: 'Hello World' });\nemit('eventB', { value: 42 });\n\n// Unsubscribe when done\nunsubscribeEventA();\nunsubscribeEventB();\n```\n\n#### Example: Using Node.js EventEmitter\n\nHere's how you would use the Node.js EventEmitter for similar functionality:\n\n```typescript\nimport { EventEmitter } from 'node:events';\n\n// Create an instance of EventEmitter\nconst eventEmitter = new EventEmitter();\n\n// Define listener functions so they can be referenced later\nconst onEventA = (data: { message: string }) =\u003e {\n    console.log(`Event A: ${data.message}`);\n};\n\nconst onEventB = (data: { value: number }) =\u003e {\n    console.log(`Event B: ${data.value}`);\n};\n\n// Subscribe to events\neventEmitter.on('eventA', onEventA);\neventEmitter.on('eventB', onEventB);\n\n// Emit events\neventEmitter.emit('eventA', { message: 'Hello World' });\neventEmitter.emit('eventB', { value: 42 });\n\n// Unsubscribe when done\neventEmitter.off('eventA', onEventA);\neventEmitter.off('eventB', onEventB);\n```\n\n### Type Safety and Unsubscription Perspective\n\n- **`zohar`**:\n  - **Type-Safe by Design**: In `zohar`, event names and associated data types are defined upfront, ensuring that only valid events with the correct data structure can be emitted. TypeScript will catch any mismatches at compile time, significantly reducing the risk of runtime errors.\n  - **Type Inference**: When you emit or subscribe to an event, TypeScript knows exactly what data type is expected, offering full type inference and autocompletion within your IDE.\n  - **Simple Unsubscription**: The `subscribe` function in `zohar` returns an `unsubscribe` function, making it easy to remove listeners even when they are defined inline. This avoids the complexity of managing listener references manually.\n\n- **Node.js EventEmitter**:\n  - **No Native Type Safety**: The Node.js EventEmitter API is inherently untyped, meaning you can emit any event with any data, and TypeScript won't provide any safety checks. This can lead to potential runtime errors if the wrong data is emitted or if a listener expects a different data structure.\n  - **Complex Unsubscription**: The `off` (or `removeListener`) method requires the exact reference to the original listener function. If the listener was defined inline, it's difficult to unsubscribe because you need to store the listener function in a variable for later reference. This adds complexity and makes the code harder to manage.\n\n### Pros and Cons\n\n#### `zohar`\n\n**Pros**:\n\n- **Strong Type Safety**: Prevents runtime errors by enforcing event types and data structures at compile time.\n- **Cleaner Code**: Type-safe events lead to more maintainable and readable code, as the expected structure is always clear.\n- **IDE Support**: Full TypeScript support with autocompletion and type inference.\n- **No Instance Required**: Unlike traditional event emitters that require an instance, `zohar` directly provides the `subscribe` and `emit` functions, making them ready to use without needing to create or manage an emitter object.\n- **Better Separation of Concerns**: Since `zohar` provides just functions, it's easier to split emitter and consumer functionalities across different modules or components. This makes the code more modular and aligns well with the principles of separation of concerns.\n- **Easier Unsubscription**: The `unsubscribe` function returned by `subscribe` makes it straightforward to remove listeners, even if they are defined inline, reducing the risk of accidentally leaving listeners active.\n\n**Cons**:\n\n- **Learning Curve**: Developers unfamiliar with TypeScript's advanced type features might find the initial setup slightly more complex.\n- **Overhead**: In small or simple projects where type safety is less of a concern, the strict typing might feel unnecessary.\n\n#### Node.js EventEmitter\n\n**Pros**:\n\n- **Simplicity**: Easy to use and widely understood, as it is a built-in Node.js feature.\n- **Flexibility**: No need to define types upfront, making it quicker to implement in simple scenarios.\n\n**Cons**:\n\n- **Lack of Type Safety**: High risk of runtime errors due to the absence of compile-time checks, especially in larger projects.\n- **Maintenance**: As projects grow, ensuring that event names and data structures remain consistent becomes challenging without type safety.\n- **Requires an Instance**: You must create and manage an instance of `EventEmitter` to use it. This can add unnecessary boilerplate in scenarios where just functions would suffice.\n- **Tighter Coupling**: The need for an emitter instance can lead to tighter coupling between event production and consumption, making it harder to separate these concerns in larger applications.\n- **Complex Unsubscription**: Requires storing listener references to unsubscribe, which complicates the code, especially when trying to manage listeners that are defined inline.\n\n### Conclusion\n\n- **When to Use `zohar`**: If you're working on a TypeScript project where maintaining type safety and avoiding runtime errors is critical, `zohar` is the better choice. It provides strong type guarantees, making your codebase more robust and easier to maintain. Additionally, if you prefer functional programming and want a clear separation between event producers and consumers, `zohar` offers a cleaner, more modular approach with simpler unsubscription.\n  \n- **When to Use Node.js EventEmitter**: If you're in a Node.js environment and need a quick and flexible event emitter for a smaller, simpler project, the built-in EventEmitter might suffice, but be aware of the potential for type-related issues, the need to manage instances, and the complexity of handling unsubscriptions.\n\n## Usage Examples\n\n### Chat Module Usage\n\nThe Chat Module handles all internal chat logic and emits events when users connect, disconnect, or send messages. As a consumer, you can subscribe to these events to react to user activities. Additionally, the module allows you to unsubscribe from events when they are no longer needed, ensuring efficient resource management.\n\n#### Chat Module Implementation\n\nThe Chat Module is implemented as a functional module that encapsulates all chat-related logic. It emits events internally when users perform actions such as connecting, disconnecting, or sending messages. The module exposes only a `subscribe` function (`onChatEvent`) for external use.\n\n```typescript\nimport { EventDescription, createEventEmitter } from 'zohar';\n\n// Define distinct event descriptions for the chat application\ntype ChatEvents = \n    EventDescription\u003c'userConnected', { userId: string; timestamp: Date }\u003e \u0026\n    EventDescription\u003c'userDisconnected', { userId: string; timestamp: Date }\u003e \u0026\n    EventDescription\u003c'messageReceived', { userId: string; message: string; timestamp: Date }\u003e;\n\n// Create an event emitter for chat events\nconst [subscribe, emit] = createEventEmitter\u003cChatEvents\u003e();\n\n// Internal logic that might use something like Redis events, databases, etc.\n// Pseudocode for event handling\n\n// Simulate user connection\nsetTimeout(() =\u003e {\n    const userId = 'user123';\n    const timestamp = new Date();\n    emit('userConnected', { userId, timestamp });\n}, 1000); // Simulate user connection after 1 second\n\n// Simulate user message\nsetTimeout(() =\u003e {\n    const userId = 'user123';\n    const message = 'Hello World!';\n    const timestamp = new Date();\n    emit('messageReceived', { userId, message, timestamp });\n}, 2000); // Simulate message after 2 seconds\n\n// Simulate user disconnection\nsetTimeout(() =\u003e {\n    const userId = 'user123';\n    const timestamp = new Date();\n    emit('userDisconnected', { userId, timestamp });\n}, 3000); // Simulate user disconnection after 3 seconds\n\n// Expose the subscribe function directly\nexport { subscribe as onChatEvent };\n```\n\n#### Subscribing to Chat Events\n\nTo listen to chat events such as user connections, disconnections, and messages, you can use the `onChatEvent` function. This function allows you to subscribe to specific events and handle them as needed.\n\n##### Available Events\n\n- **`userConnected`**: Triggered when a user connects.\n- **`userDisconnected`**: Triggered when a user disconnects.\n- **`messageReceived`**: Triggered when a user sends a message.\n\n##### Example: Subscribing to Events\n\n```typescript\nimport { onChatEvent } from './chatService';\n\n// Listen for user connection\nconst unsubscribeUserConnected = onChatEvent('userConnected', (eventName, data) =\u003e {\n    console.log(`User ${data.userId} connected at ${data.timestamp}`);\n});\n\n// Listen for user disconnection\nconst unsubscribeUserDisconnected = onChatEvent('userDisconnected', (eventName, data) =\u003e {\n    console.log(`User ${data.userId} disconnected at ${data.timestamp}`);\n});\n\n// Listen for messages received\nconst unsubscribeMessageReceived = onChatEvent('messageReceived', (eventName, data) =\u003e {\n    console.log(`Message from ${data.userId}: \"${data.message}\" at ${data.timestamp}`);\n});\n```\n\n#### Unsubscribing from Chat Events\n\nEach subscription returns an `unsubscribe` function, which you can call to stop listening to that event. This is useful when you no longer need to respond to an event, helping to optimize resource usage.\n\n##### Example: Unsubscribing from Events\n\nIn the following example, we subscribe to the `messageReceived` event and then unsubscribe from it after 2.5 seconds.\n\n```typescript\nimport { onChatEvent } from './chatService';\n\n// Subscribe to the messageReceived event\nconst unsubscribeMessageReceived = onChatEvent('messageReceived', (eventName, data) =\u003e {\n    console.log(`Message from ${data.userId}: \"${data.message}\" at ${data.timestamp}`);\n});\n\n// Unsubscribe after 2.5 seconds\nsetTimeout(() =\u003e {\n    console.log('Unsubscribing from messageReceived event');\n    unsubscribeMessageReceived(); // Stop listening to messages\n}, 2500);\n```\n\n#### Internal Logic\n\nThe Chat Module handles events such as user connections, disconnections, and messages internally. This logic could involve interactions with databases, message brokers like Redis, or other systems. The specifics of these operations are encapsulated within the module, making it a \"black box\" to the consumer. The only interaction point for consumers is the `onChatEvent` function.\n\n#### Chat Module Summary\n\nThe Chat Module provides a straightforward API for subscribing to and unsubscribing from chat-related events. By encapsulating all the complex internal logic, the module offers a clean and efficient way for consumers to react to user activities without needing to manage the underlying event handling mechanisms.\n\n### One-Time Login Event Handling with `awaited` and `once`\n\nThe following example demonstrates how to handle a one-time login event in a browser environment using the `zohar` library's `awaited` and `once` utility functions. The login process is encapsulated within a fictional module that interacts with an API and emits various events based on different operations. For illustration purposes, we focus on handling the `login` event.\n\n#### Login Module Implementation\n\nThis fictional module simulates an API login request. It emits a single `login` event with a discriminated union type that indicates whether the login was successful or not. The module could emit other events as well, but here we illustrate the usage of the `login` event. The module exposes a specific function, `onLoginEvent`, for subscribing to the `login` event.\n\n```typescript\nimport { EventDescription, createEventEmitter } from 'zohar';\n\n// Define event descriptions with a discriminated union for login results\ntype LoginResult = \n    | { success: true; userId: string; token: string }\n    | { success: false; error: string };\n\ntype LoginEvents = EventDescription\u003c'login', LoginResult\u003e;\n\n// Create an event emitter for login events (and potentially other events)\nconst [subscribe, emit] = createEventEmitter\u003cLoginEvents\u003e();\n\n// Simulated login function that interacts with an API\nasync function login(username: string, password: string) {\n    try {\n        // Simulate an API request with a timeout\n        const response = await fakeApiLoginRequest(username, password);\n\n        // Emit a successful login event\n        emit('login', { success: true, userId: response.userId, token: response.token });\n    } catch (error) {\n        // Emit a failed login event\n        emit('login', { success: false, error: error.message });\n    }\n}\n\n// Simulated API login request function\nfunction fakeApiLoginRequest(username: string, password: string): Promise\u003c{ userId: string; token: string }\u003e {\n    return new Promise((resolve, reject) =\u003e {\n        setTimeout(() =\u003e {\n            if (username === 'correctUser' \u0026\u0026 password === 'correctPassword') {\n                resolve({ userId: 'user123', token: 'abcdef' });\n            } else {\n                reject(new Error('Invalid credentials'));\n            }\n        }, 1000); // Simulate a 1-second API response time\n    });\n}\n\n// Expose the subscribe function specifically for the login event\nexport { subscribe as onLoginEvent, login };\n```\n\n#### Consumer Usage Example with `awaited`\n\nThe consumer uses the `awaited` function to wait for the `login` event. The function resolves the promise if the login is successful and rejects it if the login fails, all based on the discriminated union type.\n\n```typescript\nimport { onLoginEvent, login } from './loginService';\nimport { awaited } from 'zohar';\n\nasync function attemptLogin(username: string, password: string): Promise\u003c{ userId: string; token: string }\u003e {\n    // Trigger the login process\n    login(username, password);\n\n    // Use the `awaited` function to wait for the `login` event\n    const awaitLoginEvent = awaited(onLoginEvent);\n    const result = await awaitLoginEvent('login');\n\n    // Throw an error if the login failed\n    if (!result.success) {\n        throw new Error(result.error); // Reject with the error message\n    }\n\n    // Return the successful login data\n    return { userId: result.userId, token: result.token };\n}\n\n// Usage example\nattemptLogin('correctUser', 'correctPassword')\n    .then((data) =\u003e {\n        console.log('Login successful!', data);\n    })\n    .catch((error) =\u003e {\n        console.error('Login failed:', error.message);\n    });\n```\n\n#### Consumer Usage Example with `once`\n\nAlternatively, the consumer can use the `once` function to handle the `login` event. This function automatically unsubscribes after the event is triggered, providing a callback-based approach to handle the event.\n\n```typescript\nimport { onLoginEvent, login } from './loginService';\nimport { once } from 'zohar';\n\n// Use the `once` function to handle the `login` event\nconst onceSubscribe = once(onLoginEvent);\n\nonceSubscribe('login', (eventName, result) =\u003e {\n    if (result.success) {\n        console.log('Login successful!', result.userId, result.token);\n    } else {\n        console.error('Login failed:', result.error);\n    }\n});\n\n// Trigger the login process\nlogin('correctUser', 'correctPassword');\n```\n\n#### Explanation\n\n- **Login Module**: The module provides a `login` function that simulates an API request. It emits a `login` event with a discriminated union type that indicates whether the login was successful or not. The module could also emit other events, but this example focuses on the `login` event.\n- **Exposing the `onLoginEvent` Function**: The `onLoginEvent` function is specifically exposed to allow consumers to subscribe to the `login` event, making it clear what the function is intended for.\n- **Promise-Based Consumer with `awaited`**: The consumer uses the `awaited` function to wait for the `login` event. The promise resolves with the login data if successful or throws an error if the login fails, all within a single event handler.\n- **Callback-Based Consumer with `once`**: Alternatively, the consumer can use the `once` function to handle the `login` event. This approach automatically unsubscribes after the event is triggered, providing a more traditional callback mechanism without the need for Promises.\n\n#### Login Module Summary\n\nThis implementation demonstrates how to handle a one-time login event using the `zohar` event emitter library with both the `awaited` and `once` utility functions. The login module is fictional and is designed to illustrate the usage of the `zohar` library in handling events, specifically focusing on the `login` event. The module could emit other events, but the examples provided focus on demonstrating two different approaches to handling a single `login` event in a promise-based and callback-based manner. This approach is particularly useful when you want to handle events asynchronously, with automatic subscription management and a unified way to handle both success and failure outcomes.\n\n## License\n\nThis library is open-source and available under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconstantiner%2Fzohar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconstantiner%2Fzohar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconstantiner%2Fzohar/lists"}