{"id":17533002,"url":"https://github.com/dab0mb/event-ops","last_synced_at":"2026-06-09T17:31:37.716Z","repository":{"id":192318311,"uuid":"686455051","full_name":"DAB0mB/event-ops","owner":"DAB0mB","description":"Event Ops (Event Operations) is a library that provides very simple and minimalistic utils to create event-driven programs.","archived":false,"fork":false,"pushed_at":"2024-03-24T15:22:26.000Z","size":51,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-02T16:13:42.085Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/DAB0mB.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}},"created_at":"2023-09-02T21:10:16.000Z","updated_at":"2023-09-29T13:58:08.000Z","dependencies_parsed_at":"2024-11-13T16:46:37.024Z","dependency_job_id":"9c6b37b3-7d40-41ee-ba68-c626458b02c5","html_url":"https://github.com/DAB0mB/event-ops","commit_stats":{"total_commits":38,"total_committers":1,"mean_commits":38.0,"dds":0.0,"last_synced_commit":"0f8d6f149be31f92cbb446aeeaee84bfe3d0e673"},"previous_names":["dab0mb/event-ops"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/DAB0mB/event-ops","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fevent-ops","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fevent-ops/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fevent-ops/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fevent-ops/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DAB0mB","download_url":"https://codeload.github.com/DAB0mB/event-ops/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fevent-ops/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34118751,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-10-20T18:11:27.858Z","updated_at":"2026-06-09T17:31:37.696Z","avatar_url":"https://github.com/DAB0mB.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Event Ops\n\nEvent Ops (Event Operations) is a library that provides very simple and minimalistic utils to create event-driven programs. Event Ops was designed to work in any JavaScript environment and doesn't have any dependencies. With that said, it does provide additional helpers to work with React. Here's an example of how one can use Event Ops to write an event-driven controller:\n\n```js\nimport { Memo, State } from 'event-ops';\n\nexport class Calculator {\n  readonly num1 = new State(100);\n  readonly num2 = new State(200);\n\n  readonly sum = new Memo(() =\u003e {\n    return num1.value + num2.value;\n  }, [this.num1, this.num2]);\n}\n```\n\nTo use `sumState` in a React component, you can import some helpers from `event-ops/react`:\n\n```jsx\nimport { useValue } from 'event-ops/react';\n\nexport function SumLabel() {\n  const calculator = useCalculator();\n  const sum = useValue(calculator.sumState);\n\n  return (\n    \u003cdiv\u003e\n      sum: {sum}\n    \u003c/div\u003e\n  );\n}\n```\n\nFor a robust example, I recommend you to look at the [parallax-chess](https://dab0mb.github.io/parallax-chess/) project.\n\n## Installation\n\nEvent Ops can be installed via NPM:\n\n```\nnpm install event-ops\n```\n\n## API\n\n- core\n  - [Event](#coreevent)\n  - [EmitterEvent](#coreemitterevent)\n  - [State](#corestate)\n  - [Effect](#coreeffect)\n  - [Memo](#corememo)\n  - [Task](#coretask)\n  - [Value](#corevalue)\n  - [LazyValue](#corelazyvalue)\n- react\n  - [useListener](#reactuselistener)\n  - [useEmitterListener](#reactuseemitterlistener)\n  - [useValue](#reactusevalue)\n\n### core/Event\n\nEvent is the most basic object in Event Ops. With an Event, you can either listen to a change, or emit a change.\n\n```ts\nimport { Event } from 'event-ops';\n\nconst clickEvent = new Event\u003c[number, number]\u003e();\n\nconst clearClickListener = clickEvent.listen(([x, y]) =\u003e {\n  console.log(`Click coord: ${x},${y}`);\n});\n\nclickEvent.emit([100, 150]);\n\n// ... some time later ...\n\nclearClickListener();\n\n// OR\n\nclickEvent.unlisten(clickListener);\n```\n\n### core/EmitterEvent\n\nIn case you have an existing emitter, let's say a web socket, you can construct an event that's bound to a key using the EmitterEvent object. This is particularly useful if the instance of the emitter can vary, but the event key and value remain the same, e.g. if you have multiple web socket clients that subscribe to the same event.\n\n```ts\nimport { EmitterEvent } from 'event-ops';\n\nconst pingEvent = new EmitterEvent('ping');\nconst pongEvent = new EmitterEvent\u003cnumber\u003e('pong');\n\nwsServer.on('connection', (wsClient) =\u003e {\n  pingEvent.listen(wsClient, () =\u003e {\n    const ts = Date.now();\n    pongEvent.emit(wsClient, ts);\n  });\n});\n```\n\n### core/State\n\nA State is an object that can hold a persistant value. Similarly to an Event, you can listen to changes, except they're emitted automatically based on the State value.\n\n```ts\nimport { State } from 'event-ops';\n\nconst colorState = new State('red');\n\nconst clearColorListener = colorState.listen((color) =\u003e {\n  console.log(`Color changed to ${color}`)\n});\n\ncolorState.value = 'green';\n\n// ... some time later ...\n\nclearColorListener();\n\n// OR\n\ncolorState.unlisten(colorListener);\n```\n\nYou can also force a State change using a Value object:\n\n```ts\nimport { State, Value } from 'event-ops';\n\nconst colorState = new State('red');\n\ncolorState.listen(() =\u003e {\n  console.log('Color is still red')\n});\n\ncolorState.value = new Value('red');\n```\n\n### core/Effect\n\nWith an Effect, you can listen to multiple Events using a unified callback.\n\n\n```ts\nimport { State, Effect } from 'event-ops';\n\nconst num1State = new State(0);\nconst num2State = new State(0);\nconst sumEffect = new Effect([num1State, num2State]);\n\nconst clearSumListener = sumEffect.listen(() =\u003e {\n  console.log(`New sum is ${num1State.value + num2State.value}`);\n});\n\nnum1State.value = 100;\nnum2State.value = 200;\n\n// ... some time later ...\n\nclearSumListener();\n\n// OR\n\nsumEffect.unlisten(sumListener);\n```\n\n### core/Memo\n\nMemo provides an alternative to a LazyValue assignment in an Effect listener.\n\n```ts\nimport { State, Memo } from 'event-ops';\n\nconst num1 = new State(0);\nconst num2 = new State(0);\n\nconst sum = new Memo(() =\u003e {\n  return num1.value + num2.value;\n}, [num1, num2]);\n\n// Which is equivalent to\n\nconst num1 = new State(0);\nconst num2 = new State(0);\nconst sum = new State(0);\n\nconst effect = new Effect(() =\u003e {\n  sum.value = new LazyValue(() =\u003e num1.value + num2.value);\n}, [num1, num2]);\n\neffect.emit();\n```\n\nYou can stop listening to the underlying Memo Effect by calling the `clearEffectListener()` method.\n\n```ts\nconst sum = new Memo(() =\u003e {\n  return num1.value = num2.value;\n}, [num1, num2]);\n\n// ... some time later ...\n\nsum.clearEffectListener();\n```\n\n### core/Task\n\nAll listeners are scheduled to run using a Task system to prevent redundant computations. To tap into the Task system, you can use the Task object.\n\n```ts\nimport { State, Effect, Task } from 'event-ops';\n\nconst num1State = new State(0);\nconst num2State = new State(0);\n\nnew Effect([num1State, num2State]).listen(() =\u003e {\n  console.log(`New sum is ${num1State.value + num2State.value}`);\n});\n\n// Here we emit only a single change by scheduling a Task\n\nnew Task(() =\u003e {\n  num1State.value = 100;\n  num2State.value = 200;\n}).schedule();\n```\n\nIf some time later you would like to clear a scheduled Task, you can do so with the `unschedule()` method. This would only be relevant if the Task has not been run yet.\n\n```ts\nconst task = new Task(() =\u003e {\n  // perform task\n});\n\nconst clearTask = task.schedule();\n\n// ... some time later ...\n\nclearTask();\n\n// OR\n\ntask.unschedule();\n```\n\nYou can also use the `scheduleTask()` util to create a task and schedule it immediately.\n\n```ts\nimport { scheduleTask } from 'event-ops';\n\nconst clearTask = scheduleTask(() =\u003e {\n  // perform task\n});\n```\n\n### core/Value\n\nA Value object is useful if you would like to force-emit a State change, even if the underlying object remains the same:\n\n```ts\nimport { Value, State } from 'event-ops';\n\nconst state = new State(1);\n\nstate.listen(() =\u003e {\n  console.log('Value is still 1');\n});\n\nstate.value = new Value(1);\n```\n\n### core/LazyValue\n\nSome listeners may include heavy computations, in which case you can use a LazyValue object to compute a value only once at runtime. If you reset the State value with a new LazyValue object, the cache of the underlying value would be invalidated and the computation would rerun the next time the LazyValue is consumed:\n\n```ts\nimport { LazyValue, State } from 'event-ops';\n\nconst state = new State\u003cunknown[]\u003e([]);\nconst veryFrequentEvent = new Event\u003cunknown[]\u003e();\n\nveryFrequentEvent.listen((value) =\u003e {\n  state.value = new LazyValue(() =\u003e veryHeavyTransform(value));\n});\n\n// ... some time later ...\n\n// veryHeavyTransform() should only be called once\nconsole.log(state.value);\n```\n\n### react/useListener\n\nWith `useListener()` you can add a listener to an Event and bind it to the lifetime of the component, meaning that once the component is unmounted, the listener will be automatically cleared.\n\n```tsx\nimport { Event } from 'event-ops';\nimport { useListener } from 'event-ops/react';\n\nconst clickEvent = new Event\u003c[number, number]\u003e();\n\nfunction Component() {\n  useListener(clickEvent, ([x, y]) =\u003e {\n    console.log(`Mouse coord: ${x},${y}`);\n  });\n\n  return null;\n}\n\n// ... after render ...\n\nclickEvent.emit([100, 150]);\n```\n\nFor your convenience, you can use the `useUpdate()` hook to trigger updates once an Event change has been emitted.\n\n```tsx\nimport { Event } from 'event-ops';\nimport { useListener, useUpdate } from 'event-ops/react';\n\nconst clickEvent = new Event\u003c[number, number]\u003e();\n\nfunction Component() {\n  const update = useUpdate();\n  useListener(clickEvent, update);\n\n  return null;\n}\n```\n\nThere's also a `voidEvent` util that can be used as a default to an optional Event prop. This can be useful in React for conditional `useListener()` calls.\n\n```tsx\nimport { Event, voidEvent } from 'event-ops';\nimport { useListener, useUpdate } from 'event-ops/react';\n\ntype Props = {\n  event?: Event,\n};\n\nfunction Component(props: Props) {\n  const update = useUpdate();\n  useListener(props.event ?? voidEvent, update);\n\n  return null;\n}\n```\n\n### react/useEmitterListener\n\nThe `useEmitterListener()` hook is similar to `useListener()`, only it's based on [EmitterEvent](#coreemitterevent).\n\n```tsx\nimport { useEmitterListener } from 'event-ops/react';\n\nconst pingEvent = new EmitterEvent('ping');\nconst pongEvent = new EmitterEvent\u003cnumber\u003e('pong');\n\nfunction Component() {\n  const socket = useSocket();\n  const [ts, setTs] = useState(0);\n\n  useEmitterListener(pongEvent, socket, (ts) =\u003e {\n    setTs(ts);\n  });\n\n  return (\n    \u003cdiv\u003e\n      timestamp: {ts}\n    \u003c/div\u003e\n  );\n}\n```\n\n### react/useValue\n\nWith `useValue()` you can use the value of a State; the component will automatically be updated with every value change.\n\n```tsx\nimport { State } from 'event-ops';\nimport { useValue } from 'event-ops/react';\n\nconst colorState = new State('red');\n\nfunction Component() {\n  const color = useValue(colorState);\n\n  return (\n    \u003cdiv\u003e\n      color: {color}\n    \u003c/div\u003e\n  );\n}\n\n// ... after render ...\n\ncolorState.value = 'green';\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdab0mb%2Fevent-ops","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdab0mb%2Fevent-ops","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdab0mb%2Fevent-ops/lists"}