{"id":41038146,"url":"https://github.com/imkira/go-observer","last_synced_at":"2026-01-22T10:39:25.772Z","repository":{"id":24626457,"uuid":"28035538","full_name":"imkira/go-observer","owner":"imkira","description":"Go package for simplifying channel-based broadcasting of events from multiple publishers to multiple observers","archived":false,"fork":false,"pushed_at":"2023-07-05T14:18:57.000Z","size":30,"stargazers_count":90,"open_issues_count":4,"forks_count":14,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-08-13T16:58:24.106Z","etag":null,"topics":["broadcasting","channels","events","go","observer"],"latest_commit_sha":null,"homepage":null,"language":"Go","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/imkira.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2014-12-15T12:22:36.000Z","updated_at":"2025-07-14T21:31:35.000Z","dependencies_parsed_at":"2022-08-23T03:21:18.189Z","dependency_job_id":"0b863bd8-76b2-4aa5-b325-4a1898a5f4bd","html_url":"https://github.com/imkira/go-observer","commit_stats":{"total_commits":39,"total_committers":3,"mean_commits":13.0,"dds":0.05128205128205132,"last_synced_commit":"7307ba0975539e82138a642399a1291df3905578"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/imkira/go-observer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imkira%2Fgo-observer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imkira%2Fgo-observer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imkira%2Fgo-observer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imkira%2Fgo-observer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imkira","download_url":"https://codeload.github.com/imkira/go-observer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imkira%2Fgo-observer/sbom","scorecard":{"id":485697,"data":{"date":"2025-08-11","repo":{"name":"github.com/imkira/go-observer","commit":"8e0b61f11f1b3cf7048bd783aea8f26d74ec5acd"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","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":"Code-Review","score":1,"reason":"Found 3/23 approved changesets -- score normalized to 1","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":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"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":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"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":"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":"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":"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":"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt: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 'master'"],"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":"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 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-19T17:42:53.547Z","repository_id":24626457,"created_at":"2025-08-19T17:42:53.547Z","updated_at":"2025-08-19T17:42:53.547Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28661875,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T01:17:37.254Z","status":"online","status_checked_at":"2026-01-22T02:00:07.137Z","response_time":144,"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":["broadcasting","channels","events","go","observer"],"created_at":"2026-01-22T10:39:25.119Z","updated_at":"2026-01-22T10:39:25.763Z","avatar_url":"https://github.com/imkira.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# observer\n\n[![License](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://github.com/imkira/go-observer/blob/master/LICENSE.txt)\n[![GoDoc](https://godoc.org/github.com/imkira/go-observer?status.svg)](https://godoc.org/github.com/imkira/go-observer)\n[![Build Status](http://img.shields.io/travis/imkira/go-observer.svg?style=flat)](https://travis-ci.org/imkira/go-observer)\n[![Coverage](https://codecov.io/gh/imkira/go-observer/branch/master/graph/badge.svg)](https://codecov.io/gh/imkira/go-observer)\n[![codebeat badge](https://codebeat.co/badges/28bdd579-8b34-4940-a3e0-35ac52794a42)](https://codebeat.co/projects/github-com-imkira-go-observer)\n[![goreportcard](https://goreportcard.com/badge/github.com/imkira/go-observer)](https://goreportcard.com/report/github.com/imkira/go-observer)\n\nobserver is a [Go](http://golang.org) package that aims to simplify the problem\nof channel-based broadcasting of events from one or more publishers to one or\nmore observers.\n\n# Problem\n\nThe typical quick-and-dirty approach to notifying a set of observers in go is\nto use channels and call each in a for loop, like the following:\n\n```go\nfor _, channel := range channels {\n  channel \u003c- value\n}\n```\n\nThere are two problems with this approach:\n\n- The broadcaster blocks every time some channel is not ready to be written to.\n- If the broadcaster blocks for some channel, the remaining channels will not\n  be written to (and therefore not receive the event) until the blocking\n  channel is finally ready.\n- It is O(N). The more observers you have, the worse this loop will behave.\n\nOf course, this could be solved by creating one goroutine for each channel so\nthe broadcaster doesn't block. Unfortunately, this is heavy and\nresource-consuming. This is especially bad if you have events being raised\nfrequently and a considerable number of observers.\n\n# Approach\n\nThe way observer package tackles this problem is very simple. For every event,\na state object containing information about the event, and a channel is\ncreated. State objects are managed using a singly linked list structure: every\nstate points to the next. When a new event is raised, a new state object is\nappended to the list and the channel of the previous state is closed (this\nhelps notify all observers that the previous state is outdated).\n\nPackage observer defines 2 concepts:\n\n- Property: An object that is continuously updated by one or more publishers.\n- Stream: The list of values a property is updated to. For every property\nupdate, that value is appended to the list in the order they happen, and is\nonly discarded when you advance to the next value.\n\n# Memory Usage\n\nThe amount of memory used for one property is not dependent on the number of\nobservers. It should be proportional to the number of value updates since the\nvalue last obtained by the slowest observer. As long as you keep advancing all\nyour observers, garbage collection will take place and keep memory usage\nstable.\n\n# How to Use\n\nFirst, you need to install the package:\n\n```\ngo get -u github.com/imkira/go-observer\n```\n\nThen, you need to include it in your source:\n\n```go\nimport \"github.com/imkira/go-observer\"\n```\n\nThe package will be imported with ```observer``` as name.\n\nThe following example creates one property that is updated every second by one\nor more publishers, and observed by one or more observers.\n\n## Documentation\n\nFor advanced usage, make sure to check the\n[available documentation here](http://godoc.org/github.com/imkira/go-observer).\n\n## Example: Creating a Property\n\nThe following code creates a property with initial value ```1```.\n\n```go\nval := 1\nprop := observer.NewProperty(val)\n```\n\nAfter creating the property, you can pass it around to publishers or\nobservers as you want.\n\n## Example: Publisher\n\nThe following code represents a publisher that increments the value of the\nproperty by one every second.\n\n```go\nval := 1\nfor {\n  time.Sleep(time.Second)\n  val += 1\n  fmt.Printf(\"will publish value: %d\\n\", val)\n  prop.Update(val)\n}\n```\n\nNote:\n\n- Property is goroutine safe: you can use it concurrently from multiple\ngoroutines.\n\n## Example: Observer\n\nThe following code represents an observer that prints the initial value of a\nproperty and waits indefinitely for changes to its value. When there is a\nchange, the stream is advanced and the current value of the property is\nprinted.\n\n```go\nstream := prop.Observe()\nval := stream.Value()\nfmt.Printf(\"initial value: %d\\n\", val)\nfor {\n  select {\n    // wait for changes\n    case \u003c-stream.Changes():\n      // advance to next value\n      stream.Next()\n      // new value\n      val = stream.Value()\n      fmt.Printf(\"got new value: %d\\n\", val)\n  }\n}\n```\n\nNote:\n\n- Stream is not goroutine safe: You must create one stream by calling\n  ```Property.Observe()``` or ```Stream.Clone()``` if you want to have\n  concurrent observers for the same property or stream.\n\n## Example\n\nPlease check\n[examples/multiple.go](https://github.com/imkira/go-observer/blob/master/examples/multiple.go)\nfor a simple example on how to use multiple observers with a single updater.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimkira%2Fgo-observer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimkira%2Fgo-observer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimkira%2Fgo-observer/lists"}