{"id":16371213,"url":"https://github.com/unional/fsa-emitter","last_synced_at":"2025-03-16T15:33:13.708Z","repository":{"id":26347855,"uuid":"107716978","full_name":"unional/fsa-emitter","owner":"unional","description":"EventEmitter in FSA style","archived":false,"fork":false,"pushed_at":"2024-07-31T18:17:56.000Z","size":917,"stargazers_count":1,"open_issues_count":18,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-18T06:22:59.560Z","etag":null,"topics":["eventemitter","flux-standard-action","fsa"],"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/unional.png","metadata":{"files":{"readme":"README.md","changelog":null,"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},"funding":{"ko_fi":"unional"}},"created_at":"2017-10-20T19:14:54.000Z","updated_at":"2022-12-14T22:45:24.000Z","dependencies_parsed_at":"2023-02-17T01:15:46.651Z","dependency_job_id":"ee29991d-36db-41b6-bbd3-8a6575991cf4","html_url":"https://github.com/unional/fsa-emitter","commit_stats":{"total_commits":143,"total_committers":4,"mean_commits":35.75,"dds":0.4755244755244755,"last_synced_commit":"49d6dc0a04c92309bb90f82589e41684ec58303c"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unional%2Ffsa-emitter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unional%2Ffsa-emitter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unional%2Ffsa-emitter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unional%2Ffsa-emitter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/unional","download_url":"https://codeload.github.com/unional/fsa-emitter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221665593,"owners_count":16860287,"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":["eventemitter","flux-standard-action","fsa"],"created_at":"2024-10-11T03:07:20.940Z","updated_at":"2024-10-27T10:51:44.750Z","avatar_url":"https://github.com/unional.png","language":"TypeScript","funding_links":["https://ko-fi.com/unional"],"categories":[],"sub_categories":[],"readme":"# FSA Emitter\n\n[![NPM version][npm-image]][npm-url]\n[![NPM downloads][downloads-image]][downloads-url]\n\n[![GitHub NodeJS][github-nodejs]][github-action-url]\n[![Codecov][codecov-image]][codecov-url]\n[![Semantic Release][semantic-release-image]][semantic-release-url]\n[![Visual Studio Code][vscode-image]][vscode-url]\n\n`EventEmitter` in FSA style.\n\nWhen dealing with event, I find myself need to remember the name of the event,\nand the parameters each event has.\n\nThe person who defines the events and the person who consumes the events can be two different people.\n\nThis implicit knowledge coupling relies on communication and documentation that are prone to error,\nand are not convenient.\n\nThis library addresses this issue by emitting and consuming events using a standard format,\nand provides IDE type support, so they can be consumed easily.\n\n[@unional/events-plus] is the spiritual successor of this package.\nIt works with multiple event emitters with similar API.\nFeel free to check it out.\n\n## Best practice\n\nOne of the benefits of using events is decoupling.\nHere is one way to organize your code:\n\n```ts\n// app.ts\nimport { doWork } from './doWork'\n\nconst emitter = new Emitter()\n\ndoWork({ emitter })\n\n// doWork.ts\nimport { createEvent, Emitter } from 'fsa-emitter'\n\nexport const count = createEvent\u003cnumber\u003e('count')\n\nexport function doWork({ emitter }) {\n  emitter.emit(count(1, undefined))\n}\n\n// in UI\nimport { count } from './doWork'\n\nemitter.on(count, payload =\u003e {\n  console.log('payload is typed and is a number: ', payload)\n})\n```\n\n## Installation\n\n```sh\nnpm install fsa-emitter\n```\n\n## Emitter\n\n`Emitter` uses [`FluxStandardAction`](https://github.com/acdlite/flux-standard-action) as the standard event format.\n\nAnother key difference between `Emitter` and NodeJS `EventEmitter` is that `Emitter` will capture any error thrown in listener and send it to `console.error()`.\nThis avoids any listener code throws error and break the event emitting logic.\n\nTo create event, use one of the helper methods below:\n\n```ts\nimport {\n  createEvent,\n  createScopedCreateEvent,\n  createEventAction,\n  createScopedCreateEventAction,\n  Emitter\n} from 'fsa-emitter'\n\nconst count = createEvent\u003cnumber\u003e('count')\n\n// create scoped event\nconst createMyModuleEvent = createScopedCreateEvent('myModule')\n// `scopedCount` will emit FSA with `type: 'myModule/count'`\nconst scopedCount = createMyModuleEvent\u003cnumber\u003e('count')\n\nconst add = createAction\u003c\n  /* input */ { a: number, b: number },\n  /* payload */ number\u003e('add', ({ a, b }) =\u003e emit =\u003e emit(a + b))\n\n// create scoped action\nconst createMyModuleAction = createScopedCreateEventAction('myModule')\n// `scopedCountAction` will emit FSA with `type: 'myModule/add'`\nconst scopedAdd = createMyModuleAction\u003c\n  /* input */ { a: number, b: number },\n  /* payload */ number\u003e('add', ({ a, b }) =\u003e emit =\u003e emit(a + b))\n\nconst emitter = new Emitter()\n\nemitter.emit(count(1, undefined))\nemitter.emit(scopedCount(2, undefined))\nadd(emitter, { a: 1, b: 2 }, undefined)\nscopedAdd(emitter, { a: 1, b: 2 }, undefined)\n```\n\nNote that due to \u003chttps://github.com/Microsoft/TypeScript/issues/12400\u003e,\nyou need to supply `undefined` at where they expect `meta`.\n\nTo consume the event, use one of the following methods:\n\n```ts\nimport { createEvent, Emitter } from 'fsa-emitter'\n\nconst count = createEvent\u003cnumber\u003e('count')\nconst emitter = new Emitter()\n\nemitter.addListener(count, value =\u003e console.info(value))\nemitter.on(count, value =\u003e console.info(value))\n\n// Will only listen to the event once\nemitter.once(count, value =\u003e console.info(value))\n\n// line up in queue to listen for the event once\nemitter.queue(count, value =\u003e console.info(value))\n\n// listen to all events\nemitter.onAny(fsa =\u003e console.info(fsa.type, fsa.payload))\n\n// listen to any event that does not have a listener\nemitter.onMissed(fsa =\u003e console.info(fsa.type, fsa.payload))\n```\n\n## Command\n\n`Command` is a simple command pattern that provides a simple way to manage dependencies.\n\n```ts\nimport { Command, createEvent, Emitter } from 'fsa-emitter'\n\nconst count = createEvent\u003c{ n: number }\u003e('count')\n\nclass CountCommand extends Command {\n  run(n: number) {\n    while (n) {\n      this.emitter.emit(count({ n }, undefined))\n      n--\n    }\n  }\n}\n\nconst emitter = new Emitter()\nconst cmd = new CountCommand({ emitter })\ncmd(10)\n```\n\n```ts\nimport { Command, createEvent, Emitter } from 'fsa-emitter'\n\nconst changeHeight = createEvent\u003c{ height: number }\u003e('changeHeight')\nconst landed = createEvent('landed')\n\nclass FlyCommand extends Command\u003c{ fuel: number }\u003e {\n  fuel: number\n  height = 0\n  run() {\n    while (this.fuel \u003e 0) {\n      this.height += 100\n      this.fuel--\n      this.emitter.emit(changeHeight({ height: this.height }, undefined))\n    }\n    const gliding = setInterval(() =\u003e {\n      this.height -= 50\n      this.emitter.emit(changeHeight({ height: this.height }, undefined))\n      if (this.height \u003c= 0) {\n        this.emitter.emit(landed(undefined, undefined))\n        clearInterval(gliding)\n      }\n    }, 100)\n  }\n}\n\n// `fuel` is in the constructor context\nconst fly = new FlyCommand({ emitter: new Emitter(), fuel: 10 })\nfly.run()\n```\n\n## `TestEmitter`\n\n`TestEmitter` can be used as a drop-in replacement as `Emitter` during test.\n\nThe difference between `TestEmitter` and `Emitter` is that `TestEmitter` will not capture errors thrown in listeners.\nThis make it more suitable to use during testing so that you can detect any error thrown during the test.\n\nAlso, it provides some additional methods:\n\n### `listenerCalled(event: TypedEvent | string): boolean`\n\n```ts\nimport { TestEmitter, createEvent } from 'fsa-emitter'\n\nconst emitter = new TestEmitter()\nconst count = createEvent\u003cnumber\u003e('count')\nemitter.on(count, () =\u003e ({}))\nt.false(emitter.listenerCalled(count))\n\nemitter.emit(count(1, undefined))\nt.true(emitter.listenerCalled(count))\nt.true(emitter.listenerCalled(count.type))\n```\n\n### `allListenersCalled(): boolean`\n\n```ts\nimport { TestEmitter, createEvent } from 'fsa-emitter'\n\nconst emitter = new TestEmitter()\nconst count = createEvent\u003cnumber\u003e('count')\nconst bound = createEvent\u003cnumber\u003e('bound')\n\nemitter.on(count, () =\u003e ({}))\nemitter.on(bound, () =\u003e ({}))\n\nemitter.emit(count(1, undefined))\n\nt.false(emitter.allListenersCalled())\n\nemitter.emit(bound(1, undefined))\n\nt.true(emitter.allListenersCalled())\n```\n\n### `listenedTo(events: (TypedEvent | string)[] | { [k]: TypedEvent })`\n\n```ts\nimport { TestEmitter, createEvent } from 'fsa-emitter'\n\nconst emitter = new TestEmitter()\nconst count = createEvent\u003cnumber\u003e('count')\nconst bound = createEvent\u003cnumber\u003e('bound')\n\nemitter.on(count, () =\u003e ({}))\n\nemitter.listenedTo([count]) // true\nemitter.listenedTo({ count }) // true\nemitter.listenedTo(['count']) // true\n\nemitter.listenedTo([ bound ]) // false\nemitter.listenedTo({ bound }) // false\nemitter.listenedTo([ 'bound' ]) // false\n```\n\n## `setupCommandTest(Command, context?)`\n\n`setupCommandTest()` is a simple helper to create a `TestEmitter` for the command to run with.\nThe same completion support is available as in the `Command` constructor.\n\n```ts\nimport { Command, createEvent, setupCommandTest } from 'fsa-emitter'\n\nconst count = createEvent\u003c{ n: number }\u003e('count')\n\nclass CountCommand extends Command {\n  ...\n}\n\nconst { command, emitter } = setupCommandTest(CountCommand)\n\nclass FlyCommand extends Command\u003c{ fuel: number }\u003e {\n  ...\n}\n\n// completion is available for `fuel`\nconst { command, emitter } = setupCommandTest(FlyCommand, { fuel: 10 })\n```\n\n## Contribute\n\n```sh\n# after fork and clone\nnpm install\n\n# begin making changes\ngit checkout -b \u003cbranch\u003e\nnpm run watch\n\n# after making change(s)\ngit commit -m \"\u003ccommit message\u003e\"\ngit push\n\n# create PR\n```\n\n[@unional/events-plus]: https://github.com/unional/events-plus\n[codecov-image]: https://codecov.io/gh/unional/fsa-emitter/branch/master/graph/badge.svg\n[codecov-url]: https://codecov.io/gh/unional/fsa-emitter\n[downloads-image]: https://img.shields.io/npm/dm/fsa-emitter.svg?style=flat\n[downloads-url]: https://npmjs.org/package/fsa-emitter\n[github-nodejs]: https://github.com/unional/fsa-emitter/workflows/nodejs/badge.svg\n[github-action-url]: https://github.com/unional/fsa-emitter/actions\n[npm-image]: https://img.shields.io/npm/v/fsa-emitter.svg?style=flat\n[npm-url]: https://npmjs.org/package/fsa-emitter\n[semantic-release-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg\n[semantic-release-url]: https://github.com/semantic-release/semantic-release\n[vscode-image]: https://img.shields.io/badge/vscode-ready-green.svg\n[vscode-url]: https://code.visualstudio.com/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funional%2Ffsa-emitter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funional%2Ffsa-emitter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funional%2Ffsa-emitter/lists"}