{"id":21083256,"url":"https://github.com/kinsondigital/carbonate","last_synced_at":"2025-08-02T14:08:41.423Z","repository":{"id":65020753,"uuid":"577536537","full_name":"KinsonDigital/Carbonate","owner":"KinsonDigital","description":"Internal messaging library using the observable pattern","archived":false,"fork":false,"pushed_at":"2025-07-11T11:54:09.000Z","size":763,"stargazers_count":18,"open_issues_count":13,"forks_count":1,"subscribers_count":0,"default_branch":"preview","last_synced_at":"2025-07-28T16:37:21.341Z","etag":null,"topics":["csharp","events","hacktoberfest","messaging","notifications","observable"],"latest_commit_sha":null,"homepage":"","language":"C#","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/KinsonDigital.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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},"funding":{"github":"KinsonDigital"}},"created_at":"2022-12-13T00:32:57.000Z","updated_at":"2025-02-01T12:49:37.000Z","dependencies_parsed_at":"2023-12-18T15:13:18.247Z","dependency_job_id":"44075a0b-5dd1-49c1-ab06-99dfcccafe3b","html_url":"https://github.com/KinsonDigital/Carbonate","commit_stats":{"total_commits":275,"total_committers":4,"mean_commits":68.75,"dds":"0.41454545454545455","last_synced_commit":"0337a9a1e8fe51bad3785bbce401931a97603d32"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":"KinsonDigital/CSharpLibTemplateRepo","purl":"pkg:github/KinsonDigital/Carbonate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KinsonDigital%2FCarbonate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KinsonDigital%2FCarbonate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KinsonDigital%2FCarbonate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KinsonDigital%2FCarbonate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KinsonDigital","download_url":"https://codeload.github.com/KinsonDigital/Carbonate/tar.gz/refs/heads/preview","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KinsonDigital%2FCarbonate/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268401594,"owners_count":24244464,"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","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"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":["csharp","events","hacktoberfest","messaging","notifications","observable"],"created_at":"2024-11-19T20:17:15.411Z","updated_at":"2025-08-02T14:08:41.386Z","avatar_url":"https://github.com/KinsonDigital.png","language":"C#","funding_links":["https://github.com/sponsors/KinsonDigital"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n![logo](https://raw.githubusercontent.com/KinsonDigital/Carbonate/preview/Images/carbonate-logo-light-mode.svg#gh-light-mode-only)\n![logo](https://raw.githubusercontent.com/KinsonDigital/Carbonate/preview/Images/carbonate-logo-dark-mode.svg#gh-dark-mode-only)\n\u003c/div\u003e\n\n\u003ch1 style=\"border:0;font-weight:bold\" align=\"center\"\u003eCarbonate\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![Build PR Status Check](https://img.shields.io/github/actions/workflow/status/KinsonDigital/Carbonate/build-status-check.yml?label=%E2%9A%99%EF%B8%8FBuild)](https://github.com/KinsonDigital/Carbonate/actions/workflows/build-status-check.yml)\n[![Test PR Status Check](https://img.shields.io/github/actions/workflow/status/KinsonDigital/Carbonate/test-status-check.yml?label=%F0%9F%A7%AATests)](https://github.com/KinsonDigital/Carbonate/actions/workflows/test-status-check.yml)\n\n[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=KinsonDigital_Carbonate\u0026metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=KinsonDigital_Carbonate)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=KinsonDigital_Carbonate\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=KinsonDigital_Carbonate)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=KinsonDigital_Carbonate\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=KinsonDigital_Carbonate)\n\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=KinsonDigital_Carbonate\u0026metric=bugs)](https://sonarcloud.io/summary/new_code?id=KinsonDigital_Carbonate)\n[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=KinsonDigital_Carbonate\u0026metric=code_smells)](https://sonarcloud.io/summary/new_code?id=KinsonDigital_Carbonate)\n[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=KinsonDigital_Carbonate\u0026metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=KinsonDigital_Carbonate)\n\n[![Code Coverage](https://img.shields.io/codecov/c/github/KinsonDigital/Carbonate/preview?label=Code%20Coverage\u0026logo=CodeCov\u0026style=flat)](https://app.codecov.io/gh/KinsonDigital/Carbonate)\n\n[![Latest NuGet Release](https://img.shields.io/nuget/vpre/kinsondigital.Carbonate?label=Latest%20Release\u0026logo=nuget)](https://www.nuget.org/packages/KinsonDigital.Carbonate)\n[![Nuget Downloads](https://img.shields.io/nuget/dt/KinsonDigital.Carbonate?color=0094FF\u0026label=nuget%20downloads\u0026logo=nuget)](https://www.nuget.org/stats/packages/KinsonDigital.Carbonate?groupby=Version)\n\n[![Good First Issues](https://img.shields.io/github/issues/kinsondigital/Carbonate/good%20first%20issue?color=7057ff\u0026label=Good%20First%20Issues)](https://github.com/KinsonDigital/Carbonate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)\n[![Discord](https://img.shields.io/discord/481597721199902720?color=%23575CCB\u0026label=chat%20on%20discord\u0026logo=discord\u0026logoColor=white)](https://discord.gg/qewu6fNgv7)\n\u003c/div\u003e\n\u003ch2 style=\"font-weight:bold;border:0\" align=\"center\" \u003e!! NOTICE !!\u003c/h2\u003e\n\nThis library is still under development and is not at v1.0.0 yet!!  However, all of the major features are available, so we encourage you to use the library and provide feedback.  That is what open source is all about. 🥳\n\n\u003ch2 style=\"font-weight:bold;border:0\" align=\"center\"\u003e📖 About Carbonate 📖\u003c/h2\u003e\n\n**Carbonate** is a messaging library built on the observable pattern, empowering seamless and dependable push-and-pull message handling across various parts or systems within an application. This fosters decoupling among different components, enhancing your application's overall testability as well as separating cross-cutting concerns.\n\nYou can choose if you want to send out a push notification with or without data or if you want to poll for a notification with or without data.  These result in data only flowing in one direction.\n\nYou can also choose to push data out and receive data back in a single notification.\n\nFor a real-world example, check out the [Velaptor](https://github.com/KinsonDigital/Velaptor) code base which is an open-source 2D game development framework.  This library has been vital for decoupling the different sub-systems and increasing its testability.\n\nGo [here](https://refactoring.guru/design-patterns/observer) for information on the observable pattern. This design pattern has been extensively covered in various tutorials and examples across the web, making it well-documented, widely recognized, and a highly popular programming pattern.\n\n\u003e [!Note]\n\u003e Click [here](https://github.com/KinsonDigital/Carbonate/tree/preview/Samples/Samples) to view all of the sample projects.\n\n\u003ch2 style=\"font-weight:bold;border:0\" align=\"center\"\u003e✨ Features \u0026 Benefits ✨\u003c/h2\u003e\n\n**Features:**\n- Send push notifications with no data\n- Send push notifications with data only going out\n- Send push notifications with data only being returned\n- Send push notifications with data going out and and being returned\n- Interfaces and abstractions are provided for custom implementations and to provide testability\n\n\n**Benefits:**\n- Increases decoupling\n- Increases testability\n- Works well with dependency injection\n- Sends data and events without needing to change the public API of your library/project\n- Promotes the [Open/Closed Principle](https://www.tutorialsteacher.com/csharp/open-closed-principle)\n\n\u003ch2 style=\"font-weight:bold;border:0\" align=\"center\"\u003e💡 Examples 💡\u003c/h2\u003e\n\nBelow are some examples to demonstrate some basic uses of ***Carbonate***.  This library is very flexible but how you use it depends on the needs of your application.\n\n\u003ch3 style=\"font-weight:bold;color: #00BBC6\"\u003eNon-directional push notifications\u003c/h3\u003e\n\nTo send a _**non-directional**_ push notification, you can use the `PushReactable` class. You subscribe using the `Subscribe()` method by sending in the subscription object. The term _**non-directional**_ means that no data is being sent out or returned from the notification call stack.  This is great for sending a notification that an event has occurred when no data is needed.\n\nEvery notification sent out contains a unique ID, which subscribers must use to receive the intended notification, ensuring its exclusivity and eliminating the need for additional logic to filter out each notification going out.\n\n\u003cdetails closed\u003e\u003csummary\u003eSubscription Example\u003c/summary\u003e\n\n```cs\nvar messenger = new PushReactable(); // Create the messenger object to push notifications\nvar subId = Guid.NewGuid(); // This is the ID used to identify the event\n\n// Subscribe to the event to receive messages\nvar subscription = new ReceiveSubscription(\n    id: subId,\n    onReceive: () =\u003e Console.WriteLine(\"Received a message!\"),\n    name: \"my-subscription\",\n    onUnsubscribe: () =\u003e Console.WriteLine(\"Unsubscribed from notifications!\"),\n    onError: (ex) =\u003e Console.WriteLine($\"Error: {ex.Message}\")\n);\n\nIDisposable unsubscriber = messenger.Subscribe(subscription);\n\nmessenger.Push(subId); // Will invoke all onReceive 'Actions' subscribed to this reactable\nunsubscriber.Dispose(); // Will only unsubscribe from this subscription\n```\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n\u003cdetails closed\u003e\u003csummary\u003eHow To Unsubscribe Example\u003c/summary\u003e\n\n```cs\nvar mySubscription = new ReceiveSubscription(\n    id: subId,\n    name: \"my-subscription\",\n    onReceive: () =\u003e { Console.WriteLine(\"Received notification!\"); }\n    onUnsubscribe: () =\u003e\n    {\n       unsubscriber.Dispose(); // Will unsubscribe from further notifications\n    });\n```\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n\u003cdetails closed\u003e\u003csummary\u003eHow Not To Unsubscribe Example\u003c/summary\u003e\n\nBelow is an example of what you _**SHOULD NOT**_ do.\n```cs\nIDisposable? unsubscriber;\nvar subId = Guid.NewGuid(); // This is the ID used to identify the event\n\nvar badSubscription = new ReceiveSubscription(\n    id: subId,\n    name: \"bad-subscription\",\n    onReceive: () =\u003e\n    {\n        // DO NOT DO THIS!!\n        unsubscriber.Dispose(); // An exception will be thrown in here\n    });\nvar messenger = new PushReactable();\nunsubscriber = messenger.Subscribe(badSubscription);\nmessenger.Push(subId);\n```\n\u003c/details\u003e\n\n\u003e [!Tip]\n\u003e If you want to receive a single notification, unsubscribe from further notifications by calling the `Dispose()`\n\u003e method on the `IDisposable` object returned by the _**Reactable**_ object. All reactable objects return an unsubscriber object for unsubscribing at a later time.  The unsubscriber is returned when invoking the `Subscribe()` method.  Unsubscribing can be done anytime except in the notification delegates `onReceive`, `onRespond`, and `onReceiveRespond`.\n\n\u003e [!Tip]\n\u003e If an attempt is made to unsubscribe from notifications inside of any of the notification delegates, a `NotificationException` will be\n\u003e thrown.  This is an intentional design to prevent the removal of any internal subscriptions during the notification process.\n\u003e Of course, you can add a `try...catch` in the notification delegate to swallow the exception, but again this is not recommended.\n\n\u003ch3 style=\"font-weight:bold;color: #00BBC6\"\u003eOne way push notifications\u003c/h3\u003e\n\nTo facilitate _**one way**_ data transfer through push notifications, you can employ the `PushReactable\u003cTIn\u003e` or `PullReactable\u003cTOut\u003e` types while subscribers utilize the `ReceiveSubscription\u003cTIn\u003e` or `RespondSubscription\u003cTOut\u003e` types for their subscriptions. Setting up and using this approach follows the same steps as in the previous example. In this context, the term one-directional signifies that data exclusively flows in one direction either out from the source to the subscription delegate or from the subscription delegate to the source.\n\n\u003cdetails closed\u003e\u003csummary\u003eOne Way Out Notification Example\u003c/summary\u003e\n\n```cs\nvar messenger = new PushReactable\u003cstring\u003e(); // Create the messenger object to push notifications with data\nvar subId = Guid.NewGuid(); // This is the ID used to identify the event\n\n// Subscribe to the event to receive messages\nIDisposable unsubscriber = messenger.Subscribe(new ReceiveSubscription\u003cstring\u003e(\n    id: subId,\n    onReceive: (msg) =\u003e Console.WriteLine(msg),\n    name: \"my-subscription\",\n    onUnsubscribe: () =\u003e Console.WriteLine(\"Unsubscribed from notifications!\"),\n    onError: (ex) =\u003e Console.WriteLine($\"Error: {ex.Message}\")\n));\n\nmessenger.Push(\"hello from source!\", subId); // Will invoke all onReceive 'Actions' that have subscribed with 'subId'.\nmessenger.Unsubscribe(subId); // Will invoke all onUnsubscribe 'Actions' that have subscribed with 'subId'.\n```\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n\u003cdetails closed\u003e\u003csummary\u003eOne Way In Notification(Polling) Example\u003c/summary\u003e\n\n```cs\nvar messenger = new PullReactable\u003cstring\u003e(); // Create the messenger object to push notifications to receive data\nvar subId = Guid.NewGuid(); // This is the ID used to identify the event\n\n// Subscribe to the event to receive messages\nIDisposable unsubscriber = messenger.Subscribe(new RespondSubscription\u003cstring\u003e(\n    id: subId,\n    onRespond: (msg) =\u003e \"hello from subscriber!\",\n    name: \"my-subscription\",\n    onUnsubscribe: () =\u003e Console.WriteLine(\"Unsubscribed from notifications!\"),\n    onError: (ex) =\u003e Console.WriteLine($\"Error: {ex.Message}\")\n));\n\nvar response = messenger.Pull(subId); // Will invoke all onRespond 'Actions' that have subscribed with 'subId'.\nConsole.WriteLine(response);\nmessenger.Unsubscribe(subId); // Will invoke all onUnsubscribe 'Actions' that have subscribed with 'subId'.\n```\n\u003c/details\u003e\n\n\u003ch3 style=\"font-weight:bold;color: #00BBC6\"\u003eTwo Way Push Pull Notifications\u003c/h3\u003e\n\nTo enable ***two way*** push notifications, allowing data to be sent out and returned, you can employ the `PushPullReactable\u003cTIn, TOut\u003e` type. Subscribers, on the other hand, utilize the `ReceiveRespondSubscription\u003cTIn, TOut\u003e` when subscribing. This approach proves useful when you need to send a push notification with data required by the receiver, who then responds with data back to the source that initiated the notification.  This is synonymous with sending an email out to a person and getting a response back.\n\n\u003cdetails closed\u003e\u003csummary\u003eTwo Way Notification Example\u003c/summary\u003e\n\n```cs\nvar favoriteMessenger = new PushPullReactable\u003cstring, string\u003e();\nvar subId = Guid.NewGuid(); // This is the ID used to identify the event\n\nvar unsubscriber = favoriteMessenger.Subscribe(new ReceiveRespondSubscription\u003cstring, string\u003e(\n    id: subId,\n    onRespond: (data) =\u003e data switch\n        {\n            \"prog-lang\" =\u003e \"C#\",\n            \"food\" =\u003e \"scotch eggs\",\n            \"past-time\" =\u003e \"game development\",\n            \"music\" =\u003e \"hard rock/metal\",\n        },\n    name: \"favorites\",\n    onUnsubscribe: () =\u003e Console.WriteLine(\"Unsubscribed from notifications!\"),\n    onError: (ex) =\u003e Console.WriteLine($\"Error: {ex.Message}\")\n));\n\nConsole.WriteLine($\"Favorite Language: {favoriteMessenger.PushPull(\"prog-lang\", subId)}\");\nConsole.WriteLine($\"Favorite Food: {favoriteMessenger.PushPull(\"food\", subId)}\");\nConsole.WriteLine($\"Favorite Past Time: {favoriteMessenger.PushPull(\"past-time\", subId)}\");\nConsole.WriteLine($\"Favorite Music: {favoriteMessenger.PushPull(\"music\", subId)}\");\n```\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n\u003e [!Note]\n\u003e The difference between _**one way**_ and _**two way**_ notifications is that _**one way**_ notifications enable data travel in one direction whereas _**two way**_ notifications enable data travel in both directions.  The terms 'Push', 'Pull', 'Receive', and 'Respond' should give a clue as to the direction of travel of the data.\n\n\n\u003e [!Tip]\n\u003e Most of the time, the built in reactable implementations will suit your needs.  However, if you have any requirements that these can't provide, you can always create your own custom implementations using the interfaces provided.\n\n\n\u003ch2 style=\"font-weight:bold;\" align=\"center\"\u003e🙏🏼 Contributing 🙏🏼\u003c/h2\u003e\n\nInterested in contributing? If so, click [here](https://github.com/KinsonDigital/.github/blob/main/docs/CONTRIBUTING.md) to learn how to contribute your time or [here](https://github.com/sponsors/KinsonDigital) if you are interested in contributing your funds via a one-time or recurring donation.\n\n\u003ch2 style=\"font-weight:bold;border:0\" align=\"center\"\u003e🔧 Maintainers 🔧\u003c/h2\u003e\n\n![x-logo-dark-mode](https://raw.githubusercontent.com/KinsonDigital/.github/main/Images/x-logo-16x16-dark-mode.svg#gh-dark-mode-only)\n![x-logo-light-mode](https://raw.githubusercontent.com/KinsonDigital/.github/main/Images/x-logo-16x16-light-mode.svg#gh-light-mode-only)\n[Calvin Wilkinson](https://twitter.com/KDCoder) (KinsonDigital GitHub Organization - Owner)\n\n\n\u003ch2 style=\"font-weight:bold;border:0\" align=\"center\"\u003e🚔 Licensing and Governance 🚔\u003c/h2\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg?style=flat)](https://github.com/KinsonDigital/.github/blob/main/docs/code_of_conduct.md)\n[![GitHub](https://img.shields.io/github/license/kinsondigital/Carbonate)](https://github.com/KinsonDigital/Carbonate/blob/preview/v1.0.0/LICENSE.md)\n\n\u003cdiv align= \"left\"\u003e\n\nThis software is distributed under the very permissive MIT license and all dependencies are distributed under MIT-compatible licenses.\nThis project has adopted the code of conduct defined by the **Contributor Covenant** to clarify expected behavior in our community.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkinsondigital%2Fcarbonate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkinsondigital%2Fcarbonate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkinsondigital%2Fcarbonate/lists"}