{"id":50629130,"url":"https://github.com/flatstadt/omens","last_synced_at":"2026-06-06T19:30:59.523Z","repository":{"id":57126277,"uuid":"256568627","full_name":"flatstadt/omens","owner":"flatstadt","description":"A library to connect distributed logic","archived":false,"fork":false,"pushed_at":"2020-04-26T09:43:51.000Z","size":197,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-21T01:10:15.415Z","etag":null,"topics":["angular","messenger","model","observable"],"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/flatstadt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-17T17:32:04.000Z","updated_at":"2020-04-26T11:39:07.000Z","dependencies_parsed_at":"2022-08-31T08:11:57.634Z","dependency_job_id":null,"html_url":"https://github.com/flatstadt/omens","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/flatstadt/omens","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flatstadt%2Fomens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flatstadt%2Fomens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flatstadt%2Fomens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flatstadt%2Fomens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flatstadt","download_url":"https://codeload.github.com/flatstadt/omens/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flatstadt%2Fomens/sbom","scorecard":{"id":402778,"data":{"date":"2025-08-11","repo":{"name":"github.com/flatstadt/omens","commit":"78ee848bbcb8596e50aeaa2be804b44bfd3733a0"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"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":"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":0,"reason":"Found 0/10 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":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"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":"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":"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":"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":"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":"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":"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 '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":0,"reason":"128 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-c75v-2vq8-878f","Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-whgm-jr23-g3j9","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-x9w5-v3q2-3rhw","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-257v-vj4p-3w2h","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-3wcq-x3mq-6r9p","Warn: Project is vulnerable to: GHSA-vh7m-p724-62c2","Warn: Project is vulnerable to: GHSA-r9p9-mrjm-926w","Warn: Project is vulnerable to: GHSA-434g-2637-qmqr","Warn: Project is vulnerable to: GHSA-49q7-c7j4-3p7m","Warn: Project is vulnerable to: GHSA-977x-g7h5-7qgw","Warn: Project is vulnerable to: GHSA-f7q4-pwc6-w24p","Warn: Project is vulnerable to: GHSA-fc9h-whq2-v747","Warn: Project is vulnerable to: GHSA-vjh7-7g9h-fjfh","Warn: Project is vulnerable to: GHSA-j4f2-536g-r55m","Warn: Project is vulnerable to: GHSA-r7qp-cfhv-p84w","Warn: Project is vulnerable to: GHSA-6h5x-7c5m-7cr7","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-74fj-2j2h-c42q","Warn: Project is vulnerable to: GHSA-pw2r-vq6v-hr8c","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-6x33-pw7p-hmpq","Warn: Project is vulnerable to: GHSA-c7qv-q95q-8v27","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-7r28-3m3f-r2pr","Warn: Project is vulnerable to: GHSA-r8j5-h5cx-65gg","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-jg8v-48h5-wgxg","Warn: Project is vulnerable to: GHSA-36fh-84j7-cv5h","Warn: Project is vulnerable to: GHSA-7x7c-qm48-pq9c","Warn: Project is vulnerable to: GHSA-rc3x-jf5g-xvc5","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-82v2-mx6x-wq7q","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-qm28-7hqv-wg5j","Warn: Project is vulnerable to: GHSA-92xj-mqp7-vmcj","Warn: Project is vulnerable to: GHSA-wxgw-qj99-44c2","Warn: Project is vulnerable to: GHSA-5rrq-pxf6-6jx5","Warn: Project is vulnerable to: GHSA-8fr3-hfg3-gpgp","Warn: Project is vulnerable to: GHSA-gf8q-jrpm-jvxq","Warn: Project is vulnerable to: GHSA-2r2c-g63r-vccr","Warn: Project is vulnerable to: GHSA-cfm4-qjh2-4765","Warn: Project is vulnerable to: GHSA-x4jg-mjrx-434g","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-jmqm-f2gx-4fjv","Warn: Project is vulnerable to: GHSA-rp65-9cf3-cjxr","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-6fx8-h7jm-663j","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-h7cp-r72f-jxh6","Warn: Project is vulnerable to: GHSA-v62p-rq8g-8h59","Warn: Project is vulnerable to: GHSA-566m-qj78-rww5","Warn: Project is vulnerable to: GHSA-hwj9-h5mp-3pm3","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-hxcc-f52p-wc94","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-fxwf-4rqh-v8g3","Warn: Project is vulnerable to: GHSA-25hc-qcg6-38wj","Warn: Project is vulnerable to: GHSA-xfhh-g9f5-x4m4","Warn: Project is vulnerable to: GHSA-qm95-pgcg-qqfq","Warn: Project is vulnerable to: GHSA-cqmj-92xf-r6r9","Warn: Project is vulnerable to: GHSA-c9g6-9335-x697","Warn: Project is vulnerable to: GHSA-vx3p-948g-6vhq","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-9m6j-fcg5-2442","Warn: Project is vulnerable to: GHSA-hh27-ffr2-f2jc","Warn: Project is vulnerable to: GHSA-rqff-837h-mm52","Warn: Project is vulnerable to: GHSA-8v38-pw62-9cw2","Warn: Project is vulnerable to: GHSA-hgjh-723h-mx2j","Warn: Project is vulnerable to: GHSA-jf5r-8hm2-f872","Warn: Project is vulnerable to: GHSA-mgfv-m47x-4wqp","Warn: Project is vulnerable to: GHSA-wr3j-pwj9-hqq6","Warn: Project is vulnerable to: GHSA-4v9v-hfq4-rm2v","Warn: Project is vulnerable to: GHSA-9jgg-88mc-972h","Warn: Project is vulnerable to: GHSA-4fc4-chg7-h8gh","Warn: Project is vulnerable to: GHSA-g78m-2chm-r7qv","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-776f-qx25-q3cc","Warn: Project is vulnerable to: GHSA-72mh-269x-7mh5","Warn: Project is vulnerable to: GHSA-h4j5-c7cj-74xg","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T20:28:16.507Z","repository_id":57126277,"created_at":"2025-08-18T20:28:16.507Z","updated_at":"2025-08-18T20:28:16.507Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33997732,"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-06T02:00:07.033Z","response_time":107,"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":["angular","messenger","model","observable"],"created_at":"2026-06-06T19:30:58.282Z","updated_at":"2026-06-06T19:30:59.488Z","avatar_url":"https://github.com/flatstadt.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?style=flat-square)]()\n[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n[![All Contributors](https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square)](#contributors-)\n[![flatstadt](https://img.shields.io/badge/@-flatstadt-383636?style=flat-square\u0026labelColor=8f68d4)](https://github.com/flatstadt/)\n\n\n\u003e Omens helps managing internal events\n\nFrequently, we are in need to propagate changes and notify multiple listeners in a way that it doesn't create event loops. Omens helps to keep control over your event flow.\n\n## Features\n\n- ✅ Messenger\n- ✅ Observable Model\n- ✅ Observable Properties\n\n## Table of Contents\n\n- [Features](#features)\n- [Table of Contents](#table-of-contents)\n- [Installation](#installation)\n  - [NPM](#npm)\n  - [Yarn](#yarn)\n- [Usage](#usage)\n  - [Messenger](#messenger)\n  - [Observable Model](#observable-model)\n  - [Observable Property Changed](#observable-property-changed)\n\n\n## Installation\n\n### NPM\n\n`npm install @lapita/omens --save`\n\n### Yarn\n\n`yarn add @lapita/omens`\n\n## Usage\nOmens offers three different functionalities. A messenger to broadcast messages across components. An observable model that help to notify observers that their underlying model has changed and could react accordingly. Lastly, an observable property changed extension that creates events every time a property value changes.\n\n### Messenger\nThe messenger allows to send a message to multiple listeners. There's a default messenger that can be accessed as `Messenger.default()`, but it also possible to create new instances by doing `new Messenger()`.\n\nEach instance of `Messenger` keeps their messages separated. They work like separated delivery systems.\n\nWhen instancing a new Messenger, it's possible to pass in options to tune they way it works.\n\n```ts\nexport interface MessengerOptions {\n  buffer: number; // size of the internal buffer.\n  delay: number; // default delay of messages\n  ttl: number; // default expiration of the buffer\n  scheduler: SchedulerLike; // RXJS observable scheduler\n}\n```\nThe messenger uses an internal queue which is a hot observable. The events published before start listening are gone. The buffer size allows to always return the last n-number of messages.\n\nMessages are custom classes that extends `Messages`. When sending a new message, a new instance of the Messages is created. A listener can subscribe to a Message type by passing in the class name.\n\n```ts\nimport { Message, Messenger } from '@lapita/omens';\n\nexport class MessengerPlayground {\n\n  public static play() {\n    // Listening to CustomMessage sent through the default messenger\n    Messenger.default().listen(CustomMessage).subscribe(env =\u003e console.log('listener: msg received', env.read()));\n    console.log('Send message to only one listener');\n\n    Messenger.default().broadcast(new CustomMessage('broadcast message'));\n  }\n}\n\nclass CustomMessage extends Message {\n  constructor(public text: string) {\n    super();\n  }\n}\n```\n\n* `default()` - return the default `Messenger` instance.\n\n* `listen(type): Observable\u003cEnvelope\u003e` - listen to a type of Message\n\n```ts\nMessenger.default().listen(CustomMessage).subscribe();\n```\nThe subscription returns a `Envelope`. An envelope contains the sent Message.\n\n```ts\ninterface Envelope\u003cT extends Message\u003e {\n    get deliveredAt(): number; // when the envelope was received\n    get opened(): boolean; // whether message was already read\n    get expired(): boolean; // whether message expired\n    read(answer?: any): T; // get the inside message. It's possible to send an answer to the sender.\n}\n```\n\n* `listenToAll(): Observable\u003cEnvelope\u003e` - listen to any type of Message\n\n```ts\nMessenger.default().listenToAll().subscribe();\n```\n* `sendToOne(msg: Message, cb?: RecipientAnswer): Receipt` - send a single message\n\nThis way of sending a message makes sure that only one listener receives the message even when there are multiple subscribers.\n\nOptionally, it's possible to add a callback function `RecipientAnswer` to receive answers from recipients.\n```ts\nconst answer = (answer) =\u003e {console.log('Answer received')}\nconst receipt = Messenger.default().sentToOne(new CustomMessage(), answer);\n```\nIt returns a `Receipt`.\n\n```ts\ninterface Receipt {\n    answer: RecipientAnswer; // returns a reference of the optional answer callback fcn.\n    requestCancellation(); // cancels the message delivery\n}\n```\n* `broadcast(msg: Message, cb?: RecipientAnswer): Receipt` - broadcast a message to all listeners\n\n### Observable Model\nBy using `ModelChanged`, any class becomes observable to any change. Observers can subscribe to changes and apply updates. Any change has attached a source which allows to break infinity update loops.\n\n```ts\nimport { ModelChanged } from '@lapita/omens';\n\n// Observable Model Container\nexport class AppService extends ModelChanged\u003cAppModel\u003e {\n  constructor() {\n    super(new AppModel());\n  }\n}\n\n// Model\nclass AppModel {\n  name = 'John';\n  lastname = 'Connor';\n  createdAt = 1000;\n}\n```\n`ModelChanged\u003cAppModel\u003e` passes the needed functionality to the model container that allows to interact with it and observe changes.\n\n```ts\nexport abstract class ModelChanged\u003cU extends any\u003e {\n    modelChanged(): Observable\u003cU\u003e; // broadcast all changes of the underlying model\n    updating(): boolean; // model is being updated and no events are propagated\n    value(): U; // returns the model value\n    history(): ModelHistory\u003cU\u003e[]; // returns history of changes\n    historyPointIndex(): number; // returns current history point being applied, which changes when undoing and redoing\n    historyLength(): number; // returns history length\n    undo(opts: Partial\u003cModelActionOptions\u003e = {}); // undo last change\n    undoAll(opts: Partial\u003cModelActionOptions\u003e = {}); // undo all changes\n    redo(opts: Partial\u003cModelActionOptions\u003e = {}); // redo next change\n    redoAll(opts: Partial\u003cModelActionOptions\u003e = {}); // redo all changes\n    getPropertyValue\u003cK extends keyof U\u003e(key: K): any; // get a property value\n    getPartialValue\u003cK extends keyof U\u003e(keys: K[]): Partial\u003cU\u003e; // get a model chunk\n    update(value: Partial\u003cU\u003e, opts: Partial\u003cModelActionOptions\u003e = {}); // update the model\n    listen(observer: symbol, ownUpdates = false): Observable\u003cU\u003e; // listen to changes filtered by source.\n    beginUpdate(); // start a update batch and hold update events\n    endUpdate(opts: Partial\u003cModelActionOptions\u003e = {}); // end update and flush pending events\n}\n```\nListening events using `listen()` allows to filter events. So that if the observer match the source, the source is not notified unless `ownUpdates` is set `true`.\n\nMany methods accept extra options to change the default behavior.\n```ts\ninterface ModelActionOptions {\n    source: symbol; // source of action, if not set, internal source is used.\n    emitEvent: boolean; // whether to emit events as result of the action\n}\n```\n ```ts\nexport class ModelPlayground {\n\n  public static play() {\n    const listener = Symbol('listener');\n    const source = Symbol('source');\n    const service = new AppModelService();\n    service.listen(listener).subscribe(event =\u003e {\n      console.log('Model Changed', event);\n    });\n\n    // change model\n    service.update({ name: 'Mary' }, {source});\n    // sequence of changes\n    service.beginUpdate();\n    service.update({ name: 'Joseph'}, {source});\n    service.update({lastname: 'Bosch'}, {source});\n    service.update({createdAt: 4000}, {source});\n    service.endUpdate();\n    // get change history\n    console.log('Change history:');\n    console.table(service.history);\n    // undo change\n    service.undo();\n    // undo all\n    service.undoAll();\n    // redo all\n    service.redoAll();\n    console.log('final name', service.getPropertyValue('name'));\n    console.log('final partial value', service.getPartialValue(['name', 'lastname']));\n  }\n}\n```\n\n### Observable Property Changed\nSimilarly to `ModelChanged`, `PropertyChanged` can be extended by a custom model container and make any change observable. The different lays in that `PropertyChanged` observes properties one by one, even properties inside child objects.\n\n```ts\nimport { PropertyChanged } from '@lapita/omens';\n\nexport class AppService extends PropertyChanged\u003cAppModel\u003e {\n  constructor() {\n    super(new AppModel());\n  }\n}\n\nclass AppModel {\n  name = 'John';\n  lastname = 'Connor';\n  createdAt = 1000;\n  address = {\n    street: 'Big way',\n    number: 3\n  };\n}\n```\n\nThe `constructor` of `PropertyChanged` accepts extra options to modify its default behavior.\n\n```ts\ninterface PropertyOptions {\n    bufferTime: number; // pack update notification in bundle of n milliseconds\n    omitOlderUpdates: boolean; // only propagate the most recent updates in a given update bundle. e.g.  [update(name = 'Tim') - update(name = 'Josh')], observer only receive update(name = 'Josh')] if this option is set true.\n}\n```\n`PropertyChanged\u003cAppModel\u003e` passes the needed functionality to the model container that allows to interact with it and observe changes.\n\n```ts\nabstract class PropertyChanged\u003cU extends any\u003e {\n    propertyChanged(): Observable\u003cPropertyChangedEvent\u003e; // notify any change\n    value(): U; // returns the model value\n    updating(): boolean; // indicates whether the model is being updated\n    history(): PropertyHistory[]; // returns history of changes\n    historyPointIndex(): number; // returns current history point\n    historyLength(): number; // returns history length\n    properties(): FlattenProperty[]; // returns a list of properties\n    paths(): string[][]; // returns a list of the property paths\n    undo(opts: Partial\u003cPropertyActionOptions\u003e = {}); // undo last change\n    undoAll(opts: Partial\u003cPropertyActionOptions\u003e = {}); // undo all changes\n    redo(opts: Partial\u003cPropertyActionOptions\u003e = {}); // redo last undo\n    redoAll(opts: Partial\u003cPropertyActionOptions\u003e = {}); // redo all changes\n    getPropertyValue(path: string | string[]): any; // get a specific property value\n    setPropertyValue(path: string | string[], value: any, opts: Partial\u003cPropertyActionOptions\u003e = {}); // change a property value\n    setPropertiesValue(data: Partial\u003cU\u003e, opts: Partial\u003cPropertyActionOptions\u003e = {}); // set the value of a collection of properties\n    listen(observer: symbol, ownUpdates = false): Observable\u003cPropertyChangedEvent\u003e; // listen to updates\n    beginUpdate(); // begin a batch of changes withholding events\n    endUpdate(opts: Partial\u003cPropertyActionOptions\u003e = {}); // end current update batch and flush events\n}\n```\nThis example makes use of `PropertyChanged` to listen for any change and performing updates using two different source and listener identifiers.\n```ts\nimport { AppPropertyService } from './app-property';\n\nexport class PropertyPlayground {\n\n  public static play() {\n    const listener = Symbol('listener');\n    const source = Symbol('source');\n    const service = new AppPropertyService();\n    service.listen(listener).subscribe(event =\u003e {\n      console.log('Changed', event);\n    });\n\n    // set a subset of properties\n    console.log('-\u003eSet Name and Lastname');\n    service.setPropertiesValue({name: 'Mary', lastname: 'Pills'}, {source});\n    console.log('-\u003eSet CreatedAt');\n    service.setPropertyValue('createdAt', 1200, {source});\n\n    // redo the\n    setTimeout(() =\u003e {\n      console.log('-\u003eUndo');\n      service.undo();\n      console.log('history-\u003e', service.history);\n    }, 20);\n    setTimeout(() =\u003e {\n      console.log('-\u003eSet Lastname');\n      service.setPropertyValue('lastname', 'Bosch');\n      console.log('history-\u003e', service.history);\n    }, 40);\n\n    setTimeout(() =\u003e {\n      console.log('-\u003eUndo all');\n      service.undoAll();\n      console.log('history-\u003e', service.history);\n    }, 60);\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflatstadt%2Fomens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflatstadt%2Fomens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflatstadt%2Fomens/lists"}