{"id":19281261,"url":"https://github.com/zouloux/signal","last_synced_at":"2025-10-24T12:26:51.369Z","repository":{"id":65525034,"uuid":"504445421","full_name":"zouloux/signal","owner":"zouloux","description":"Thin and simple entity-based event system","archived":false,"fork":false,"pushed_at":"2023-07-30T05:46:25.000Z","size":254,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-12T11:07:49.817Z","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/zouloux.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":"2022-06-17T08:01:32.000Z","updated_at":"2024-06-27T20:36:26.000Z","dependencies_parsed_at":"2024-06-21T20:24:43.445Z","dependency_job_id":"1d6f3194-319b-498d-8f18-b9605b053118","html_url":"https://github.com/zouloux/signal","commit_stats":{"total_commits":26,"total_committers":2,"mean_commits":13.0,"dds":"0.038461538461538436","last_synced_commit":"2c2f412ffaadb7b103253c59741fffbbd38e52b0"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zouloux%2Fsignal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zouloux%2Fsignal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zouloux%2Fsignal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zouloux%2Fsignal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zouloux","download_url":"https://codeload.github.com/zouloux/signal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250161950,"owners_count":21385013,"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-11-09T21:22:10.205Z","updated_at":"2025-10-24T12:26:46.339Z","avatar_url":"https://github.com/zouloux.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Signal\n\n[//]: # (TODO : Image)\n\nThin and simple functional event system with strong typing.\nSignal size is __![less than 300b](./bits/signal.es2017.min.js.svg)__ with __no external dependencies__.\n\u003cbr\u003eInspired from [Robert Penner](https://github.com/robertpenner)'s AS3 Signals.\n\u003cbr\u003eSource code in Typescript, compiled to ESM \u0026 CJS Javascript thanks to [TSBundle](https://github.com/zouloux/tsbundle).\nWorks in Node and Browser environments.\n\n---\n\u003cp align=\"center\"\u003e\n\t\u003cstrong\u003eSignal\u003c/strong\u003e ➡\n\t\u003ca href=\"#concept\"\u003eConcept\u003c/a\u003e\u0026nbsp;/\u0026nbsp;\n\t\u003ca href=\"#usage\"\u003eUsage\u003c/a\u003e\u0026nbsp;/\u0026nbsp;\n\t\u003ca href=\"#naming-signals\"\u003eNaming Signals\u003c/a\u003e\u0026nbsp;/\u0026nbsp;\n\t\u003ca href=\"#remove\"\u003eRemove\u003c/a\u003e\u0026nbsp;/\u0026nbsp; \n\t\u003ca href=\"#state-signal\"\u003eState Signal\u003c/a\u003e\u0026nbsp;/\u0026nbsp;\n\t\u003ca href=\"#unpkg\"\u003eUnpkg\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n### Concept\n\nClassic event dispatcher systems are __string based__, which can be difficult to track across your application.\n\n```typescript\ndocument.addEventListener( \"which one already ?\", () =\u003e {} );\n```\n\nWith Signal, every event is represented by an __entity__ with `add`, `remove` and `dispatch` methods.\n\u003cbr\u003eMessages can be dispatched and followed __more fluently__ thanks to its dot notation.\n\n```typescript\nconst onMessage = Signal()\nonMessage.add( message =\u003e {\n\tconsole.log( message ) // { from: \"Michael\", content: \"Hello !\" }\n})\nonMessage.dispatch({\n\tfrom: \"Michael\",\n\tcontent: \"Hello !\"\n})\n```\n\n### Usage\n\nSignal follow the [__composition over inheritance__](https://en.wikipedia.org/wiki/Composition_over_inheritance) concept of design patterns\nto allow highly scalable projects and libraries. Ne need to extend EventDispatcher again.\nSimple example of composition with several Signals :\n\n```typescript\nfunction createMessageSystem () { // No class, no inheritence, no pain\n\treturn {\n\t\t// Two events -\u003e two entities, no string used here\n\t\tonConnected: Signal\u003c[ Boolean ]\u003e(), // Optional, can pass type of arguments\n\t\tonMessage: Signal(), // No type here, so no check of passed object with TS\n\n\t\tconnect () {\n\t\t\t// ...\n\t\t\tonConnected.dispatch( true );\n\t\t},\n\t\tsendMessage ( userName:string, content:string ) {\n\t\t\t// ...\n\t\t\tonMessage.dispatch( { from: userName, content } )\n\t\t}\n\t}\n}\nconst messageSystem = createMessageSystem();\nmessageSystem.onConnected.once( state =\u003e {\n\t// Called once when connected\n})\nmessageSystem.connect();\nmessageSystem.onMessage.add( message =\u003e {\n\tconsole.log( message )\n})\nmessageSystem.sendMessage(\"Bernie\", \"Hey\")\n// ...\nmessageSystem.sendMessage(\"Bernie\", \"What'up ?\")\n\n```\n\n### Naming Signals\n\nSignal are object entities which can and should be named correctly.\nIt's better to name signal prefixed with __\"on\"__ and with usage of preterit if possible.\n\n```\n✅ onMessage\n✅ onMessageReceived\n🚫 message\n🚫 messageReceived\n🚫 receiveMessage\n---\n✅ onConnected\n✅ onData\n✅ onDataSent\n✅ onDataReceived\n```\n\n### Remove\n\nSignal handlers can be detached with the remove function, but you need to keep track of the handler's reference.\n\n```tsx\nfunction handler () {\n\t// Called once\n}\nonSignal.add( handler )\nonSignal.dispatch()\n// ...\nonSignal.remove( handler ) // dettach listener\nonSignal.dispatch()\n```\n\nFor convenience and easier usage, when a signal is attached, a remove thunk is returned.\nIt allows fast removal of anonymous handlers without having to target it manually. \n\n```tsx\nconst removeListener = onSignal.add(() =\u003e {\n\t// Called once\n})\nonSignal.dispatch()\n// ...\nremoveListener() // dettach listener without handler ref\nonSignal.dispatch()\n```\n\nWorks well with React Hooks :\n\n```tsx\nfunction ReactComponent ( props ) {\n\tuseLayoutEffect(() =\u003e {\n\t\t// onData.add returns the remove function,\n\t\t// so the layoutEffect will remove when component will be destroyed\n\t\treturn Model.onData.add( data =\u003e {\n\t\t\t\n\t\t})\n\t}, [])\n\treturn \u003cdiv\u003e\u003c/div\u003e\n}\n```\n\nCan be shortened to \n```tsx\nfunction ReactComponent ( props ) {\n\tuseLayoutEffect(() =\u003e Model.onData.add( data =\u003e {\n\t\t// Data changed, listener will be removed automatically with component\n\t}))\n}\n```\n\nTo clear all listeners. Useful to dispose a signal and allow garbage collection.\n\n```tsx\nonSignal.clear();\n```\n\n\n### State Signal\n\nStateSignal is a kind of Signal which holds the last dispatched value.\nA StateSignal can be initialized with a default value.\n\n```tsx\n// No need for generics here, state type is gathered from default value\nconst onStateSignal = StateSignal( 12 ) // 12 is the default value here\nconsole.log(onStateSignal.state) // == 12\n\nonStateSignal.add( value =\u003e {\n\t// Is dispatched twice.\n\tconsole.log( value )\n\t// 1st -\u003e 12 (call at init)\n\t// 2nd -\u003e 15 (dispatch)\n}, true) // True here means \"call at init\" (will call handler when attached)\n\n// Read and alter state\nif ( onStateSignal.state === 12 )\n\tonStateSignal.dispatch( 15 ) // Change the state value\n```\n\nState Signal will send old value as second argument. It can be useful to diff changes.\n\n```tsx\nconst onStateSignal = StateSignal( 12 )\nonStateSignal.add( ( newValue, oldValue ) =\u003e {\n\t// Continue only when value actually changes\n\tif ( newValue == oldValue )\n\t\treturn\n\tif ( newValue \u003e oldValue )\n\t\tconsole.log(\"Greater\")\n\telse\n\t\tconsole.log(\"Smaller\")\n})\nonStateSignal.dispatch( 15 ) // Greater\nonStateSignal.dispatch( 5 ) // Smaller\nonStateSignal.dispatch( 5 ) // No effect\n```\n\n### Unpkg\n\nSignal is available on [unpkg](https://unpkg.com/) CDN as :\n- [Only Signal](https://unpkg.com/@zouloux/signal@latest/dist/signal.es2017.min.js) ![](./bits/signal.es2017.min.js.svg)\n- [State-Signal + Signal.js](https://unpkg.com/@zouloux/signal@latest/dist/state-signal.es2017.min) ![](./bits/state-signal.es2017.min.js.svg)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzouloux%2Fsignal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzouloux%2Fsignal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzouloux%2Fsignal/lists"}