{"id":17651314,"url":"https://github.com/raiondesu/eventhoven","last_synced_at":"2025-05-07T08:29:21.045Z","repository":{"id":35192330,"uuid":"214983551","full_name":"Raiondesu/eventhoven","owner":"Raiondesu","description":"Event manager that composes events effortlessly 🎵","archived":false,"fork":false,"pushed_at":"2023-01-04T22:45:29.000Z","size":2396,"stargazers_count":5,"open_issues_count":18,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-14T16:46:44.741Z","etag":null,"topics":["compose-events","event","event-handlers","event-management","event-manager","event-signature","functional","functional-programming","manager","meta-events","solid","solid-principles","tree-shakeable","type-safe","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/eventhoven","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/Raiondesu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-14T08:03:15.000Z","updated_at":"2022-12-26T19:41:51.000Z","dependencies_parsed_at":"2023-01-15T15:52:23.059Z","dependency_job_id":null,"html_url":"https://github.com/Raiondesu/eventhoven","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raiondesu%2Feventhoven","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raiondesu%2Feventhoven/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raiondesu%2Feventhoven/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raiondesu%2Feventhoven/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Raiondesu","download_url":"https://codeload.github.com/Raiondesu/eventhoven/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246436393,"owners_count":20776995,"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":["compose-events","event","event-handlers","event-management","event-manager","event-signature","functional","functional-programming","manager","meta-events","solid","solid-principles","tree-shakeable","type-safe","typescript"],"created_at":"2024-10-23T11:40:49.936Z","updated_at":"2025-04-01T01:32:16.745Z","avatar_url":"https://github.com/Raiondesu.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\" style=\"text-align: center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/eventhoven\"\u003e\u003cimg src=\"assets/logo.png\"/\u003e\u003c/a\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://travis-ci.com/Raiondesu/eventhoven\" title=\"Latest Travis CI build\"\u003e\u003cimg src=\"https://img.shields.io/travis/com/raiondesu/eventhoven?style=flat-square\" alt=\"travis\"\u003e\u003c/a\u003e\n\u003ca href=\"https://www.npmjs.com/package/eventhoven\" title=\"Downloads per month, but who cares?\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/eventhoven.svg?style=flat-square\" alt=\"npm\"\u003e\u003c/a\u003e\n\u003ca href=\"https://bundlephobia.com/result?p=eventhoven@latest\" title=\"minzipped size\"\u003e\u003cimg src=\"https://img.shields.io/bundlephobia/minzip/eventhoven@latest?style=flat-square\" alt=\"size\"\u003e\u003c/a\u003e\n\u003ca href=\"https://coveralls.io/github/Raiondesu/eventhoven\" title=\"Code coverage\"\u003e\u003cimg src=\"https://img.shields.io/coveralls/github/Raiondesu/eventhoven?style=flat-square\" alt=\"coveralls\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codeclimate.com/github/Raiondesu/eventhoven/maintainability\" title=\"Code quality\"\u003e\u003cimg src=\"https://img.shields.io/codeclimate/maintainability/Raiondesu/eventhoven?style=flat-square\" alt=\"code quality\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://codepen.io/raiondesu/pen/qBBPPom\" title=\"Link to in-browser playground\"\u003e\u003cimg src=\"https://img.shields.io/badge/playground-link-blueviolet?style=flat-square\" alt=\"code pen\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Table of Contents \u003c!-- omit in toc --\u003e\n- [What is this?](#what-is-this)\n- [Disclaimer](#disclaimer)\n  - [TypeScript](#typescript)\n  - [Currying](#currying)\n  - [External state \u003e\u003e\u003e Internal state](#external-state--internal-state)\n  - [OK, but why not %event-manager-package%?](#ok-but-why-not-event-manager-package)\n- [Installation](#installation)\n  - [Importing](#importing)\n  - [Simple usage examples](#simple-usage-examples)\n- [API](#api)\n  - [`eventMap(events)`](#eventmapevents)\n    - [Event handler](#event-handler)\n    - [Event context](#event-context)\n  - [`emit(eventMap)(event)(...args): Promise\u003cvoid\u003e`](#emiteventmapeventargs-promisevoid)\n  - [`emitAll(eventMap)(eventArgs): object`](#emitalleventmapeventargs-object)\n  - [`subscribe(eventMap)(event)(...handlers): () =\u003e void`](#subscribeeventmapeventhandlers---void)\n  - [`subscribeToAll(eventMap)(...handlers)`](#subscribetoalleventmaphandlers)\n  - [`unsubscribe(eventMap)(event)(...handlers)`](#unsubscribeeventmapeventhandlers)\n  - [`unsubscribeFromAll(eventMap)(...handlers)`](#unsubscribefromalleventmaphandlers)\n  - [`once(handler): handler`](#oncehandler-handler)\n  - [`wait(eventMap)(event): Promise\u003cargs[]\u003e`](#waiteventmapevent-promiseargs)\n  - [`harmonicWait(eventMap)(event)(): Promise\u003cargs[]\u003e`](#harmonicwaiteventmapevent-promiseargs)\n  - [`debug(isEnabled)`](#debugisenabled)\n    - [Custom logging function](#custom-logging-function)\n  - [Collections](#collections)\n  - [Meta-Events (Plugin API)](#meta-events-plugin-api)\n  - [Class API](#class-api)\n- [Contribute](#contribute)\n\n## What is this?\nIt's a tiny and simple type-safe event manager library for browser and node,\n~1KB (gzipped, tree-shakeable - [essentials are less than 500B](#api)).\n\nIt provides a powerful set of tools for creating and composing event managers.\\\nIn other words, it manages event managers!\n\nMain list of features includes (but is not limited to):\n- Full tree-shaking\n- Functional-style API\n- Multiple event arguments\n- Event names can also be symbols (private events)\n- Soft error-handling - no unexpected runtime errors!\n- Versatile plugin system (using [meta-events](#meta-events-plugin-api))\n- Fully type-safe - each event remembers its name and type signature\n- All functions are curried and point-free, which makes them easier to use in a functional environment\n  (for example, with [`ramda`](https://github.com/ramda/ramda) and similar tools)\n- **SOLID** code\n  - **S**RP - every function does only one thing\n  - **O**CP - HOFs allow to change certain behaviours without the need to rewrite code\n  - **L**SP - all functions are easily substitutable as long as they adhere to the same API\n  - **I**SP - all data types are the least specific versions of them\n  - **D**IP - API depends only on abstractions\n- Code-generation-friendly:\\\n  Due to the SRP, all functions have a very limited number of ways of invocation.\\\n  This allows to automatically generate efficient code (for example, CRUD events) for this library without concerns about its stability.\n- **KISS** and **DRY** code\n\nSomething's missing or found a bug?\\\nFeel free to [create an issue](https://github.com/Raiondesu/eventhoven/issues/new)! 😉\n\n---\n\n## Disclaimer\n\u003e and some ground principles\n\n### TypeScript\n\n`eventhoven`'s main concern is type-safety at every step,\nso all the code examples will be written in [typescript](https://www.typescriptlang.org).\n\nIt was written in a \"type-first, implementation-later\" way,\nand will be kept that way to ensure that runtime types always match the static ones.\n\n### Currying\n\n\"Why curry functions?\" you may ask. Great question! It has many answers on the web already, but I'd recommend reading [this](https://medium.com/@iquardt/currying-the-underestimated-concept-in-javascript-c95d9a823fc6) and [this](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/ch04.html).\n\n`eventhoven` uses the concept of currying to elevate the abstraction\nand allow for a much wider and precise usage of it's API in return for sometimes writing `)(` instead of usual `, `,\nwhich is not too much of a price to pay for this feature.\n\nIt also allows `eventhoven` to be used effortlessly with other functional libraries like [`ramda`](https://github.com/ramda/ramda) and many others.\n\nNot all `eventhoven` function are curried. Those, which are, however, will have the following disclaimer:\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\n### External state \u003e\u003e\u003e Internal state\n\n`eventhoven` doesn't store anything internally, it's a completely stateless, pure and side-effect-free library.\\\nIt only has side-effects from closures on an *external* state that a user provides.\\\nSo, there it is - no private fields, no hidden implementation details, no complications.\\\nThis allows for easier testing *and* simpler usage.\n\nThanks to this rule, `eventhoven` is a higher abstraction above other event-managers. A Higher-Order-Event-Manager, if you like.\\\nThat is, any other event manager's API can be built on top of what `eventhoven` gives you, providing a nearly endless set of possibilities.\n\n### OK, but why not %event-manager-package%?\n\n`eventhoven` is not in direct comparison to other event managers.\nAs stated in the main description - its main purpose is to **compose** events and event managers.\n\nIn production, it's a fairly typical scenario that multiple libraries\nwith multiple event systems exist and function in the same project at the same time.\\\nFront-end libraries do that all the time - `vue`, `react`, `angular` - all have own separate event systems - even from the DOM!\n`eventhoven` aims to provide a connecting bridge for different event managing strategies,\nby **providing instruments** for unifying the event management API.\\\nIn other words, it allows to **unify** event management.\n\nIt just so happens that it can do event management very efficiently too. 😉\n\n---\n\n## Installation\n\n**npm**:\n```bash\nnpm i -S eventhoven\n```\n\n**browser**:\n```html\n\u003c!-- ES2015 --\u003e\n\u003cscript type=\"module\"\u003e\n  import { eventMap, emit, on, off } from 'https://unpkg.com/eventhoven';\n\n  // use it here\n\u003c/script\u003e\n\n\u003c!-- ES5 with IE11+ general syntax polyfills, global object - `eventhoven` --\u003e\n\u003c!-- Polyfill `window.Promise` and `Object.assign` yourself! --\u003e\n\u003cscript src=\"https://unpkg.com/eventhoven/dist/umd.js\"\u003e\u003c/script\u003e\n```\n\n### Importing\n\n```ts\n// TS-module (pure typescript),\n// allows compilation settings to be set from the project config\nimport { eventMap, emit, on, off } from 'eventhoven/src';\n\n// ES-module (npm/node, typescript)\nimport { eventMap, emit, on, off } from 'eventhoven';\n\n// ESNext (no polyfills for esnext)\nimport { eventMap, emit, on, off } from 'eventhoven/dist/esnext';\n\n// ES-module (browser, node)\nimport { eventMap, emit, on, off } from 'https://unpkg.com/eventhoven';\n\n// Classic node commonjs\nconst { eventMap, emit, on, off } = require('eventhoven/dist/js');\n```\n\n\n### Simple usage examples\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eExample 1\u003c/b\u003e\u003c/summary\u003e\n\n```ts\n// Essential imports\nimport { eventMap, emit, on, off } from 'eventhoven';\n\n// Event-map declaration\nconst emojiEvents = eventMap({\n  // key - event name,\n  // function arguments - event arguments,\n  // function body - default handler for the event\n  // (leave emtpy if you need to just declare the event)\n  '👩'(context, emoji: '👨' | '👩') {},\n  '🌈'(context, emoji: '🦄' | '🌧') {},\n  '🎌'(context, emoji: '👘' | '🍣' | '🚗', amount: number) {},\n});\n\non(emojiEvents)('🎌')(\n  (context, emoji, amount) =\u003e console.log(`Yay!, ${amount} ${emoji}-s from ${context.event}!`)\n);\n\non(emojiEvents)('🎌')(\n  // Returning promises is also allowed (example API from http://www.sushicount.com/api)\n  (context, emoji, amount) =\u003e fetch('http://api.sushicount.com/add-piece-of-sushi/')\n    .then(_ =\u003e _.json())\n    .then(resp =\u003e console.log(`Yay!, ${resp.pieces_of_sushi} ${emoji}-s loaded from sushicount!`))\n);\n\n// It's possible to await execution of all event handlers too\nawait emit(emojiEvents)(\n  // Autocomplete for event names here!\n  '🎌'\n)(\n  // Autocomplete for arguments here!\n  '🍣', 10\n);\n// Console output:\n// =\u003e Yay!, 10 🍣-s from 🎌!\n// =\u003e Yay!, 11 🍣-s loaded from sushicount!\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eExample 2 (Todo-App)\u003c/b\u003e\u003c/summary\u003e\n\n```ts\nimport { eventMap, emit, on, off } from 'eventhoven';\n\ntype Todo = { done: boolean; text: string; };\n\nconst todos: Todo[] = [];\n\n// Event-map declaration\nconst todoEvents = eventMap({\n  // key - event name,\n  // first argument (context) - event contxext\n  // function arguments - event arguments,\n  // function body - default handler for the event\n  // (leave emtpy if you need to just declare the event)\n  'add-todos'(context, todos: Todo[], ...newTodos: Todo[]) {\n    // Typically, a default handler is used to compose events from other event managers here.\n    // But we'll just implement a simple todo-app for now\n    todos.push(...newTodos);\n  },\n  'done-change'(context, todo: Todo, newDone: boolean) {\n    todo.done = newDone;\n  },\n  'text-change'(context, todo: Todo, newText: string) {\n    todo.text = newText;\n  },\n});\n\nconst emitTodo = emit(todoEvents);\n\nconst unsubFromAddTodos = on(todoEvents)('add-todos')(\n  // Parameter types are inferred here\n  (context, todos, ...newTodos) =\u003e newTodos.forEach(todo =\u003e console.log(\n    `Wow, new todo added - \"${todo.text}\"!`,\n    todo.done ? `And it's done already!` : 'Need to do it!'\n  ))\n);\n\nconst addTodos = emitTodo('add-todos');\n\naddTodos(\n  todos,\n  { text: 'learn fp', done: true },\n  { text: 'publish a cool event manager', done: true },\n);\n// =\u003e Wow, new todo added - \"learn fp\"! And it's done already!\n// =\u003e Wow, new todo added - \"publish a cool event manager\"! And it's done already!\n\n// Unsubbed the wow-ing console.log from the event\nunsubFromAddTodos();\n\naddTodos(\n  todos,\n  { text: 'buy milk', done: false }\n);\n// nothing happens in the console now\n\nconsole.log(todos);\n// =\u003e [\n//   { text: 'learn fp', done: true },\n//   { text: 'publish a cool event manager', done: true },\n//   { text: 'buy milk', done: false }\n// ]\n\nconst changeText = emitTodo('text-change');\nconst changeDone = emitTodo('done-change');\n\nchangeText(todos[2], 'write documentation');\n\nchangeDone(todos[2], true);\n\nconsole.log(todos);\n// =\u003e [\n//   { text: 'learn fp', done: true },\n//   { text: 'publish a cool event manager', done: true },\n//   { text: 'buy milk', done: true }\n// ]\n```\n\u003c/details\u003e\n\n[**Code Pen Playground**](https://codepen.io/Raiondesu/pen/qBBPPom)\n\n---\n\n## API\n\nThere are only 4 essential exports that are needed to use this library:\n\nname | type | description\n-----|------|--------------------\n[`eventMap`](#eventmapevents) | `function` | Event-map factory\n[`emit`](#emiteventmapeventargs-promisevoid) | `function` | Event emitter factory\n[`on`](#subscribeeventmapeventhandlers---void) | `function` | Event subscriber factory\n[`off`](#unsubscribeeventmapeventhandlers) | `function` | Event unsubscriber factory\n\n**Together they add up to less than 500 Bytes (gzipped)!**\n\nEverything else is just syntax sugar and boilerplate reduction.\n\n\u003cdetails\u003e\n\u003csummary\u003e\nList of all exports is as follows\n\u003c/summary\u003e\n\nname | type | description\n-----|------|--------------------\n[`eventMap`](#eventmapevents) | `function` | Event-map factory\n[`emit`](#emiteventmapeventargs-promisevoid) | `function` | Event emitter factory\n[`subscribe`](#subscribeeventmapeventhandlers---void) | `function` | Event subscriber factory\n[`subscribeToAll`](#subscribetoalleventmaphandlers) | `function` | Event subscriber factory for all events in a collection\n[`on`](#subscribeeventmapeventhandlers---void) | `function` | Alias for [`subscribe`](#subscribeeventmapeventhandlers---void)\n[`onAll`](#subscribetoalleventmaphandlers) | `function` | Alias for [`subscribeToAll`](#subscribetoalleventmaphandlers)\n[`unsubscribe`](#unsubscribeeventmapeventhandlers) | `function` | Event unsubscriber factory\n[`unsubscribeFromAll`](#unsubscribefromalleventmaphandlers) | `function` | Event unsubscriber factory\n[`off`](#unsubscribeeventmapeventhandlers) | `function` | Alias for [`unsubscribe`](#unsubscribeeventmapeventhandlers)\n[`offAll`](#unsubscribefromalleventmaphandlers) | `function` | Alias for [`unsubscribeFromAll`](#unsubscribefromalleventmaphandlers)\n[`once`](#oncehandler-handler) | `function` | Makes a handler be executed only once\n[`emitCollection`](#collections) | `function` | Creates a collection of event-emitters from an event-map\n[`subscribeCollection`](#collections) | `function` | Creates a collection of event-subscribers from an event-map\n[`unsubscribeCollection`](#collections) | `function` | Creates a collection of event-unsubscribers from an event-map\n[`eventCollection`](#collections) | `function` | Creates a collection of the three previous collections from an event-map\n[`wait`](#waiteventmapevent-promiseargs) | `function` | Waits for an event to be executed\n[`harmonicWait`](#harmonicwaiteventmapevent-promiseargs) | `function` | Same as [`wait`](#waiteventmapevent-promiseargs) but has an arity of 3, just as all the other event-handling functions\n[`debug`](#debugisenabled) | `function` | Sets the debug mode (if enabled - logs all events to the console)\n[`customDebug`](#custom-logging-function) | `function` | Creates a custom debugger based on a function passed to it\n[`metaEvents`](#meta-events-plugin-api) | `object` | A meta-event-map. Can be used to subscribe to the internal eventhoven's events\n`emitMeta` | `function` | A meta-event emitter. An [`emit`](#emiteventmapeventargs-promisevoid) function created for [`metaEvents`](#meta-events-plugin-api)\n[`Eventhoven`](#class-api) | `class` | A class wrapper for `eventMap` and `eventCollection`.\n\u003c/details\u003e\n\n\n\n---\n\n### `eventMap(events)`\n\nCreates an event-map from event signatures.\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`events` | [`TEventSignatures`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L12) | a collection of event signatures\n\n**Returns**: [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15)\n\nThis function is the main \"entry point\" to the whole event management pipeline.\nIt constructs a base storage for events and their handlers, which is then used by all of the other functions.\n\nIn other words, to start working with events in `eventhoven` you start by creating an event-map:\n```ts\nimport { eventMap } from 'eventhoven';\n\n// `keyboardEvents` should now be used for all event interactions\nconst keyboardEvents = eventMap({\n  keyup(context, e: KeyboardEvent) {},\n  keydown(context, e: KeyboardEvent) {},\n  keypress(context, e: KeyboardEvent, modifier?: string) {\n    // This is a default handler for the event,\n    // it's always executed when the event is invoked\n    console.log('modifier:', modifier);\n  },\n});\n```\n\nIn this example, keys in `keyboardEvents` correspond to event names ('keyup', 'keydown', etc.) and values contain handler maps and amount (and types) of arguments for a given event.\n\n\u003cdetails\u003e\n\u003csummary\u003e\nWhy plain functions as event signatures?\n\u003c/summary\u003e\n\nThe decision to use plain functions as event signatures comes down to 3 advantages:\n1. It's easier to make type inference that way.\\\n   Since a handler and the event signature are both functions,\\\n   there's no need to convert types from event signature to event handler.\n\n2. It allows to compose event managers easier.\\\n   Having an event signature be a default event handler\\\n   allows to emit other managers' events directly in the event declaration, for example:\n   ```ts\n    import { eventMap } from 'eventhoven';\n    import { VueApp } from './app.vue';\n\n    const someEventMap = eventMap({\n      someEvent() {\n        // Emitting a Vue event\n        VueApp.$emit('someEvent');\n      }\n    });\n   ```\n\n   Also, it is a common practice to add a default handler for the event\\\n   right after its declaration, which produces lines like this:\n   ```ts\n    import { Manager } from 'some-event-manager';\n\n    const manager = new Manager([ 'event' ]);\n    // Boilerplate code:\n    manager.on('event', () =\u003e console.log('I want to debug all invocations of this event!'));\n   ```\n   `eventhoven` allows to never have to do that.\n\n3. It allows code analysis tools detect which events are never being emitted.\\\n   Since the event signatures are executable pieces of code, which are **always** executed on their respective events,\\\n   code analysis tools (code coverages, testing frameworks) can detect and count the amount of executions\\\n   of these signatures. And this amount is equivalent to the amount of emits of a particular event.\\\n   This allows, in turn, to always know which events are never emitted and should be deleted from the event-map.\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\nHow do I add new events at runtime?\n\u003c/summary\u003e\n\n- by adding an event to a generic map:\n  ```ts\n  import { eventMap, TEventSignatures } from 'eventhoven';\n\n                     // Makes event-map accept any event into itself\n  const someEventMap = eventMap\u003cTEventSignatures\u003e({\n    someEvent() {}\n  });\n\n  // Adding a new event to the map! (notice, no default handler)\n  someEventMap['newEvent'] = new Map();\n\n  // And now we can use it:\n  emit(someEventMap)('newEvent')();\n  ```\n\n- by creating a new event-map 😁:\n  ```ts\n  const inputEvents = {\n    ...keyboardEvents,\n    ...eventMap({\n      'mouse-click'(context, e: MouseEvent) {},\n    }),\n  }\n\n  // Still have type inference here!\n  emit(inputEvents)('mouse-click')\n  ```\n\n\u003c/details\u003e\n\n\n#### Event handler\n\nAs stated earlier, event signatures are read from the default **event handlers** in the event-map.\n\nAny event handler has [a following signature](https://github.com/Raiondesu/eventhoven/tree/master/src/types.d.ts#L6):\n\n`(context: TEventContext, ...args: any[]) =\u003e unknown | Promise\u003cunknown\u003e`\n\n**Parameters**:\n\nname | type | description\n------|-----|--------------\n`context` | [`TEventContext`](#event-context) | a context given by `eventhovent` for every event\n`...args` | `any[]` (contextual) | an event-specific array of arguments\n\n**Returns**: any value or a promise with any value.\\\nThe type of said value is determined by the default handler in the event-map, or remains `unknown`.\n\n\u003cdetails\u003e\n\u003csummary\u003e\nSimple example\n\u003c/summary\u003e\n\n```ts\nconst map = eventMap({\n  // An event handler that returns a number\n  numberEvent(context): number {\n    // The default is 42\n    return 42;\n  }\n});\n\n// Type is usually inferred automatically\nconst result: number[] = await emit(map)('numberEvent')();\n\nconsole.log(result); // =\u003e [42]\n\n// Let's add another handler to the mix\non(map)('numberEvent')(ctx =\u003e 43);\n\nconst result2 = await emit(map)('numberEvent')();\n\nconsole.log(result2); // =\u003e [42, 43]\n```\n\n\u003c/details\u003e\n\n#### Event context\n\nYou've probably noticed by now,\nthat all event handlers have a first `context` parameter.\n\nThis is the event context that's provided by `eventhoven`, and it is an object of the following signature:\n\nkey | type | description\n----|------|-----------------\n`event` | `PropertyKey` | An event that triggered this handler.\n`unsubscribe` | `() =\u003e void` | A function that unsubscribes the current handler from the event.\n\n\u003cdetails\u003e\n\u003csummary\u003e\nSimple example\n\u003c/summary\u003e\n\n```ts\nconst map = eventMap({\n  eventName(context) {\n    console.log(context.event); // =\u003e \"eventName\"\n    console.log(typeof context.unsubscribe); // =\u003e \"function\"\n  }\n});\n```\n\u003c/details\u003e\n\n---\n\n### `emit(eventMap)(event)(...args): Promise\u003cvoid\u003e`\n\nCreates event emitters for an event-map.\\\nIf an event does not exist, it will be ignored.\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to emit events from\n`event` | `PropertyKey` | An event name to emit for a given event-map (can be a symbol too)\n`...args` | `any (contextual)` | Arguments for the specific event, spread\n\n**Returns**: `Promise\u003cvoid\u003e` - a promise that is resolved when all event handlers have finished their execution\n\n---\n\n### `emitAll(eventMap)(eventArgs): object`\n\nEmits **all** events in an event map.\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to subscribe to.\n`eventArgs` | [`TEventParamsMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/all.ts#L31) | Parameters for all events in an event map.\n\n**Returns**: `Record\u003ckeyof M, Promise\u003cvoid\u003e\u003e` - a map for all events' emits promises (each will resolve upon all event handlers' resolution).\n\n\n---\n\n### `subscribe(eventMap)(event)(...handlers): () =\u003e void`\n\nCreates event subscribers for an event in an event-map.\\\nIf an event does not exist, it will be ignored.\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to get events from.\n`event` | `PropertyKey` | An event name to subscribe to for a given event-map (can be a symbol too).\n`...handlers` | `function[]` | Handlers to execute on the event, spread. If emtpy, no subscribing is done.\n\n**Returns**: `() =\u003e void` - a function that unsubscribes the handler from the event\n\n**Alias**: `on`\n\n### `subscribeToAll(eventMap)(...handlers)`\n\nSubscribes handler(s) to **all** events in an event map.\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to subscribe to.\n`...handlers` | `function[]` | Handlers to execute on the events, spread. If emtpy, no subscribing is done.\n\n**Returns**: `void`\n\n**Alias**: `onAll`\n\n---\n\n### `unsubscribe(eventMap)(event)(...handlers)`\n\nUnsubscribes handlers from events of an event-map.\\\nIf an event does not exist, it will be ignored.\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to unsubscribe handlers from.\n`event` | `PropertyKey` | An event name to unsub from for a given event-map (can be a symbol too).\n`...handlers` | `function[]` | Handlers to unsubscribe from the event, spread. If empty - all currently subbed handlers will be unsubscribed.\n\n**Returns**: `void`\n\n**Alias**: `off`\n\n\n### `unsubscribeFromAll(eventMap)(...handlers)`\n\nUnsubscribes handler(s) from **all** events in an event map.\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to unsubscribe from.\n`...handlers` | `function[]` | Handlers to unsubscribe from the events, spread. If empty - all currently subbed handlers will be unsubscribed.\n\n**Returns**: `void`\n\n**Alias**: `offAll`\n\n---\n\n### `once(handler): handler`\n\nMakes a handler being called only once upon the subscribed event invocation.\\\nShould be used with [`subscribe`](#subscribeeventmapeventhandlers---void) to reduce boilerplate for one-time handlers.\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`handler` | `function` | An event handler to be `once`-d\n\n**Returns**: `handler` - a changed handler that was passed in\n\nUsage example:\n```ts\non(eventmap)('some-event')(once((ctx, arg) =\u003e {\n  console.log('This handler will only be called once!');\n}));\n```\n\n---\n\n### `wait(eventMap)(event): Promise\u003cargs[]\u003e`\n\nAllows to wait for an event without the need for callbacks.\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\nBasically, promise-style `subscribe` with the `once` flag.\\\nIt is a way to block execution flow until some event occurs.\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to wait events from.\n`event` | `PropertyKey` | An event name to wait for in a given event-map (can be a symbol too).\n\n**Returns**: `Promise\u003cArray\u003cunknown\u003e\u003e (contextual)` - a promise with array of parameters passed to the event.\n\n\u003cdetails\u003e\n\u003csummary\u003eSimple example\u003c/summary\u003e\n\n```ts\nimport { wait } from 'eventhoven';\n\nconst keydown = wait(keyboardEvents)('keydown');\n\n//... some time later in async context\n\n// Resolves upon the first 'keydown' event emit\n// Returns a tuple of arguments that would otherwise go to the handler\n// (excluding the context)\nconst [e] = await keydown;\nconsole.log(e);\n// =\u003e KeyboardEvent {}\n```\n\u003c/details\u003e\n\n---\n\n### `harmonicWait(eventMap)(event)(): Promise\u003cargs[]\u003e`\n\nSame as [`wait`](#waiteventmapevent-promiseargs), but returns a promise factory instead of a plain promise.\n\n\u003e Note, that the function is [curried](#currying), which means that it must be called partially\n\nUseful due to having the same signature as [`emit`](#emiteventmapeventargs-promisevoid), [`subscribe`](#subscribeeventmapeventhandlers---void) and [`unsubscribe`](#unsubscribeeventmapeventhandlers),\nwhich allows for an easier composition of waiters.\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to wait events from.\n`event` | `PropertyKey` | An event name to wait for in a given event-map (can be a symbol too).\n\n**Returns**: `() =\u003e Promise\u003cArray\u003cunknown\u003e\u003e (contextual)` - a promise factory with array of parameters passed to the event.\n\n\u003cdetails\u003e\n\u003csummary\u003eSimple example\u003c/summary\u003e\n\n```ts\nimport { harmonicWait } from 'eventhoven';\n\n// Function that initiates a waiter\nconst waitForKeydown = harmonicWait(keyboardEvents)('keydown');\n\n//... some time later in async context\n\n// Resolves upon the first 'keydown' event emit\n// since the call of the `waitForKeydown`\nconst [e] = await waitForKeydown();\nconsole.log(e);\n// =\u003e KeyboardEvent {}\n```\n\u003c/details\u003e\n\n---\n\n### `debug(isEnabled)`\n\nSets the debug mode.\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`enabled` | `boolean` | Whether to enable the debug mode or disable it.\n\n**Returns**: `void`\n\nWhen debug mode is enabled, all emits, subscribes and unsubscribes are logged to the console\nin a following format (default):\n\n```\nMM:SS.fff [{event-type} {event-name}]: {event-handler-or-params}\n```\n\nWhere:\n- `{event-type}` - [type of the event](https://github.com/Raiondesu/eventhoven/blob/master/src/meta-events.ts)\n- `{event-name}` - name of the event\n- `{event-handler-or-params}`\n  - the handler for the event (when subscribing or unsubscribing)\n  - params of the event (when emitting)\n\nExample:\n```ts\nimport { emit, debug } from 'eventhoven';\n\ndebug(true);\n\nemit(emojiEvents)('🎌')('🍣', 10);\n\n// logs:\n// 59:05.512 [EMIT \"🎌\"]: [🍣, 10]\n```\n\nIf an event does not exist in an event-map, the log will contain `(INVALID)` mark:\n```ts\nimport { emit, debug } from 'eventhoven';\n\ndebug(true);\n\n// event \"😎\" doesn't exist in the `emojiEvents` map\nemit(emojiEvents)('😎')('🍣', 10);\n\n// logs:\n// 59:05.512 [EMIT \"😎\" (INVALID)]: [🍣, 10]\n```\n\n#### Custom logging function\n\nIf you want coloring or some other features - pass a custom logging function to the `customDebug` factory.\nIt accepts a function of the same signature as any other [event handler](#event-handler):\n```ts\nimport { customDebug, TLogHandler, emit } from 'eventhoven';\n\n// Let's say we want warnings instead of logs\nconst customLogFunction: TLogHandler = (ctx, ...args) =\u003e console.warn('custom!', ctx.event, ...args);\n\nconst debug = customDebug(customLog);\n\n// Then use your custom debug function exactly like the default one:\ndebug(true);\n\nemit(emojiEvents)('🎌')('🍣', 10);\n\n// logs:\n// custom! EMIT [object Object] 🎌 [🍣, 10]\n```\n\n---\n\n### Collections\n\n`eventhoven` provides a way to group your event-managing needs using `collections`.\n\n**Parameters**:\n\nname | type | description\n-----|------|---------------\n`eventMap` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event-map to wait events from.\n\n**Return**: A map of event names to the action for that event name.\n\nCurrently available `collections` are:\n\nname | action | description\n-----|--------|------------------\n`emitCollection(eventMap)` | [`emit`](#emiteventmapeventargs-promisevoid) | Creates an object, where each property is a function that emits a prescribed event\n`subscribeCollection(eventMap)` | [`subscribe`](#subscribeeventmapeventhandlers---void) | Creates an object, where each property is a function that subscribes to a prescribed event\n`unsubscribeCollection(eventMap)` | [`unsubscribe`](#unsubscribeeventmapeventhandlers) | Creates an object, where each property is a function that unsubscribes from a prescribed event\n`eventCollection(eventMap)` | all of the above | Creates an object that contains all three collections in itself. Can be used to create a singleton that manages all events in an event-map.\n\n\u003cdetails\u003e\n\u003csummary\u003eSimple example\u003c/summary\u003e\n\n```ts\nimport {\n  eventMap,\n  emitCollection,\n  subscribeCollection,\n  unsubscribeCollection,\n  eventCollection\n} from 'eventhoven';\n\nconst myEvents = eventMap({\n  event1() {},\n  event2(ctx, arg1: number, arg2: string) {},\n  event3(ctx, arg: boolean) {},\n});\n\nconst emit = emitCollection(myEvents);\n\nemit.event1();// =\u003e Promise\u003cvoid\u003e\nemit.event2(12, 'some string');// =\u003e Promise\u003cvoid\u003e\nemit.event3(true);// =\u003e Promise\u003cvoid\u003e\n\nconst on = subscribeCollection(myEvents);\nconst handler = (ctx, arg: boolean) =\u003e console.log(arg);\n\non.event3(handler);\n\nconst off = unsubscribeCollection(myEvents);\n\noff.event3(handler);\n\nconst myCollection = eventCollection(myEvents);\n// The same as\n/*\nconst myCollection = {\n  emit: emitCollection(myEvents),\n  subscribe: subscribeCollection(myEvents),\n  unsubscribe: unsubscribeCollection(myEvents)\n};\n*/\n\nmyCollection.emit.event1();// =\u003e Promise\u003cvoid\u003e\nmyCollection.emit.event2(12, 'some string');// =\u003e Promise\u003cvoid\u003e\nmyCollection.emit.event3(true);// =\u003e Promise\u003cvoid\u003e\n\nmyCollection.subscribe.event3(handler);\nmyCollection.unsubscribe.event3(handler);\n```\n\u003c/details\u003e\n\n---\n\n### Meta-Events (Plugin API)\n\nIt's also possible to write custom plugins for `eventhoven` thanks to meta-events!\n\nMeta-events is a simple [`event-map`](#eventmapevents) with events for internal `eventhoven` actions, like [`emit`](#emiteventmapeventargs-promisevoid).\\\nOne can subscribe to these events to execute some actions or emit these events to emulate them for the `eventhoven`.\n\nThe simplest possible plugin is already written for you - the [`debug`](https://github.com/Raiondesu/eventhoven/blob/master/src/debug.ts) plugin.\\\nIt can be used as an example for writing your own plugins for `eventhoven`!\n\n\u003e Note:\n\u003e - A meta-event is always emitted **before** the event itself happens.\n\u003e - Any meta-event that returns a promise is going to be executed in parallel with the handlers for the event itself.\n\nCurrent list of all meta-events is as follows:\n\n name  | emitted when\n-------|------------------\n`EMIT` | Any event is emitted, except itself.\n`SUBSCRIBE` | Any event is subscribed to, except itself.\n`UNSUBSCRIBE` | Any event is unsubscribed from, except itself.\n\nThis list is also described as a [const enum EMetaEvents](https://github.com/Raiondesu/eventhoven/blob/master/src/meta-events.ts).\n\nSimple example:\n\n```ts\nimport { metaEvents, EMetaEvents, on } from 'eventhoven';\n\non(metaEvents)(EMetaEvents.EMIT)(\n  (ctx, eventMap, eventName, eventArgs) =\u003e console.log(\n    `This handler will be executed when ANY event is emitted, for example ${eventName}!`\n  )\n);\n```\n\n### Class API\n\nEven though `eventhoven` is functional in its nature, nothing prohibits to use it in Object-Oriented way.\\\nClass API can help with this:\n```ts\nimport { Eventhoven } from 'eventhoven';\n\nconst myEventManager = new Eventhoven({\n  myEvent1(ctx, arg: string) {\n    console.log('yay, my oop event!', arg);\n  }\n});\n\nmyEventManager.emit('myEvent1', 'first emit');\n// =\u003e yay, my oop event! first emit\n\nmyEventManager.on('myEvent1', (ctx, arg) =\u003e {\n  console.log('another handler!', arg);\n});\n\nmyEventManager.emit('myEvent1', 'second emit');\n// =\u003e yay, my oop event! second emit\n// =\u003e another handler! second emit\n\nconsole.log(Object.keys(myEventManager.map));\n// =\u003e ['myEvent1']\n```\n\nBasically, the `Eventhoven` class is a wrapper of [`eventCollection`](#collections) with the following properties:\n\nvisibility | name | type | description\n------|------|---------|-----------------\n`public` | `map` | [`TEventMap`](https://github.com/Raiondesu/eventhoven/blob/master/src/types.d.ts#L15) | An event map that contains all of the instance events\n`public` | `emit` | `(event, ...args) =\u003e Promise\u003cany[]\u003e` | An event emitter function. Basically, an uncurried version of [`emit`](#emiteventmapeventargs-promisevoid)\n`public` | `on` | `(event, ...handlers) =\u003e () =\u003e void` | An event subscriber function. Basically, an uncurried version of [`subscribe`](#subscribeeventmapeventhandlers---void).\n`public` | `off` | `(event, ...handlers) =\u003e void` | An event unsubscriber function. Basically, an uncurried version of [`unsubscribe`](#unsubscribeeventmapeventhandlers---void).\n`static` | `emit` | [`emit`](#emiteventmapeventargs-promisevoid) | The `emit` function, with all of its args flattened\n`static` | `on` | [`subscribe`](#subscribeeventmapeventhandlers---void) | The `subscribe` function, with all of its args flattened\n`static` | `off` | [`unsubscribe`](#unsubscribeeventmapeventhandlers---void) | The `unsubscribe` function, with all of its args flattened\n\nIt's also possible to utilize static methods of the `Eventhovent` class in order to use a shorter version of main API:\n```ts\nimport { Eventhoven } from 'eventhoven';\n\nconst myEventManager = new Eventhoven({\n  myEvent1(ctx, arg: string) {\n    console.log('yay, my oop event!', arg);\n  }\n});\n\nconst myEvents = eventMap({\n  someEvent(ctx, arg: boolean) {\n    console.log('functional event -', arg);\n  },\n});\n\n// All three methods accept an event-map or an Eventhoven instance as a first argument\n\n// Accepting a class\nEventhoven.on(myEventManager, 'myEvent1', (ctx, arg) =\u003e {\n  console.log('another handler!', arg);\n});\nEventhoven.emit(myEventManager, 'myEvent1', 'static emit');\n// =\u003e yay, my oop event! static emit\n// =\u003e another handler! static emit\n\n// Accepting an event-map\nEventhoven.on(myEvents, 'someEvent', (ctx, arg) =\u003e {\n  console.log('another handler:', arg);\n});\nEventhoven.emit(myEvents, 'someEvent', true);\n// =\u003e functional event - true\n// =\u003e another handler: true\n```\n\n---\n\n## Contribute\n\nFirst, fork the repo and clone it:\n```\ngit clone https://github.com/%your-github-username%/eventhoven.git\n```\n\nThen:\n```\nnpm install\n```\n\nThen:\n```\nnpm run dev\n```\n\nThen introduce changes and propose a PR!\\\nI'll be happy to review it!\n\n---\n\nSomething's missing or found a bug?\\\nFeel free to [create an issue](https://github.com/Raiondesu/eventhoven/issues/new)! 😉\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraiondesu%2Feventhoven","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraiondesu%2Feventhoven","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraiondesu%2Feventhoven/lists"}