{"id":14981292,"url":"https://github.com/lume/eventful","last_synced_at":"2026-01-18T03:19:46.051Z","repository":{"id":65693482,"uuid":"279220860","full_name":"lume/eventful","owner":"lume","description":"A class (or mixin) whose instances emit events that external code can subscribe to.","archived":false,"fork":false,"pushed_at":"2024-10-02T21:11:06.000Z","size":56,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-10-27T12:34:56.383Z","etag":null,"topics":["3d","3d-graphics","css","custom-elements","emit-events","event-driven","event-emitter","eventful","game-dev","game-engine","graphics","html","lume","threejs","web-components","webgl"],"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/lume.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,"zenodo":null}},"created_at":"2020-07-13T05:39:22.000Z","updated_at":"2024-10-02T21:11:10.000Z","dependencies_parsed_at":"2023-12-16T14:03:28.719Z","dependency_job_id":"765f59a5-734d-4372-befe-68e21d2cb88e","html_url":"https://github.com/lume/eventful","commit_stats":{"total_commits":53,"total_committers":2,"mean_commits":26.5,"dds":"0.018867924528301883","last_synced_commit":"57918d51b5c98e6a733a369ee645180b64d1f54a"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/lume/eventful","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Feventful","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Feventful/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Feventful/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Feventful/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lume","download_url":"https://codeload.github.com/lume/eventful/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Feventful/sbom","scorecard":{"id":605072,"data":{"date":"2025-08-11","repo":{"name":"github.com/lume/eventful","commit":"77cc182549f43a739d02d6760479970a5804642e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/25 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/lume/eventful/tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/lume/eventful/tests.yml/main?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/tests.yml:23","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-21T01:23:48.862Z","repository_id":65693482,"created_at":"2025-08-21T01:23:48.863Z","updated_at":"2025-08-21T01:23:48.863Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28528036,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"online","status_checked_at":"2026-01-18T02:00:07.578Z","response_time":98,"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":["3d","3d-graphics","css","custom-elements","emit-events","event-driven","event-emitter","eventful","game-dev","game-engine","graphics","html","lume","threejs","web-components","webgl"],"created_at":"2024-09-24T14:03:16.668Z","updated_at":"2026-01-18T03:19:46.008Z","avatar_url":"https://github.com/lume.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @lume/eventful\n\nEmit and subscribe to events.\n\nThis is a standard event listener pattern, similar to `EventEmitter` in Node.js\nwhere objects have a `.on('event-name')` method, or `EventTarget` in the web\nwhere HTML elements have a `.addEventListener('event-name')` method, for listening to events from such objects.\n\nAdditionally, the `Eventful` class provided by this package is a _mixin_. It can\nbe added onto _any_ existing class without modifying its class hierarchy. More\non that below.\n\n#### `npm install @lume/eventful`\n\n## Usage\n\n### Working with events\n\n`@lume/eventful` provides a class (or _mixin_) called `Eventful` that your\nobjects can extend from so that they can emit events and other code can\nsubscribe to those events.\n\n```js\nimport {Eventful} from '@lume/eventful'\n\n// Here we define a Dog class that extends from `Eventful()` so that it can emit\n// a \"hungry\" event any time its `makeHungry()` method is called.\nclass Dog extends Eventful() {\n\tmakeHungry() {\n\t\t// Emit a \"hungry\" event when this method is called.\n\t\tthis.emit('hungry')\n\t}\n\n\tfeed(food) {\n\t\t// ... dog eats food (use imagination here) ...\n\t}\n}\n\nconst dog = new Dog()\n\nfunction handleHungryDog() {\n\tdog.feed('chow')\n}\n\n// When the dog emits the \"hungry\" event, let's feed it. We pass in the\n// handleHungryDog function to be called any time the dog emits the \"hungry\"\n// event.\ndog.on('hungry', handleHungryDog)\n\n// This triggers the \"hungry\" event, which causes the callback passed to\n// `dog.on('hungry')` to fire.\ndog.makeHungry()\n\n// We can stop listening for \"hungry\" events by using `.off()` to remove our\n// event handler, the handleHungryDog function.\ndog.off('hungry', handleHungryDog)\n\n// This time we trigger the \"hungry\" event again, but our handleHungryDog\n// function will not run again (poor dog, don't do this to your dog in real\n// life).\ndog.makeHungry()\n```\n\nThat's basically it. See also [`Eventful.test.ts`](./src/Eventful.test.ts) to\nget more of an idea.\n\n### Using `Eventful` as a mixin\n\nDid you notice the `()` in `extends Eventful()` above? That's because `Eventful`\nis a _mixin_.\n\nBy default, it returns an `Eventful` class that extends from `Object`. Pass in a\ncustom base class to extend from anything you want.\n\nAs an example, imagine you already have a class defined somewhere that extends\nfrom an existing base class.\n\n```js\nimport Animal from 'somewhere'\n\n// Imagine we have this existing Cat class.\nclass Cat extends Animal {\n\tisFeelingCurious = false\n\n\tgoExploring() {\n\t\tif (this.isFeelingCurious) {\n\t\t\t// ... cat goes exploring (use imagination here) ...\n\t\t} else {\n\t\t\t// ... cat sleeps on your fluffy pillow ...\n\t\t}\n\t}\n}\n\nconst cat = new Cat()\ncat.isFeelingCurious = true\ncat.goExploring()\n```\n\nLet's say we're updating our app, and we wish to add `Eventful` functionality to this class. Without a mixin, our only option would be to find the base-most class of the Cat (whether that's `Animal`, or something even lower like `Organism`), and we'd have to make that base-most class extend from `Eventful` (if it weren't a mixin).\n\nBut `Eventful` is a mixin! We can simply add it to our class, without touching\nanything else! Like so:\n\n```js\nimport {Eventful} from '@lume/eventful'\nimport Animal from 'somewhere'\n\n// Now our class extends from both Eventful and Animal. Convenient! No\n// base-class refactoring needed!\nclass Cat extends Eventful(Animal) {\n\t#isFeelingCurious = false // Let's make this private now.\n\n\tgoExploring() {\n\t\tif (this.#isFeelingCurious) {\n\t\t\t// ... cat goes exploring (use imagination here) ...\n\t\t} else {\n\t\t\t// ... cat sleeps on your fluffy pillow ...\n\t\t}\n\t}\n\n\t// Let us now add a public getter/setter that sets our state, but also emits an event:\n\tset isFeelingCurious(val) {\n\t\tthis.#isFeelingCurious = val\n\t\tthis.emit('curiosity-changed') // We have the ability to emit events now.\n\t}\n\tget isFeelingCurious() {\n\t\treturn this.#isFeelingCurious\n\t}\n}\n\nconst cat = new Cat()\ncat.isFeelingCurious = true // Setting this emits the \"curiosity-changed\" event.\n\n// Define a function to run when `isFeelingCurious` changes (i.e. when a \"curiosity-changed\" event is emitted)\ncat.on('curiosity-changed', () =\u003e {\n\tcat.goExploring()\n})\n\ncat.isFeelingCurious = true // Causes the event to be emitted, which makes the cat go exploring.\n```\n\n## Why?\n\nWhy do you want to make your `Cat` class a little more complicated to do the same thing?\n\nThe answer is, it isn't about _you_! Events are useful for letting _other\npeople_ (or other parts of your program, written by you or other people) to\nreact to changes (events).\n\nThe above `Cat` class is more practical when it another piece of code has\nmultiple instances of it, and needs to react in differing way depending on which\nevents happen.\n\nFor example, let's imagine that we give a `Cat` instance to some other piece of\ncode by exporting it for sake of example:\n\n```js\nclass Cat extends Eventful(Animal) {\n\t// ... same as before ...\n}\n\nexport const cat = new Cat()\n\n// At random points in time, make the cat randomly curious or not:\nsetTimeout(function updateCuriousityOverTime() {\n\tcat.isFeelingCurious = Math.random() \u003e 0.5\n\n\t// Keep updating every random interval of up to 1 second:\n\tsetTimeout(updateCuriousityOverTime, Math.random() * 1000)\n})\n```\n\nNow, some other code can decide, _independently of the code that defined the\n`cat`_, what to do whenever the cat's curiosity has changed, and the original\ncode that defined the cat is not responsible for reacting to the change:\n\n```js\nimport {cat} from './cat.js'\n\n// Maybe we're making a game (use imagination here). Let's react to cat curiosity changes:\ncat.on('curiosity-changed', () =\u003e {\n\tif (cat.isFeelingCurious) {\n\t\t// Perhaps make a set of `Rat` and `Mouse` instances start to frantically go hide.\n\t} else {\n\t\t// Make the `Rat` and `Mouse` instances relax, the cat has gone to lay on a pillow.\n\t}\n})\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flume%2Feventful","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flume%2Feventful","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flume%2Feventful/lists"}