{"id":18603025,"url":"https://github.com/devlooped/merq","last_synced_at":"2025-06-20T16:39:13.679Z","repository":{"id":47435180,"uuid":"514481521","full_name":"devlooped/Merq","owner":"devlooped","description":"Internal application architecture via command and event messages","archived":false,"fork":false,"pushed_at":"2025-03-18T00:15:04.000Z","size":1417,"stargazers_count":26,"open_issues_count":7,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-25T03:34:05.313Z","etag":null,"topics":["dotnet","smart-nuget"],"latest_commit_sha":null,"homepage":"https://clarius.org/Merq","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/devlooped.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"devlooped"}},"created_at":"2022-07-16T04:57:23.000Z","updated_at":"2025-02-18T06:28:48.000Z","dependencies_parsed_at":"2024-06-13T14:45:08.849Z","dependency_job_id":"f01e4ea6-64dd-4f98-85f3-13628a476b2f","html_url":"https://github.com/devlooped/Merq","commit_stats":{"total_commits":440,"total_committers":13,"mean_commits":33.84615384615385,"dds":"0.15681818181818186","last_synced_commit":"3120db8f10d7b6d2d95ce339430b71c0e4f99db0"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FMerq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FMerq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FMerq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FMerq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devlooped","download_url":"https://codeload.github.com/devlooped/Merq/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248281400,"owners_count":21077423,"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":["dotnet","smart-nuget"],"created_at":"2024-11-07T02:13:16.907Z","updated_at":"2025-04-10T19:31:19.898Z","avatar_url":"https://github.com/devlooped.png","language":"C#","funding_links":["https://github.com/sponsors/devlooped","https://github.com/sponsors"],"categories":[],"sub_categories":[],"readme":"![Icon](https://raw.github.com/devlooped/Merq/main/assets/img/32.png) Merq\n================\n\n[![Version](https://img.shields.io/nuget/vpre/Merq.svg?color=royalblue)](https://www.nuget.org/packages/Merq)\n[![Downloads](https://img.shields.io/nuget/dt/Merq.svg?color=green)](https://www.nuget.org/packages/Merq)\n[![License](https://img.shields.io/github/license/devlooped/Merq.svg?color=blue)](https://github.com/devlooped/Merq/blob/main/license.txt)\n\n\u003c!-- #core --\u003e\n\u003e **Mercury:** messenger of the Roman gods\n\n\u003e *Mercury* \u003e *Merq-ry* \u003e **Merq** \n\n\n**Merq** brings the [Message Bus](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff647328(v=pandp.10)) pattern together with \na [command-oriented interface](https://www.martinfowler.com/bliki/CommandOrientedInterface.html) for an \nextensible and decoupled in-process application architecture.\n\nThese patterns are well established in microservices and service oriented \narchitectures, but their benefits can be applied to apps too, especially \nextensible ones where multiple teams can contribute extensions which \nare composed at run-time.\n\nThe resulting improved decoupling between components makes it easier to evolve \nthem independently, while improving discoverability of available commands and \nevents. You can see this approach applied in the real world in \n[VSCode commands](https://code.visualstudio.com/api/extension-guides/command) \nand various events such as [window events](https://code.visualstudio.com/api/references/vscode-api#window). \nClearly, in the case of VSCode, everything is in-process, but the benefits of \na clean and predictable API are pretty obvious.\n\n*Merq* provides the same capabilities for .NET apps. \n\n## Events\n\nEvents can be any type, there is no restriction or interfaces you must implement.\nNowadays, [C# record types](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records) \nare a perfect fit for event data types. An example event could be a one-liner such as:\n\n```csharp\npublic record ItemShipped(string Id, DateTimeOffset Date);\n```\n\nThe events-based API surface on the message bus is simple enough:\n\n```csharp\npublic interface IMessageBus\n{\n    void Notify\u003cTEvent\u003e(TEvent e);\n    IObservable\u003cTEvent\u003e Observe\u003cTEvent\u003e();\n}\n```\n\nBy relying on `IObservable\u003cTEvent\u003e`, *Merq* integrates seamlessly with \nmore powerful event-driven handling via [System.Reactive](http://nuget.org/packages/system.reactive) \nor the more lightweight [RxFree](https://www.nuget.org/packages/RxFree).\nSubscribing to events with either of those packages is trivial:\n\n```csharp\nIDisposable subscription;\n\n// constructor may use DI to get the dependency\npublic CustomerViewModel(IMessageBus bus)\n{\n    subscription = bus.Observe\u003cItemShipped\u003e().Subscribe(OnItemShipped);\n}\n\nvoid OnItemShipped(ItemShipped e) =\u003e // Refresh item status\n\npublic void Dispose() =\u003e subscription.Dispose();\n```\n\nIn addition to event producers just invoking `Notify`, they can also be \nimplemented as `IObservable\u003cTEvent\u003e` directly, which is useful when the\nproducer is itself an observable sequence. \n\nBoth features integrate seamlessly and leverage all the power of \n[Reactive Extensions](https://github.com/dotnet/reactive).\n\n\n## Commands\n\nCommands can also be any type, and C# records make for concise definitions:\n\n```csharp\nrecord CancelOrder(string OrderId) : IAsyncCommand;\n```\n\nUnlike events, command messages need to signal the invocation style they require \nfor execution:\n\n| Scenario | Interface | Invocation |\n| --- | --- | --- |\n| void synchronous command | `ICommand` | `IMessageBus.Execute(command)` |\n| value-returning synchronous command | `ICommand\u003cTResult\u003e` | `var result = await IMessageBus.Execute(command)` |\n| void asynchronous command | `IAsyncCommand` | `await IMessageBus.ExecuteAsync(command)` |\n| value-returning asynchronous command | `IAsyncCommand\u003cTResult\u003e` | `var result = await IMessageBus.ExecuteAsync(command)` |\n| async stream command | `IStreamCommand\u003cTResult\u003e` | `await foreach(var item in IMessageBus.ExecuteStream(command))` |\n\nThe sample command shown before can be executed using the following code:\n\n```csharp\n// perhaps a method invoked when a user \n// clicks/taps a Cancel button next to an order\nasync Task OnCancel(string orderId)\n{\n    await bus.ExecuteAsync(new CancelOrder(orderId), CancellationToken.None);\n    // refresh UI for new state.\n}\n```\n\nAn example of a synchronous command could be:\n\n```csharp\n// Command declaration\nrecord SignOut() : ICommand;\n\n// Command invocation\nvoid OnSignOut() =\u003e bus.Execute(new SignOut());\n\n// or alternatively, for void commands that have no additional data:\nvoid OnSignOut() =\u003e bus.Execute\u003cSignOut\u003e();\n```\n\nThe marker interfaces on the command messages drive the compiler to only allow \nthe right invocation style on the message bus, as defined by the command author:\n\n```csharp\npublic interface IMessageBus\n{\n    // sync void\n    void Execute(ICommand command);\n    // sync value-returning\n    TResult Execute\u003cTResult\u003e(ICommand\u003cTResult\u003e command);\n    // async void\n    Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation);\n    // async value-returning\n    Task\u003cTResult\u003e ExecuteAsync\u003cTResult\u003e(IAsyncCommand\u003cTResult\u003e command, CancellationToken cancellation);\n    // async stream\n    IAsyncEnumerable\u003cTResult\u003e ExecuteStream\u003cTResult\u003e(IStreamCommand\u003cTResult\u003e command, CancellationToken cancellation);\n}\n```\n\nFor example, to create a value-returning async command that retrieves some \nvalue, you would have:\n\n```csharp\nrecord FindDocuments(string Filter) : IAsyncCommand\u003cIEnumerable\u003cstring\u003e\u003e;\n\nclass FindDocumentsHandler : IAsyncCommandHandler\u003cFindDocument, IEnumerable\u003cstring\u003e\u003e\n{\n    public bool CanExecute(FindDocument command) =\u003e !string.IsNullOrEmpty(command.Filter);\n    \n    public Task\u003cIEnumerable\u003cstring\u003e\u003e ExecuteAsync(FindDocument command, CancellationToken cancellation)\n        =\u003e // evaluate command.Filter across all documents and return matches\n}\n```\n\nIn order to execute such command, the only execute method the compiler will allow \nis:\n\n```csharp\nIEnumerable\u003cstring\u003e files = await bus.ExecuteAsync(new FindDocuments(\"*.json\"));\n```\n\nIf the consumer tries to use `Execute`, the compiler will complain that the \ncommand does not implement `ICommand\u003cTResult\u003e`, which is the synchronous version \nof the marker interface. \n\nWhile these marker interfaces on the command messages might seem unnecessary, \nthey are actually quite important. They solve a key problem that execution\nabstractions face: whether a command execution is synchronous or asynchronous \n(as well as void or value-returning) should *not* be abstracted away since \notherwise you can end up in two common anti-patterns (i.e. [async guidelines for ASP.NET](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md)), \nknown as [sync over async](https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/) and \n[async over sync](https://devblogs.microsoft.com/pfxteam/should-i-expose-asynchronous-wrappers-for-synchronous-methods/).\n\nLikewise, mistakes cannot be made when implementing the handler, since the \nhandler interfaces define constraints on what the commands must implement:\n\n```csharp\n// sync\npublic interface ICommandHandler\u003cin TCommand\u003e : ... where TCommand : ICommand;\npublic interface ICommandHandler\u003cin TCommand, out TResult\u003e : ... where TCommand : ICommand\u003cTResult\u003e;\n\n// async\npublic interface IAsyncCommandHandler\u003cin TCommand\u003e : ... where TCommand : IAsyncCommand;\npublic interface IAsyncCommandHandler\u003cin TCommand, TResult\u003e : ... where TCommand : IAsyncCommand\u003cTResult\u003e\n\n// async stream\npublic interface IStreamCommandHandler\u003cin TCommand, out TResult\u003e: ... where TCommand : IStreamCommand\u003cTResult\u003e\n```\n\nThis design choice also makes it impossible to end up executing a command\nimplementation improperly.\n\nIn addition to execution, the `IMessageBus` also provides a mechanism to determine \nif a command has a registered handler at all via the `CanHandle\u003cT\u003e` method as well \nas a validation mechanism via `CanExecute\u003cT\u003e`, as shown above in the `FindDocumentsHandler` example.\n\nCommands can notify new events, and event observers/subscribers can in turn \nexecute commands.\n\n### Async Streams\n\nFor .NET6+ apps, *Merq* also supports [async streams](https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/generate-consume-asynchronous-stream) \nas a command invocation style. This is useful for scenarios where the command\nexecution produces a potentially large number of results, and the consumer\nwants to process them as they are produced, rather than waiting for the entire\nsequence to be produced.\n\nFor example, the filter documents command above could be implemented as an \nasync stream command instead:\n\n```csharp\nrecord FindDocuments(string Filter) : IStreamCommand\u003cstring\u003e;\n\nclass FindDocumentsHandler : IStreamCommandHandler\u003cFindDocument, string\u003e\n{\n    public bool CanExecute(FindDocument command) =\u003e !string.IsNullOrEmpty(command.Filter);\n    \n    public async IAsyncEnumerable\u003cstring\u003e ExecuteAsync(FindDocument command, [EnumeratorCancellation] CancellationToken cancellation)\n    {\n        await foreach (var file in FindFilesAsync(command.Filter, cancellation))\n            yield return file;\n    }\n}\n```\n\nIn order to execute such command, the only execute method the compiler will allow \nis:\n\n```csharp\nawait foreach (var file in bus.ExecuteStream(new FindDocuments(\"*.json\")))\n    Console.WriteLine(file);\n```\n\n\n## Analyzers and Code Fixes\n\nBeyond the compiler complaining, *Merq* also provides a set of analyzers and \ncode fixes to learn the patterns and avoid common mistakes. For example, if you\ncreated a simple record to use as a command, such as:\n\n```csharp\npublic record Echo(string Message);\n```\n\nAnd then tried to implement a command handler for it:\n\n```csharp\npublic class EchoHandler : ICommandHandler\u003cEcho\u003e\n{\n}\n```\n\nthe compiler would immediately complain about various contraints and interfaces \nthat aren't satisfied due to the requirements on the `Echo` type itself. For \na seasoned *Merq* developer, this is a no-brainer, but for new developers, \nit can be a bit puzzling:\n\n![compiler warnings screenshot](https://raw.githubusercontent.com/devlooped/Merq/main/assets/img/command-interfaces.png)\n\nA code fix is provided to automatically implement the required interfaces \nin this case:\n\n![code fix to implement ICommand screenshot](https://raw.githubusercontent.com/devlooped/Merq/main/assets/img/implement-icommand.png)\n\nLikewise, if a consumer attempted to invoke the above `Echo` command asynchronously \n(known as the [async over sync anti-pattern](https://devblogs.microsoft.com/pfxteam/should-i-expose-asynchronous-wrappers-for-synchronous-methods/)), \nthey would get a somewhat unintuitive compiler error:\n\n![error executing sync command as async](https://raw.githubusercontent.com/devlooped/Merq/main/assets/img/async-sync-command.png)\n\nBut the second error is more helpful, since it points to the actual problem, \nand a code fix can be applied to resolve it:\n\n![code fix for executing sync command as async](https://raw.githubusercontent.com/devlooped/Merq/main/assets/img/async-sync-command-fix.png)\n\nThe same analyzers and code fixes are provided for the opposite anti-pattern, \nknown as [sync over async](https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/), \nwhere a synchronous command is executed asynchronously.\n\n\u003c!-- #core --\u003e\n\n## Message Bus\n\nThe default implementation lives in a separate package [Merq.Core](https://www.nuget.org/packages/Merq.Core) \nso that application components can take a dependency on just the interfaces.\n\n[![Version](https://img.shields.io/nuget/vpre/Merq.Core.svg?color=royalblue)](https://www.nuget.org/packages/Merq.Core)\n[![Downloads](https://img.shields.io/nuget/dt/Merq.Core.svg?color=green)](https://www.nuget.org/packages/Merq.Core)\n\n\u003c!-- #implementation --\u003e\nThe default implementation of the message bus interface `IMessageBus` has \nno external dependencies and can be instantiated via the `MessageBus` constructor \ndirectly.\n\nThe bus locates command handlers and event producers via the passed-in \n`IServiceProvider` instance in the constructor:\n\n```csharp\nvar bus = new MessageBus(serviceProvider);\n\n// execute a command\nbus.Execute(new MyCommand());\n\n// observe an event from the bus\nbus.Observe\u003cMyEvent\u003e().Subscribe(e =\u003e Console.WriteLine(e.Message));\n```\n\n\n\u003c!-- #implementation --\u003e\n\nWhen using [dependency injection for .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection), \nthe [Merq.DependencyInjection](https://www.nuget.org/packages/Merq.DependencyInjection) package \nprovides a simple mechanism for registering the message bus:\n\n\u003c!-- #di --\u003e\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n...\nbuilder.Services.AddMessageBus();\n```\n\nAll command handlers and event producers need to be registered with the \nservices collection as usual, using the main interface for the component, \nsuch as `ICommandHandler\u003cT\u003e` and `IObservable\u003cTEvent\u003e`. \n\n\u003e NOTE: *Merq* makes no assumptions about the lifetime of the registered \n\u003e components, so it's up to the consumer to register them with the desired\n\u003e lifetime.\n\nTo drastically simplify registration of handlers and producers, we \nrecommend the [Devlooped.Extensions.DependencyInjection.Attributed](https://www.nuget.org/packages/Devlooped.Extensions.DependencyInjection.Attributed/).\npackage, which provides a simple attribute-based mechanism for automatically \nemitting at compile-time the required service registrations for all types \nmarked with the provided `[Service]` attribute, which also allows setting the \ncomponent lifetime, such as `[Service(ServiceLifetime.Transient)]` (default \nlifetime is `ServiceLifetime.Singleton` for this source generator-based \npackage).\n\nThis allows to simply mark all command handlers and event producers as \n`[Service]` and then register them all with a single line of code:\n\n```csharp\nbuilder.Services.AddServices();\n```\n\n### Telemetry and Monitoring\n\nThe core implementation of the `IMessageBus` is instrumented with `ActivitySource` and \n`Metric`, providing out of the box support for [Open Telemetry](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs)-based monitoring, as well \nas via [dotnet trace](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) \nand [dotnet counters](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters).\n\nTo export telemetry using [Open Telemetry](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs), \nfor example:\n\n```csharp\nusing var tracer = Sdk\n    .CreateTracerProviderBuilder()\n    .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(\"ConsoleApp\"))\n    .AddSource(source.Name)\n    .AddSource(\"Merq\")\n    .AddConsoleExporter()\n    .AddZipkinExporter()\n    .AddAzureMonitorTraceExporter(o =\u003e o.ConnectionString = config[\"AppInsights\"])\n    .Build();\n```\n\nCollecting traces via [dotnet-trace](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace):\n\n```shell\ndotnet trace collect --name [PROCESS_NAME] --providers=\"Microsoft-Diagnostics-DiagnosticSource:::FilterAndPayloadSpecs=[AS]Merq,System.Diagnostics.Metrics:::Metrics=Merq\"\n```\n\nMonitoring metrics via [dotnet-counters](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters):\n\n```shell\ndotnet counters monitor --process-id [PROCESS_ID] --counters Merq\n```\n\nExample rendering from the included sample console app:\n\n![dotnet-counters screenshot](https://raw.githubusercontent.com/devlooped/Merq/main/assets/img/dotnet-counters.png)\n\n## Duck Typing Support\n\n\u003c!-- #duck --\u003e\nBeing able to loosely couple both events (and their consumers) and command execution (from their \ncommand handler implementations) is a key feature of Merq. To take this decoupling to the extreme, \nMerq allows a similar capability as allowed by the TypeScript/JavaScript in VSCode: you can just \ncopy/paste an event/command definition as *source* into your assembly, and perform the regular \noperations with it (like `Observe` an event and `Execute` a command), in a \"duck typing\" manner.\n\nAs long as the types' full name match, the conversion will happen automatically. Since this \nfunctionality isn't required in many scenarios, and since there are a myriad ways to implement \nsuch an object mapping functionality, the `Merq.Core` package only provides the hooks to enable \nthis, but does not provide any built-in implementation for it. In other words, no duck typing \nis performed by default.\n\nThe [Merq.AutoMapper](https://www.nuget.org/packages/Merq.AutoMapper) package provides one such \nimplementation, based on the excelent [AutoMapper](https://automapper.org/) library. It can be \nregistered with the DI container as follows:\n\n```csharp\nbuilder.Services.AddMessageBus\u003cAutoMapperMessageBus\u003e();\n// register all services, including handlers and producers\nbuilder.Services.AddServices();\n```\n\n\u003c!-- #duck --\u003e\n\n\u003c!-- #ci --\u003e\n\n# Dogfooding\n\n[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.dev/vpre/Devlooped.Merq/main\u0026label=nuget.ci\u0026color=brightgreen)](https://pkg.kzu.dev/index.json)\n[![Build](https://github.com/devlooped/Merq/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/Merq/actions)\n\nWe also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced. \n\nThe CI feed is `https://pkg.kzu.dev/index.json`. \n\nThe versioning scheme for packages is:\n\n- PR builds: *42.42.42-pr*`[NUMBER]`\n- Branch builds: *42.42.42-*`[BRANCH]`.`[COMMITS]`\n\n\u003c!-- #sponsors --\u003e\n\u003c!-- include https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n# Sponsors \n\n\u003c!-- sponsors.md --\u003e\n[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png \"Clarius Org\")](https://github.com/clarius)\n[![Kirill Osenkov](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KirillOsenkov.png \"Kirill Osenkov\")](https://github.com/KirillOsenkov)\n[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png \"MFB Technologies, Inc.\")](https://github.com/MFB-Technologies-Inc)\n[![Stephen Shaw](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/decriptor.png \"Stephen Shaw\")](https://github.com/decriptor)\n[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png \"Torutek\")](https://github.com/torutek-gh)\n[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png \"DRIVE.NET, Inc.\")](https://github.com/drivenet)\n[![Daniel Gnägi](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dgnaegi.png \"Daniel Gnägi\")](https://github.com/dgnaegi)\n[![Ashley Medway](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/AshleyMedway.png \"Ashley Medway\")](https://github.com/AshleyMedway)\n[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png \"Keith Pickford\")](https://github.com/Keflon)\n[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png \"Thomas Bolon\")](https://github.com/tbolon)\n[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png \"Kori Francis\")](https://github.com/kfrancis)\n[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png \"Toni Wenzel\")](https://github.com/twenzel)\n[![Giorgi Dalakishvili](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Giorgi.png \"Giorgi Dalakishvili\")](https://github.com/Giorgi)\n[![Mike James](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MikeCodesDotNET.png \"Mike James\")](https://github.com/MikeCodesDotNET)\n[![Dan Siegel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dansiegel.png \"Dan Siegel\")](https://github.com/dansiegel)\n[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png \"Reuben Swartz\")](https://github.com/rbnswartz)\n[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png \"Jacob Foshee\")](https://github.com/jfoshee)\n[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png \"\")](https://github.com/Mrxx99)\n[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png \"Eric Johnson\")](https://github.com/eajhnsn1)\n[![Norman Mackay](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/mackayn.png \"Norman Mackay\")](https://github.com/mackayn)\n[![Certify The Web](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/certifytheweb.png \"Certify The Web\")](https://github.com/certifytheweb)\n[![Ix Technologies B.V.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/IxTechnologies.png \"Ix Technologies B.V.\")](https://github.com/IxTechnologies)\n[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png \"David JENNI\")](https://github.com/davidjenni)\n[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png \"Jonathan \")](https://github.com/Jonathan-Hickey)\n[![Oleg Kyrylchuk](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/okyrylchuk.png \"Oleg Kyrylchuk\")](https://github.com/okyrylchuk)\n[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png \"Charley Wu\")](https://github.com/akunzai)\n[![Jakob Tikjøb Andersen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jakobt.png \"Jakob Tikjøb Andersen\")](https://github.com/jakobt)\n[![Seann Alexander](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/seanalexander.png \"Seann Alexander\")](https://github.com/seanalexander)\n[![Tino Hager](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tinohager.png \"Tino Hager\")](https://github.com/tinohager)\n[![Mark Seemann](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ploeh.png \"Mark Seemann\")](https://github.com/ploeh)\n[![Angelo Belchior](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/angelobelchior.png \"Angelo Belchior\")](https://github.com/angelobelchior)\n[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png \"Ken Bonny\")](https://github.com/KenBonny)\n[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png \"Simon Cropp\")](https://github.com/SimonCropp)\n[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png \"agileworks-eu\")](https://github.com/agileworks-eu)\n[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sorahex.png \"\")](https://github.com/sorahex)\n[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png \"Zheyu Shen\")](https://github.com/arsdragonfly)\n[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png \"Vezel\")](https://github.com/vezel-dev)\n\n\n\u003c!-- sponsors.md --\u003e\n\n[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png \"Sponsor this project\")](https://github.com/sponsors/devlooped)\n\u0026nbsp;\n\n[Learn more about GitHub Sponsors](https://github.com/sponsors)\n\n\u003c!-- https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fmerq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevlooped%2Fmerq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fmerq/lists"}