Skip to content

A simulator that provides endpoints to mimic the functionality of Azure Event Grid topics and subscribers and is compatible with the Azure.Messaging.EventGrid client library.

License

Notifications You must be signed in to change notification settings

pm7y/AzureEventGridSimulator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Azure Event Grid Simulator

GitHub Workflow Status GitHub contributors GitHub tag (latest SemVer) GitHub all releases Docker Pulls NuGet Version

A simulator that provides endpoints to mimic the functionality of Azure Event Grid topics and subscribers and is compatible with the Microsoft.Azure.EventGrid client library. Both the EventGrid schema and the CloudEvents v1.0 schema are supported.

Note: This simulator is intended for local development and testing only. It is not designed for production use. For production workloads, use the official Azure Event Grid service.

Installation

.NET Tool (Recommended)

Requires .NET 8.0 SDK or later. Supports .NET 8.0, 9.0, and 10.0.

Global Install

Install once, use from anywhere:

dotnet tool install -g AzureEventGridSimulator
azure-eventgrid-simulator

To update: dotnet tool update -g AzureEventGridSimulator

Local Install (Project-level)

Install per-project for team consistency and version control:

# Create tool manifest if it doesn't exist
dotnet new tool-manifest

# Install the tool
dotnet tool install AzureEventGridSimulator

# Run it
dotnet tool run azure-eventgrid-simulator

The tool manifest (.config/dotnet-tools.json) can be committed to source control. Team members just run dotnet tool restore after cloning.

Docker

See the Docker section below for running via Docker.

Binary Release

Download standalone executables from GitHub Releases.

Configuration

Topics and their subscribers are configured in the appsettings.json file.

You can add multiple topics. Each topic must have a unique port. Each topic can have multiple subscribers. An example of one topic with one subscriber is shown below.

{
  "topics": [
    {
      "name": "MyAwesomeTopic",
      "port": 60101,
      "key": "TheLocal+DevelopmentKey=",
      "subscribers": [
        {
          "name": "LocalAzureFunctionSubscription",
          "endpoint": "http://localhost:7071/runtime/webhooks/EventGrid?functionName=PersistEventToDb",
          "disableValidation": true
        }
      ]
    }
  ]
}

Topic Settings

Setting Description
name The name of the topic. It can only contain letters, numbers, and dashes.
port The port to use for the topic endpoint. The topic will listen on https://0.0.0.0:{port}/.
key The key that will be used to validate the aeg-sas-key or aeg-sas-token header in each request. If this is not supplied then no key validation will take place.
subscribers The subscriptions for this topic.
inputSchema (Optional) The expected input event schema. Values: EventGridSchema or CloudEventV1_0. If not specified, the schema is auto-detected from the request.
outputSchema (Optional) The output event schema for delivery to subscribers. If not specified, events are delivered in the same schema they were received in.
serviceBusConnectionString (Optional) Default Service Bus connection string for all Service Bus subscribers in this topic. Subscribers can override with their own.
serviceBusNamespace (Optional) Default Service Bus namespace (without .servicebus.windows.net). Use with serviceBusSharedAccessKeyName and serviceBusSharedAccessKey.
serviceBusSharedAccessKeyName (Optional) Default shared access key name for Service Bus.
serviceBusSharedAccessKey (Optional) Default shared access key for Service Bus.
storageQueueConnectionString (Optional) Default Storage Queue connection string for all Storage Queue subscribers in this topic. Subscribers can override with their own.

Subscriber Settings

The simulator supports three subscriber types: HTTP webhooks, Azure Service Bus (queues and topics), and Azure Storage Queues.

Grouped Format (Recommended)

{
  "subscribers": {
    "http": [
      {
        "name": "WebhookSubscription",
        "endpoint": "https://example.com/webhook"
      }
    ],
    "serviceBus": [
      {
        "name": "ServiceBusSubscription",
        "connectionString": "Endpoint=sb://my-namespace.servicebus.windows.net/;SharedAccessKeyName=...;SharedAccessKey=...",
        "queue": "my-queue"
      }
    ],
    "storageQueue": [
      {
        "name": "StorageQueueSubscription",
        "connectionString": "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net",
        "queueName": "my-queue"
      }
    ]
  }
}

Legacy Format (Still Supported)

For backwards compatibility, a flat array of HTTP subscribers is still supported:

{
  "subscribers": [
    {
      "name": "WebhookSubscription",
      "endpoint": "https://example.com/webhook"
    }
  ]
}

HTTP Subscriber Settings

Setting Description
name The name of the subscriber. It can only contain letters, numbers, and dashes.
endpoint The subscription endpoint url. Events received by topic will be sent to this address.
disableValidation Set to true to disable subscription validation. Default is false, which means subscription validation will be attempted each time the simulator starts.
deliverySchema (Optional) Override the delivery schema for this specific subscriber. Values: EventGridSchema or CloudEventV1_0. Takes precedence over the topic's outputSchema.
retryPolicy (Optional) Retry policy settings. See Retry & Dead-Letter section.
deadLetter (Optional) Dead-letter settings. See Retry & Dead-Letter section.

Service Bus Subscriber Settings

Setting Description
name The name of the subscriber.
connectionString The Service Bus connection string. Can be omitted if serviceBusConnectionString is set at the topic level.
namespace The Service Bus namespace (without .servicebus.windows.net suffix). Alternative to connectionString. Can inherit from topic-level settings.
sharedAccessKeyName The shared access key name (e.g., RootManageSharedAccessKey). Used with namespace.
sharedAccessKey The shared access key. Used with namespace.
queue The queue name. Either queue or topic must be specified (not both).
topic The topic name. Either queue or topic must be specified (not both).
deliverySchema (Optional) Override the delivery schema. Values: EventGridSchema or CloudEventV1_0.
properties (Optional) Custom delivery properties to add to Service Bus messages. See below.
retryPolicy (Optional) Retry policy settings. See Retry & Dead-Letter section.
deadLetter (Optional) Dead-letter settings. See Retry & Dead-Letter section.

Service Bus Delivery Properties

You can add custom application properties to Service Bus messages using static or dynamic values:

{
  "properties": {
    "Region": {
      "type": "static",
      "value": "west-us"
    },
    "EventType": {
      "type": "dynamic",
      "value": "EventType"
    },
    "CustomerId": {
      "type": "dynamic",
      "value": "data.customerId"
    }
  }
}
  • Static properties: The value is used as-is.
  • Dynamic properties: The value is a path to extract from the event. Supported paths:
    • Top-level fields: Id, Subject, EventType, EventTime, DataVersion, Source/Topic
    • Data properties: data.propertyName or data.nested.property

Storage Queue Subscriber Settings

Setting Description
name The name of the subscriber.
connectionString The Storage Queue connection string. Can be omitted if storageQueueConnectionString is set at the topic level.
queueName The name of the queue to send events to.
deliverySchema (Optional) Override the delivery schema. Values: EventGridSchema or CloudEventV1_0.
retryPolicy (Optional) Retry policy settings. See Retry & Dead-Letter section.
deadLetter (Optional) Dead-letter settings. See Retry & Dead-Letter section.

Complete Example

{
  "topics": [
    {
      "name": "OrdersTopic",
      "port": 60101,
      "key": "TheLocal+DevelopmentKey=",
      "serviceBusConnectionString": "Endpoint=sb://my-namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=...",
      "storageQueueConnectionString": "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=...;EndpointSuffix=core.windows.net",
      "subscribers": {
        "http": [
          {
            "name": "WebhookSubscription",
            "endpoint": "https://myapp.com/webhooks/orders",
            "disableValidation": true,
            "filter": {
              "includedEventTypes": ["Order.Created", "Order.Updated"]
            }
          }
        ],
        "serviceBus": [
          {
            "name": "OrdersQueueSubscription",
            "queue": "orders-queue",
            "properties": {
              "OrderId": { "type": "dynamic", "value": "data.orderId" },
              "EventType": { "type": "dynamic", "value": "EventType" },
              "Source": { "type": "static", "value": "EventGridSimulator" }
            }
          }
        ],
        "storageQueue": [
          {
            "name": "OrdersStorageQueueSubscription",
            "queueName": "orders-archive"
          }
        ]
      }
    }
  ]
}

Note: The serviceBus and storageQueue subscribers above inherit their connection strings from the topic-level settings. Subscribers can override these by specifying their own connectionString.

App Settings

Setting Description
dangerousAcceptAnyServerCertificateValidator Set to true to accept any server certificate. Useful when testing with self-signed certificates.

Subscription Validation

When a subscription is added to Azure Event Grid it first sends a validation event to the subscription endpoint. The validation event contains a validationCode which the subscription endpoint must echo back. If this does not occur then Azure Event Grid will not enable the subscription.

More information about subscription validation can be found at https://docs.microsoft.com/en-us/azure/event-grid/webhook-event-delivery.

The Azure Event Grid Simualator will mimick this validation behaviour at start up but it can be disabled using the disableValidation setting (above).

Filtering Events

Event filtering is configurable on each subscriber using the filter model defined here: https://docs.microsoft.com/en-us/azure/event-grid/event-filtering. This page provides a full guide to the configuration options available and all parts of this guide are currently supported. For ease of transition, explicit limitations have also been adhered to. The restrictions mentioned have been further modified (https://azure.microsoft.com/en-us/updates/advanced-filtering-generally-available-in-event-grid/) and these new less restrictive filtering limits have been observed.

Extending the example above to include a basic filter which will only deliver events to the subscription if they are of a specific type is illustrated below.

{
  "topics": [
    {
      "name": "MyAwesomeTopic",
      "port": 60101,
      "key": "TheLocal+DevelopmentKey=",
      "subscribers": [
        {
          "name": "LocalAzureFunctionSubscription",
          "endpoint": "http://localhost:7071/runtime/webhooks/EventGrid?functionName=PersistEventToDb",
          "filter": {
            "includedEventTypes": ["my.eventType"]
          }
        }
      ]
    }
  ]
}

This can be extended to allow subject filtering:

"filter": {
  "subjectBeginsWith": "/blobServices/default/containers/mycontainer/log",
  "subjectEndsWith": ".jpg",
  "isSubjectCaseSensitive": true
}

or advanced filtering:

"filter": {
  "advancedFilters": [
    {
      "operatorType": "NumberGreaterThanOrEquals",
      "key": "Data.Key1",
      "value": 5
    },
    {
      "operatorType": "StringContains",
      "key": "Subject",
      "values": ["container1", "container2"]
    }
  ]
}

Advanced Filter Operators

Each advanced filter requires an operatorType, a key (the event property to filter on), and either a value, values, or no value property depending on the operator type. Up to 25 advanced filters can be configured per subscription.

Operator Property Description
NumberGreaterThan value Event value must be greater than the specified number
NumberGreaterThanOrEquals value Event value must be greater than or equal to the specified number
NumberLessThan value Event value must be less than the specified number
NumberLessThanOrEquals value Event value must be less than or equal to the specified number
NumberIn values Event value must match one of the specified numbers (max 5 values)
NumberNotIn values Event value must not match any of the specified numbers (max 5 values)
NumberInRange values Event value must be within one of the specified ranges (e.g., [[0, 10], [20, 30]])
NumberNotInRange values Event value must not be within any of the specified ranges
BoolEquals value Event value must equal the specified boolean
StringContains values Event value must contain at least one of the specified strings
StringNotContains values Event value must not contain any of the specified strings
StringBeginsWith values Event value must begin with at least one of the specified strings
StringNotBeginsWith values Event value must not begin with any of the specified strings
StringEndsWith values Event value must end with at least one of the specified strings
StringNotEndsWith values Event value must not end with any of the specified strings
StringIn values Event value must match one of the specified strings (max 5 values)
StringNotIn values Event value must not match any of the specified strings (max 5 values)
IsNullOrUndefined (none) Key must be null or not exist
IsNotNull (none) Key must exist and have a non-null value

Note: The key property supports nested data properties using dot notation, e.g., Data.MyProperty or Data.Nested.Value.

Note: String comparisons in advanced filters are case-insensitive. "Not" operators (StringNotIn, NumberNotIn, etc.) return true when the key doesn't exist.

Note: you can also specify the configuration file to use by setting the ConfigFile command line argument, e.g.

AzureEventGridSimulator.exe --ConfigFile=/path/to/config.json

Retry & Dead-Letter

The simulator supports Azure Event Grid-compatible retry and dead-letter behavior. When delivery to a subscriber fails, the event is automatically retried with exponential backoff. Events that cannot be delivered after all retry attempts are written to a dead-letter folder as JSON files.

Retry is enabled by default (matching Azure Event Grid behavior). You can configure retry and dead-letter settings per subscriber.

Retry Schedule

The retry schedule follows Azure Event Grid's exponential backoff:

Attempt Delay
1 10 sec
2 30 sec
3 1 min
4 5 min
5 10 min
6 30 min
7 1 hour
8 3 hours
9 6 hours
10+ 12 hours

After attempt 10, retries continue every 12 hours until the event TTL expires (default 24 hours).

HTTP Status Code Handling

Status Code Behavior
200-204 Success - delivery complete
400, 401, 403 Immediate dead-letter (no retry)
413 Immediate dead-letter (payload too large)
404 Retry with minimum 5 minute delay
408 Retry with minimum 2 minute delay
503 Retry with minimum 30 second delay
Other errors Retry with standard exponential backoff

Retry Policy Settings

Configure retry behavior per subscriber:

{
  "retryPolicy": {
    "enabled": true,
    "maxDeliveryAttempts": 30,
    "eventTimeToLiveInMinutes": 1440
  }
}
Setting Description Default
enabled Enable or disable retry for this subscriber true
maxDeliveryAttempts Maximum delivery attempts (1-30) 30
eventTimeToLiveInMinutes Time-to-live in minutes before event expires (1-1440) 1440

Dead-Letter Settings

Configure dead-letter behavior per subscriber:

{
  "deadLetter": {
    "enabled": true,
    "folderPath": "./dead-letters"
  }
}
Setting Description Default
enabled Enable or disable dead-lettering true
folderPath Folder path for dead-letter JSON files ./dead-letters

Dead-Letter File Format

Failed events are written to: {folderPath}/{topicName}/{subscriberName}/{timestamp}_{eventId}.json

{
  "deadLetterReason": "MaxDeliveryAttemptsExceeded",
  "deliveryAttempts": 10,
  "lastDeliveryOutcome": "HttpError",
  "lastHttpStatusCode": 503,
  "lastErrorMessage": "Service Unavailable",
  "publishTime": "2025-01-15T10:30:00Z",
  "lastDeliveryAttemptTime": "2025-01-15T11:30:00Z",
  "topicName": "OrdersTopic",
  "subscriberName": "MyWebhook",
  "subscriberType": "http",
  "event": { }
}

Complete Example with Retry & Dead-Letter

{
  "topics": [
    {
      "name": "OrdersTopic",
      "port": 60101,
      "key": "TheLocal+DevelopmentKey=",
      "subscribers": {
        "http": [
          {
            "name": "MyWebhook",
            "endpoint": "https://myapp.com/webhooks/orders",
            "disableValidation": true,
            "retryPolicy": {
              "enabled": true,
              "maxDeliveryAttempts": 10,
              "eventTimeToLiveInMinutes": 60
            },
            "deadLetter": {
              "enabled": true,
              "folderPath": "./dead-letters"
            }
          }
        ]
      }
    }
  ]
}

Disabling Retry (Fire-and-Forget)

To restore the previous fire-and-forget behavior:

{
  "retryPolicy": {
    "enabled": false
  }
}

Docker

There's a published image available on the ↗ Docker hub called pmcilreavy/azureeventgridsimulator:latest. The image is not configured with any topics or subscribers. The configuration can be passed in via command line environment variables (as below) or via a json file.

Docker Run

Here's an example of running a container based on that image and passing in the configuration via environment variables to create 1 topic with 2 subscribers. In this example the folder C:\src\AzureEventGridSimulator\docker on the host is being shared with the container. * Note:* see the notes section further below on how to create a certificate file.

docker run `
        --detach `
        --publish 60101:60101 `
        -v C:\src\AzureEventGridSimulator\docker:/aegs `
        -e ASPNETCORE_ENVIRONMENT=Development `
        -e ASPNETCORE_Kestrel__Certificates__Default__Path=/aegs/azureEventGridSimulator.pfx `
        -e ASPNETCORE_Kestrel__Certificates__Default__Password=Y0urSup3rCrypt1cPa55w0rd! `
        -e TZ=Australia/Brisbane `
        -e AEGS_Topics__0__name=ExampleTopic `
        -e AEGS_Topics__0__port=60101 `
        -e AEGS_Topics__0__key=TheLocal+DevelopmentKey= `
        -e AEGS_Topics__0__subscribers__0__name=RequestCatcherSubscription `
        -e AEGS_Topics__0__subscribers__0__endpoint=https://azureeventgridsimulator.requestcatcher.com/ `
        -e AEGS_Topics__0__subscribers__0__disableValidation=true `
        -e AEGS_Topics__0__subscribers__1__name=AzureFunctionSubscription `
        -e AEGS_Topics__0__subscribers__1__endpoint=http://host.docker.internal:7071/runtime/webhooks/EventGrid?functionName=ExampleFunction `
        -e AEGS_Topics__0__subscribers__1__disableValidation=true `
        -e AEGS_Serilog__MinimumLevel__Default=Verbose `
        pmcilreavy/azureeventgridsimulator:latest

Docker Compose

There is a docker-compose.yml file in the docker/ folder that you can use to build and run the simulator along with an Azure Service Bus emulator for local development.

cd docker
docker-compose up --build --detach

The Docker Compose setup includes:

  • Azure Event Grid Simulator - The main simulator
  • Azure Service Bus Emulator - For testing Service Bus subscribers locally
  • SQL Server - Required by the Service Bus emulator

See docker/appsettings.docker.json for an example configuration with both HTTP and Service Bus subscribers.

Using the Simulator

Once configured and running, requests are posted to a topic endpoint. The endpoint of a topic will be in the form: https://localhost:<configured-port>/api/events?api-version=2018-01-01.

cURL Example (Event Grid Schema)

curl -k -H "Content-Type: application/json" -H "aeg-sas-key: TheLocal+DevelopmentKey=" -X POST "https://localhost:60101/api/events?api-version=2018-01-01" -d @Data.json

Data.json

[
  {
    "id": "8727823",
    "subject": "/example/subject",
    "data": {
      "MyProperty": "This is my awesome data!"
    },
    "eventType": "Example.DataType",
    "eventTime": "2019-01-01T00:00:00.000Z",
    "dataVersion": "1"
  }
]

cURL Example (CloudEvents Structured Mode)

curl -k -H "Content-Type: application/cloudevents+json" -H "aeg-sas-key: TheLocal+DevelopmentKey=" -X POST "https://localhost:60101/api/events?api-version=2018-01-01" -d @CloudEvent.json

CloudEvent.json

{
  "specversion": "1.0",
  "type": "com.example.someevent",
  "source": "/mycontext/subcontext",
  "id": "A234-1234-1234",
  "time": "2025-01-15T10:30:00Z",
  "subject": "/example/subject",
  "datacontenttype": "application/json",
  "data": {
    "MyProperty": "This is my awesome data!"
  }
}

cURL Example (CloudEvents Binary Mode)

In binary mode, CloudEvents attributes are passed as HTTP headers:

curl -k \
  -H "Content-Type: application/json" \
  -H "aeg-sas-key: TheLocal+DevelopmentKey=" \
  -H "ce-specversion: 1.0" \
  -H "ce-type: com.example.someevent" \
  -H "ce-source: /mycontext/subcontext" \
  -H "ce-id: A234-1234-1234" \
  -H "ce-time: 2025-01-15T10:30:00Z" \
  -H "ce-subject: /example/subject" \
  -X POST "https://localhost:60101/api/events?api-version=2018-01-01" \
  -d '{"MyProperty": "This is my awesome data!"}'

Postman

An example request that you can import into Postman can be found in the AzureEventGridSimulator repo here https://github.com/pmcilreavy/AzureEventGridSimulator/blob/master/src/Azure%20Event%20Grid%20Simulator.postman_collection.json.

EventGridClient

var client = new EventGridClient(new TopicCredentials("TheLocal+DevelopmentKey="));
await client.PublishEventsWithHttpMessagesAsync(
    topicHostname: "localhost:60101",
    events: new List<EventGridEvent> { <your event> });

Notes

HTTPs

Azure Event Grid only accepts connections over https and so the simulator only supports https too.

The simulator will attempt to use the dotnet development certificate to secure each topic port. You can ensure that this certificate is installed and trusted by running the following command.

dotnet dev-certs https --trust

You can also generate a certificate file (suitable for using with a Docker container) like so.

dotnet dev-certs https --export-path ./docker/azureEventGridSimulator.pfx --password Y0urSup3rCrypt1cPa55w0rd!

Subscribers

A topic can have 0 to n subscribers. When a request is received for a topic, the events will be forwarded to each of the subscribers with the addition of an aeg-event-type: Notification header. If the message contains multiple events, they will be sent to each subscriber one at a time inline with the Azure Event Grid behaviour. "Event Grid sends the events to subscribers in an array that has a single event. This behavior may change in the future." https://docs.microsoft.com/en-us/azure/event-grid/event-schema

Key Validation

The simulator supports both: aeg-sas-key or aeg-sas-token request headers. Using aeg-sas-key is the simplest way. Just set the value of the aeg-sas-key to the same key value configured for the topic. Using an aeg-sas-token is more secure as the key is hashed but it's a bit trickier to set up. More information on sas token can be found here https://docs.microsoft.com/en-us/azure/event-grid/security-authentication#sas-tokens.

If the incoming request contains either an aeg-sas-token or an aeg-sas-key header and there is a Key configured for the topic then the simulator will validate the key and reject the request if the value in the header is not valid. If you want to skip the validation then set the Key to null in appsettings.json.

Size Validation

Azure Event Grid imposes certain size limits to the overall message body and to the each individual event. The overall message body and each individual event must be <= 1048576 bytes (1 Mb). These are the advertised size limits. My testing has shown that the actual limits are 1536000 bytes (1.5 Mb) for the overall message body and 1049600 bytes (1 Mb) for each individual event.

Message Validation

Ensures that the properties of each event meets the minimum requirements.

Event Grid Schema

Field Description
Id Must be a string. Not null or whitespace.
Subject Must be a string. Not null or whitespace.
EventType Must be a string. Not null or whitespace.
EventTime Must be a valid date/time.
MetadataVersion Must be null or 1.
Topic Leave null or empty. Event Grid will populate this field.
DataVersion Optional. e.g. 1.
Data Optional. Any custom object.

CloudEvents Schema

Field Description
specversion Must be 1.0.
type Must be a string. Not null or whitespace.
source Must be a string. Not null or whitespace.
id Must be a string. Not null or whitespace.
time Optional. Must be a valid RFC 3339 timestamp if provided.
subject Optional.
datacontenttype Optional.
dataschema Optional. Must be a valid URI if provided.
data Optional. Cannot be used together with data_base64.
data_base64 Optional. Base64-encoded binary data. Cannot be used with data.

Why?

There are a couple of similar projects out there. What I found though is that they don't adequately simulate an actual Event Grid Topic endpoint.

Azure Event Grid only excepts connections over https and the Microsoft.Azure.EventGrid client only sends requests over https. If you're posting events to an Event Grid topic using custom code then maybe this isn't an issue. If you are using the client library though then any test endpoint must be https.

Typically an event grid topic endpoint url is like so: https://topic-name.location-name.eventgrid.azure.net/api/events. Note that all the information needed to post to a topic is contained in the host part. The Microsoft.Azure.EventGrid client will essentially reduce the url you give it down to just the host part and prefix it with https (regardless of the original scheme).

It posts the payload to https://host:port and drops the query uri. All of the existing simulator/ emulator projects I found don't support https and use a the query uri to distinguish between the topics. This isn't compatible with the Microsoft.Azure.EventGrid client.

Development

Code Formatting

This project uses CSharpier for code formatting. CSharpier runs automatically on every build, so your code will be formatted before compilation.

To manually format the code:

dotnet tool restore
dotnet csharpier format src

Future Development

Some features that could be added if there was a need for them:

  • Certificate configuration in appsettings.json.
  • Subscriber token auth.
  • Azure Event Hub subscriber support.
  • Web-based console for admin stats etc.

About

A simulator that provides endpoints to mimic the functionality of Azure Event Grid topics and subscribers and is compatible with the Azure.Messaging.EventGrid client library.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

Contributors 10

Languages