Full Code of ThreeDotsLabs/watermill for AI

master c9b951f72c9d cached
495 files
2.1 MB
578.4k tokens
1538 symbols
1 requests
Download .txt
Showing preview only (2,303K chars total). Download the full file or copy to clipboard to get everything.
Repository: ThreeDotsLabs/watermill
Branch: master
Commit: c9b951f72c9d
Files: 495
Total size: 2.1 MB

Directory structure:
gitextract_cdt0elb2/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   └── workflows/
│       ├── master.yml
│       ├── pr-examples.yml
│       ├── pr.yml
│       └── tests.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── RELEASE-PROCEDURE.md
├── UPGRADE-0.3.md
├── UPGRADE-0.4.md
├── UPGRADE-1.0.md
├── _examples/
│   ├── basic/
│   │   ├── 1-your-first-app/
│   │   │   ├── .validate_example.yml
│   │   │   ├── README.md
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── 2-realtime-feed/
│   │   │   ├── .validate_example_subscribing.yml
│   │   │   ├── README.md
│   │   │   ├── consumer/
│   │   │   │   ├── go.mod
│   │   │   │   ├── go.sum
│   │   │   │   └── main.go
│   │   │   ├── docker-compose.yml
│   │   │   └── producer/
│   │   │       ├── go.mod
│   │   │       ├── go.sum
│   │   │       └── main.go
│   │   ├── 3-router/
│   │   │   ├── .validate_example.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── 4-metrics/
│   │   │   ├── .validate_example.yml
│   │   │   ├── README.md
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   ├── main.go
│   │   │   └── prometheus.yml
│   │   ├── 5-cqrs-protobuf/
│   │   │   ├── .validate_example.yml
│   │   │   ├── Makefile
│   │   │   ├── README.md
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   ├── main.go
│   │   │   ├── messages.pb.go
│   │   │   └── proto/
│   │   │       └── messages.proto
│   │   └── 6-cqrs-ordered-events/
│   │       ├── .validate_example.yml
│   │       ├── Makefile
│   │       ├── README.md
│   │       ├── activity.go
│   │       ├── docker-compose.yml
│   │       ├── go.mod
│   │       ├── go.sum
│   │       ├── main.go
│   │       ├── message.go
│   │       ├── messages.pb.go
│   │       ├── proto/
│   │       │   └── messages.proto
│   │       └── subscribers.go
│   ├── pubsubs/
│   │   ├── amqp/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── aws-sns/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── aws-sqs/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── go-channel/
│   │   │   ├── .validate_example.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── googlecloud/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── kafka/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── nats-core/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── nats-jetstream/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── nats-streaming/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── redisstream/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── sql/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── sqlite/
│   │   │   ├── .gitignore
│   │   │   ├── .validate_example.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   ├── main.go
│   │   │   └── transaction.go
│   │   └── sqlite-zombiezen/
│   │       ├── .gitignore
│   │       ├── .validate_example.yml
│   │       ├── go.mod
│   │       ├── go.sum
│   │       ├── main.go
│   │       └── transaction.go
│   └── real-world-examples/
│       ├── consumer-groups/
│       │   ├── README.md
│       │   ├── api/
│       │   │   ├── http.go
│       │   │   ├── main.go
│       │   │   ├── public/
│       │   │   │   └── index.html
│       │   │   └── storage.go
│       │   ├── common/
│       │   │   ├── events.go
│       │   │   └── messaging.go
│       │   ├── crm-service/
│       │   │   └── main.go
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── newsletter-service/
│       │       └── main.go
│       ├── delayed-messages/
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       ├── delayed-requeue/
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       ├── exactly-once-delivery-counter/
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── run.go
│       │   ├── schema.sql
│       │   ├── server/
│       │   │   ├── go.mod
│       │   │   ├── go.sum
│       │   │   └── main.go
│       │   └── worker/
│       │       ├── go.mod
│       │       ├── go.sum
│       │       └── main.go
│       ├── persistent-event-log/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       ├── receiving-webhooks/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       ├── sending-webhooks/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── producer/
│       │   │   ├── go.mod
│       │   │   ├── go.sum
│       │   │   └── main.go
│       │   ├── router/
│       │   │   ├── go.mod
│       │   │   ├── go.sum
│       │   │   └── main.go
│       │   └── webhooks-server/
│       │       └── main.go
│       ├── server-sent-events/
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── schema.sql
│       │   └── server/
│       │       ├── event_handlers.go
│       │       ├── feeds_storage.go
│       │       ├── go.mod
│       │       ├── go.sum
│       │       ├── http.go
│       │       ├── main.go
│       │       ├── models.go
│       │       ├── posts_storage.go
│       │       └── public/
│       │           └── index.html
│       ├── server-sent-events-htmx/
│       │   ├── Dockerfile
│       │   ├── README.md
│       │   ├── docker/
│       │   │   ├── Dockerfile
│       │   │   └── reflex.conf
│       │   ├── docker-compose.yml
│       │   ├── events.go
│       │   ├── go.mod
│       │   ├── go.sum
│       │   ├── http.go
│       │   ├── main.go
│       │   ├── models.go
│       │   ├── repository.go
│       │   └── views/
│       │       ├── base.templ
│       │       ├── base_templ.go
│       │       ├── pages.templ
│       │       └── pages_templ.go
│       ├── synchronizing-databases/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   ├── main.go
│       │   ├── mysql.go
│       │   └── postgres.go
│       ├── transactional-events/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       └── transactional-events-forwarder/
│           ├── .validate_example.yml
│           ├── README.md
│           ├── docker-compose.yml
│           ├── go.mod
│           ├── go.sum
│           └── main.go
├── codecov.yml
├── components/
│   ├── cqrs/
│   │   ├── command_bus.go
│   │   ├── command_bus_test.go
│   │   ├── command_handler.go
│   │   ├── command_handler_test.go
│   │   ├── command_processor.go
│   │   ├── command_processor_test.go
│   │   ├── cqrs.go
│   │   ├── cqrs_test.go
│   │   ├── ctx.go
│   │   ├── doc.go
│   │   ├── event_bus.go
│   │   ├── event_bus_test.go
│   │   ├── event_handler.go
│   │   ├── event_handler_test.go
│   │   ├── event_processor.go
│   │   ├── event_processor_group.go
│   │   ├── event_processor_group_test.go
│   │   ├── event_processor_test.go
│   │   ├── marshaler.go
│   │   ├── marshaler_json.go
│   │   ├── marshaler_json_test.go
│   │   ├── marshaler_protobuf.go
│   │   ├── marshaler_protobuf_events_new_test.go
│   │   ├── marshaler_protobuf_events_test.go
│   │   ├── marshaler_protobuf_gogo.go
│   │   ├── marshaler_protobuf_gogo_test.go
│   │   ├── marshaler_protobuf_test.go
│   │   ├── name.go
│   │   ├── name_test.go
│   │   ├── object.go
│   │   └── testdata/
│   │       └── events.proto
│   ├── delay/
│   │   ├── delay.go
│   │   ├── publisher.go
│   │   └── publisher_test.go
│   ├── fanin/
│   │   ├── fanin.go
│   │   └── fanin_test.go
│   ├── forwarder/
│   │   ├── envelope.go
│   │   ├── envelope_test.go
│   │   ├── forwarder.go
│   │   ├── forwarder_test.go
│   │   └── publisher.go
│   ├── metrics/
│   │   ├── builder.go
│   │   ├── ctx.go
│   │   ├── handler.go
│   │   ├── http.go
│   │   ├── http_test.go
│   │   ├── labels.go
│   │   ├── publisher.go
│   │   └── subscriber.go
│   ├── requestreply/
│   │   ├── backend_pubsub.go
│   │   ├── backend_pubsub_marshaler.go
│   │   ├── command_bus.go
│   │   ├── handler.go
│   │   ├── requestreply.go
│   │   └── requestreply_test.go
│   └── requeuer/
│       ├── requeuer.go
│       └── requeuer_test.go
├── dev/
│   ├── consolidate-gomods/
│   │   └── main.go
│   ├── coverage.sh
│   ├── prometheus.yml
│   ├── update-examples-deps/
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── main.go
│   └── validate-examples/
│       ├── go.mod
│       ├── go.sum
│       └── main.go
├── doc.go
├── docs/
│   ├── .npmignore
│   ├── .npmrc
│   ├── .prettierignore
│   ├── .prettierrc.yaml
│   ├── DEVELOP.md
│   ├── assets/
│   │   ├── images/
│   │   │   └── .gitkeep
│   │   ├── js/
│   │   │   └── custom.js
│   │   ├── jsconfig.json
│   │   ├── scss/
│   │   │   └── common/
│   │   │       ├── _custom.scss
│   │   │       └── _variables-custom.scss
│   │   └── svgs/
│   │       └── .gitkeep
│   ├── build.sh
│   ├── config/
│   │   ├── _default/
│   │   │   ├── hugo.toml
│   │   │   ├── languages.toml
│   │   │   ├── markup.toml
│   │   │   ├── menus/
│   │   │   │   └── menus.en.toml
│   │   │   ├── module.toml
│   │   │   └── params.toml
│   │   ├── babel.config.js
│   │   ├── next/
│   │   │   └── hugo.toml
│   │   ├── postcss.config.js
│   │   └── production/
│   │       └── hugo.toml
│   ├── content/
│   │   ├── _index.md
│   │   ├── advanced/
│   │   │   ├── delayed-messages.md
│   │   │   ├── fanin.md
│   │   │   ├── fanout.md
│   │   │   ├── forwarder.md
│   │   │   ├── metrics.md
│   │   │   └── requeuing-after-error.md
│   │   ├── development/
│   │   │   ├── benchmark.md
│   │   │   ├── contributing.md
│   │   │   ├── pub-sub-implementing.md
│   │   │   └── releases.md
│   │   ├── docs/
│   │   │   ├── _index.md
│   │   │   ├── articles.md
│   │   │   ├── awesome.md
│   │   │   ├── cqrs.md
│   │   │   ├── message/
│   │   │   │   ├── .validate_example.yml
│   │   │   │   ├── go.mod
│   │   │   │   ├── go.sum
│   │   │   │   └── receiving-ack.go
│   │   │   ├── message.md
│   │   │   ├── messages-router.md
│   │   │   ├── middlewares.md
│   │   │   ├── pub-sub.md
│   │   │   ├── snippets/
│   │   │   │   ├── amqp-consumer-groups/
│   │   │   │   │   ├── .validate_example.yml
│   │   │   │   │   ├── docker-compose.yml
│   │   │   │   │   ├── go.mod
│   │   │   │   │   ├── go.sum
│   │   │   │   │   └── main.go
│   │   │   │   └── tail-log-file/
│   │   │   │       ├── go.mod
│   │   │   │       ├── go.sum
│   │   │   │       └── main.go
│   │   │   └── troubleshooting.md
│   │   ├── learn/
│   │   │   ├── _index.md
│   │   │   ├── getting-started.md
│   │   │   └── quickstart.md
│   │   ├── pubsubs/
│   │   │   ├── _index.md
│   │   │   ├── amqp.md
│   │   │   ├── aws.md
│   │   │   ├── bolt.md
│   │   │   ├── firestore.md
│   │   │   ├── gochannel.md
│   │   │   ├── googlecloud.md
│   │   │   ├── http.md
│   │   │   ├── io.md
│   │   │   ├── kafka.md
│   │   │   ├── nats.md
│   │   │   ├── redisstream.md
│   │   │   ├── sql.md
│   │   │   └── sqlite.md
│   │   └── support.md
│   ├── extract_middleware_godocs.py
│   ├── layouts/
│   │   ├── _default/
│   │   │   ├── _markup/
│   │   │   │   └── render-link.html
│   │   │   ├── learn.html
│   │   │   └── quickstart.html
│   │   ├── index.html
│   │   ├── partials/
│   │   │   ├── footer/
│   │   │   │   ├── footer.html
│   │   │   │   └── script-footer-custom.html
│   │   │   ├── head/
│   │   │   │   ├── custom-head.html
│   │   │   │   ├── resource-hints.html
│   │   │   │   └── script-header.html
│   │   │   ├── header/
│   │   │   │   └── header.html
│   │   │   ├── main/
│   │   │   │   └── edit-page.html
│   │   │   ├── private/
│   │   │   │   └── has-headings.html
│   │   │   ├── seo/
│   │   │   │   ├── opengraph.html
│   │   │   │   └── twitter.html
│   │   │   └── sidebar/
│   │   │       └── section-menu.html
│   │   └── shortcodes/
│   │       ├── load-snippet-partial.html
│   │       ├── load-snippet.html
│   │       ├── readfile.html
│   │       ├── tab.html
│   │       └── tabs.html
│   ├── package.json
│   ├── resources/
│   │   └── _gen/
│   │       └── assets/
│   │           └── scss/
│   │               ├── app.scss_901a6e181e810c5c7347a10d84f037ab.content
│   │               ├── app.scss_901a6e181e810c5c7347a10d84f037ab.json
│   │               ├── app.scss_cdf9d7c9eb97e4550ded64a8776dd9e8.content
│   │               └── app.scss_cdf9d7c9eb97e4550ded64a8776dd9e8.json
│   └── static/
│       └── .gitkeep
├── go.mod
├── go.sum
├── internal/
│   ├── channel.go
│   ├── channel_test.go
│   ├── name.go
│   ├── name_test.go
│   ├── norace.go
│   ├── publisher/
│   │   ├── errors.go
│   │   ├── retry.go
│   │   └── retry_test.go
│   ├── race.go
│   └── subscriber/
│       └── multiplier.go
├── log.go
├── log_test.go
├── message/
│   ├── decorator.go
│   ├── decorator_bench_test.go
│   ├── decorator_test.go
│   ├── message.go
│   ├── message_test.go
│   ├── messages.go
│   ├── messages_test.go
│   ├── metadata.go
│   ├── pubsub.go
│   ├── router/
│   │   ├── middleware/
│   │   │   ├── circuit_breaker.go
│   │   │   ├── circuit_breaker_test.go
│   │   │   ├── correlation.go
│   │   │   ├── correlation_test.go
│   │   │   ├── deduplicator.go
│   │   │   ├── deduplicator_test.go
│   │   │   ├── delay_on_error.go
│   │   │   ├── delay_on_error_test.go
│   │   │   ├── duplicator.go
│   │   │   ├── duplicator_test.go
│   │   │   ├── ignore_errors.go
│   │   │   ├── ignore_errors_test.go
│   │   │   ├── instant_ack.go
│   │   │   ├── instant_ack_test.go
│   │   │   ├── message_test.go
│   │   │   ├── poison.go
│   │   │   ├── poison_test.go
│   │   │   ├── randomfail.go
│   │   │   ├── randomfail_test.go
│   │   │   ├── recoverer.go
│   │   │   ├── recoverer_test.go
│   │   │   ├── retry.go
│   │   │   ├── retry_test.go
│   │   │   ├── throttle.go
│   │   │   ├── throttle_test.go
│   │   │   ├── timeout.go
│   │   │   └── timeout_test.go
│   │   └── plugin/
│   │       └── signals.go
│   ├── router.go
│   ├── router_context.go
│   ├── router_context_test.go
│   ├── router_test.go
│   └── subscriber/
│       ├── read.go
│       └── read_test.go
├── netlify.toml
├── pubsub/
│   ├── doc.go
│   ├── gochannel/
│   │   ├── doc.go
│   │   ├── fanout.go
│   │   ├── fanout_test.go
│   │   ├── pubsub.go
│   │   ├── pubsub_bench_test.go
│   │   ├── pubsub_internal_test.go
│   │   ├── pubsub_stress_test.go
│   │   └── pubsub_test.go
│   ├── sync/
│   │   ├── waitgroup.go
│   │   └── waitgroup_test.go
│   └── tests/
│       ├── bench_pubsub.go
│       ├── test_asserts.go
│       ├── test_pubsub.go
│       └── test_pubsub_stress.go
├── slog.go
├── slog_test.go
├── tools/
│   ├── mill/
│   │   ├── .default-config.yml
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── cmd/
│   │   │   ├── amqp.go
│   │   │   ├── consume.go
│   │   │   ├── googlecloud.go
│   │   │   ├── internal/
│   │   │   │   └── indent.go
│   │   │   ├── kafka.go
│   │   │   ├── produce.go
│   │   │   └── root.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── main.go
│   └── pq/
│       ├── README.md
│       ├── backend/
│       │   └── postgres.go
│       ├── cli/
│       │   ├── backend.go
│       │   ├── message.go
│       │   └── model.go
│       ├── go.mod
│       ├── go.sum
│       └── main.go
├── uuid.go
└── uuid_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

<!--
The resources of our team are limited.
If you want to speed up fixing the problem, please follow the guidelines below.
It will help us to understand and reproduce the issue and to find a solution faster.
-->

### Steps to reproduce

<!--
⚠️  Important: Problem Reproduction Steps  ⚠️
To help us investigate and fix your issue effectively, we need clear reproduction steps.
Include relevant code snippets and descriptions that demonstrate the problem.
-->

docker-compose.yml
```yaml

```

```go
// Your reproduction code goes here
```

### Expected behavior

<!-- What should happen -->

### Actual behavior

<!-- What happens instead -->

### Possible solution

<!-- Consider submitting a pull request - it will help us provide faster feedback on your solution. -->


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''

---

## Feature request

<!--
The resources of our team are limited. If you want to
We will be able to help you faster if you follow the guidelines below.
It will help us to understand and reproduce the issue and to find a solution faster.
-->

### Description

<!-- Describe the new feature clearly and concisely -->

### Example use case

<!--
Provide an example of how you would use this feature. 
It will help us to understand the context of the request and to find the best solution.
-->

#### How it can look like in code

```go

```


================================================
FILE: .github/pull_request_template.md
================================================
<!--
Thanks for contributing to Watermill!

The following template aims to help contributors write a good description for their pull requests.
**The more information you provide, the faster we will be able to review and merge your PR.**

Feel free to skip this template for minor changes like typo fixes.

-->

### Motivation / Background

<!--

Explain the purpose of this Pull Request:
- What issue or bug does it address?
- What new functionality does it add?
- Why are these changes needed?
For bug fixes, include "Fixes #ISSUE" to automatically link to the related issue.

-->

### Detail


### Alternative approaches considered (if applicable)

<!-- If applicable, describe alternative approaches you considered and why you chose this one. -->

### Checklist

The resources of our team are limited. **There are a couple of things that you can do to help us merge your PR faster**:

- [ ] I wrote tests for the changes.
- [ ] All tests are passing.
  - If you are testing a Pub/Sub, you can start Docker with `make up`.
  - You can start with `make test_short` for a quick check.
  - If you want to run all tests, use `make test`.
- [ ] Code has no breaking changes.
- [ ] _(If applicable)_ documentation on [watermill.io](https://watermill.io/) is updated.
  - Documentation is built in the [github.com/ThreeDotsLabs/watermill/docs](https://github.com/ThreeDotsLabs/watermill/tree/master/docs).
  - You can find development instructions in the [DEVELOP.md](https://github.com/ThreeDotsLabs/watermill/tree/master/docs/DEVELOP.md).

================================================
FILE: .github/workflows/master.yml
================================================
name: master
on:
  push:
    branches:
      - master
jobs:
  ci:
    uses: ThreeDotsLabs/watermill/.github/workflows/tests.yml@master
    with:
      stress-tests: true
      codecov: true
    secrets:
      codecov_token: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .github/workflows/pr-examples.yml
================================================
name: pr-examples
on:
  pull_request:
    paths:
      - '_examples/**/*'

jobs:
  validate-examples:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: '^1.21.1'
      - run: make validate_examples
        timeout-minutes: 30


================================================
FILE: .github/workflows/pr.yml
================================================
name: pr
on:
  pull_request:
jobs:
  ci:
    uses: ThreeDotsLabs/watermill/.github/workflows/tests.yml@master
    with:
      codecov: true
    secrets:
      codecov_token: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .github/workflows/tests.yml
================================================
name: tests
on:
  workflow_call:
    inputs:
      stress-tests:
        description: 'Run stress tests'
        required: false
        type: boolean
        default: false
      codecov:
        required: false
        type: boolean
        default: false
      runs-on:
        required: false
        type: string
        default: 'ubuntu-latest'
    secrets:
      codecov_token:
        required: false
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: '^1.21.1'
      - run: make build

  detect-modules:
    runs-on: ubuntu-latest
    outputs:
      modules: ${{ steps.set-modules.outputs.modules }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '^1.21.1'
      - id: set-modules
        run: echo "modules=$(go list -m -json | jq -s '.' | jq -c '[.[].Dir]')" >> $GITHUB_OUTPUT

  lint:
    needs: detect-modules
    runs-on: ubuntu-latest
    strategy:
      matrix:
        modules: ${{ fromJSON(needs.detect-modules.outputs.modules) }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: '^1.21.1'
      - name: golangci-lint ${{ matrix.modules }}
        uses: golangci/golangci-lint-action@v6.5.2
        with:
          working-directory: ${{ matrix.modules }}

  tests:
    needs: [build]
    runs-on: ${{ inputs.runs-on }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: '^1.21.1'
      - run: cat .env >> $GITHUB_ENV || true
      - run: make up
      - run: make wait
      - run: make test_short
      - run: make test
        timeout-minutes: 30
      - run: make test_race
      - run: make test_stress
        if: ${{ inputs.stress-tests }}
      - name: Dump docker logs on failure
        if: failure()
        uses: jwalton/gh-docker-logs@a8cb5301950dd4d2b86619cd487b3b281526b178 # v2.2.0
  codecov:
    runs-on: ${{ inputs.runs-on }}
    if: ${{ inputs.codecov }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: '^1.21.1'
      - run: cat .env >> $GITHUB_ENV || true
      - run: make up
      - run: make wait
      - run: make test_codecov
      - uses: codecov/codecov-action@v4
        with:
          fail_ci_if_error: true
          files: ./coverage.out
          token: ${{ secrets.codecov_token }}


================================================
FILE: .gitignore
================================================
.idea
vendor
docs/themes/
docs/node_modules/
docs/public/
docs/content/src-link
docs/content/middleware
docs/hugo_stats.json
*.out
*.log
.mod-cache


================================================
FILE: CONTRIBUTING.md
================================================
# Contributors guide v0.1

## How can I help?

We are always happy to help you in contributing to Watermill. If you have any ideas, please let us know on our [Discord server](https://watermill.io/support/).

There are multiple ways in which you can help us.

### Existing issues

You can pick one of the existing issues. Most of the issues should have an estimation (S - small, M - medium, L - large).

- [Good first issues list](https://github.com/ThreeDotsLabs/watermill/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) - simple issues to begin with
- [Help wanted issues list](https://github.com/ThreeDotsLabs/watermill/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) - tasks that are already more or less clear, and you can start to implement them pretty quickly

### New Pub/Sub implementations

If you have an idea to create a Pub/Sub based on some technology and it is not listed yet in our issues (because we don't know it, or it is just some crazy idea, like physical mail based Pub/Sub), feel free to add your own implementation.
You can do it in your private repository or if you want, we can move it to `ThreeDotsLabs/watermill-[name]`.

*Please keep in mind that you will not be able to push changes directly to the master branch in a project in our organization*.

When adding a new Pub/Sub implementation, you should start with this guide: [https://watermill.io/docs/pub-sub-implementing/](https://watermill.io/docs/pub-sub-implementing/).

### New ideas

If you have any idea that is not covered in the issues list, please post a new issue describing it. 
It's recommended to discuss your idea on [Discord](https://discord.gg/QV6VFg4YQE)/GitHub before creating production-ready implementation - in some situations, it may save a lot of your time before implementing something that can be simplified or done more easily. :)

In general, it's helpful to discuss a Proof of Concept to align with the idea.

## Local development

Makefile and docker-compose (for Pub/Subs) are your friends. You can run all tests locally (they are running in CI in the same way).

Useful commands:
- `make up` - docker-compose up
- `make test` - tests
- `make test_short` - run short tests (useful to perform a very fast check after changes)
- `make fmt` - do goimports

## Code standards

- you should run `make fmt`
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments)
- [Effective Go](https://golang.org/doc/effective_go.html)
- SOLID
- code should be open for configuration and not coupled to any serialization method (for example: [AMQP marshaler](https://github.com/ThreeDotsLabs/watermill-amqp/blob/master/pkg/amqp/marshaler.go), [AMQP Config](https://github.com/ThreeDotsLabs/watermill-amqp/blob/master/pkg/amqp/config.go)


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 Three Dots Labs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
up:

test:
	go test ./...

test_v:
	go test -v ./...

test_short:
	go test ./... -short

test_race:
	go test ./... -short -race

test_stress:
	go test -tags=stress -timeout=30m ./...

test_codecov:
	go test -coverprofile=coverage.out -covermode=atomic ./...

test_reconnect:
	go test -tags=reconnect ./...

build:
	go build ./...

wait:

fmt:
	go fmt ./...
	goimports -l -w .

generate_gomod:
	rm go.mod go.sum || true
	go mod init github.com/ThreeDotsLabs/watermill

	go install ./...
	sed -i '\|go |d' go.mod
	go mod edit -fmt

update_examples_deps:
	go run dev/update-examples-deps/main.go

validate_examples:
	(cd dev/validate-examples/ && go run main.go)


================================================
FILE: README.md
================================================
# Watermill
<img align="right" width="300" src="https://watermill.io/img/gopher.svg">

[![CI Status](https://github.com/ThreeDotsLabs/watermill/actions/workflows/master.yml/badge.svg)](https://github.com/ThreeDotsLabs/watermill/actions/workflows/master.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/ThreeDotsLabs/watermill.svg)](https://pkg.go.dev/github.com/ThreeDotsLabs/watermill)
[![Go Report Card](https://goreportcard.com/badge/github.com/ThreeDotsLabs/watermill)](https://goreportcard.com/report/github.com/ThreeDotsLabs/watermill)
[![codecov](https://codecov.io/gh/ThreeDotsLabs/watermill/branch/master/graph/badge.svg)](https://codecov.io/gh/ThreeDotsLabs/watermill)

Watermill is a Go library for working efficiently with message streams. It is intended
for building event driven applications, enabling event sourcing, RPC over messages,
sagas and basically whatever else comes to your mind. You can use conventional pub/sub
implementations like Kafka or RabbitMQ, but also HTTP or PostgreSQL if that fits your use case.

## Goals

* **Easy** to understand.
* **Universal** - event-driven architecture, messaging, stream processing, CQRS - use it for whatever you need.
* **Fast** (see [Benchmarks](#benchmarks)).
* **Flexible** with middlewares, plugins and Pub/Sub configurations.
* **Resilient** - using proven technologies and passing stress tests (see [Stability](#stability)).

## Getting Started

Pick what you like the best or see in order:

1. [Quickstart](https://watermill.io/learn/quickstart/) — learn by coding!
2. Follow the [Getting Started guide](https://watermill.io/learn/getting-started/).
3. See examples below.
4. Read the full documentation: https://watermill.io/

## Our online hands-on training

Go Event-Driven goes beyond Watermill Quickstart. You'll learn industry standard concepts and patterns like:

* Handling at-least-once delivery
* Asynchronous read models
* Events & Commands
* Observability
* Message ordering
* Sagas

<a href="https://threedots.tech/event-driven/?utm_source=watermill-readme"><img align="center" width="400" src="https://threedots.tech/event-driven-banner.png"></a>

## Examples

* Basic
    * [Your first app](_examples/basic/1-your-first-app) - **start here!**
    * [Realtime feed](_examples/basic/2-realtime-feed)
    * [Router](_examples/basic/3-router)
    * [Metrics](_examples/basic/4-metrics)
    * [CQRS with protobuf](_examples/basic/5-cqrs-protobuf)
* [Pub/Subs usage](_examples/pubsubs)
    * These examples are part of the [Getting started guide](https://watermill.io/learn/getting-started/) and show usage of a single Pub/Sub at a time.
* Real-world examples
    * [Exactly-once delivery counter](_examples/real-world-examples/exactly-once-delivery-counter)
    * [Receiving webhooks](_examples/real-world-examples/receiving-webhooks)
    * [Sending webhooks](_examples/real-world-examples/sending-webhooks)
    * [Synchronizing Databases](_examples/real-world-examples/synchronizing-databases)
    * [Persistent Event Log](_examples/real-world-examples/persistent-event-log)
    * [Transactional Events](_examples/real-world-examples/transactional-events)
    * [Real-time HTTP updates with Server-Sent Events](_examples/real-world-examples/server-sent-events)
    * [Real-time HTTP updates with Server-Sent Events and htmx](_examples/real-world-examples/server-sent-events-htmx)
* Complete projects
    * [NATS example with live code reloading](https://github.com/ThreeDotsLabs/nats-example)
    * [RabbitMQ, webhooks and Kafka integration](https://github.com/ThreeDotsLabs/event-driven-example)

## Background

Building distributed and scalable services is rarely as easy as some may suggest. There is a
lot of hidden knowledge that comes with writing such systems. Just like you don't need to know the
whole TCP stack to create a HTTP REST server, you shouldn't need to study all of this knowledge to
start with building message-driven applications.

Watermill's goal is to make communication with messages as easy to use as HTTP routers. It provides
the tools needed to begin working with event-driven architecture and allows you to learn the details
on the go.

At the heart of Watermill there is one simple interface:
```go
func(*Message) ([]*Message, error)
```

Your handler receives a message and decides whether to publish new message(s) or return
an error. What happens next is up to the middlewares you've chosen.

You can find more about our motivations in our [*Introducing Watermill* blog post](https://threedots.tech/post/introducing-watermill/).

## Pub/Subs

All publishers and subscribers have to implement an interface:

```go
type Publisher interface {
	Publish(topic string, messages ...*Message) error
	Close() error
}

type Subscriber interface {
	Subscribe(ctx context.Context, topic string) (<-chan *Message, error)
	Close() error
}
```

Supported Pub/Subs:

- AMQP (RabbitMQ) Pub/Sub [(`github.com/ThreeDotsLabs/watermill-amqp/v3`)](https://github.com/ThreeDotsLabs/watermill-amqp/)
- AWS SNS/SQS Pub/Sub [(`github.com/ThreeDotsLabs/watermill-aws`)](https://github.com/ThreeDotsLabs/watermill-aws/)
- Bolt Pub/Sub [(`github.com/ThreeDotsLabs/watermill-bolt`)](https://github.com/ThreeDotsLabs/watermill-bolt/)
- Firestore Pub/Sub [(`github.com/ThreeDotsLabs/watermill-firestore`)](https://github.com/ThreeDotsLabs/watermill-firestore/)
- Google Cloud Pub/Sub [(`github.com/ThreeDotsLabs/watermill-googlecloud/v2`)](https://github.com/ThreeDotsLabs/watermill-googlecloud/)
- HTTP Pub/Sub [(`github.com/ThreeDotsLabs/watermill-http/v2`)](https://github.com/ThreeDotsLabs/watermill-http/)
- io.Reader/io.Writer Pub/Sub [(`github.com/ThreeDotsLabs/watermill-io`)](https://github.com/ThreeDotsLabs/watermill-io/)
- Kafka Pub/Sub [(`github.com/ThreeDotsLabs/watermill-kafka/v3`)](https://github.com/ThreeDotsLabs/watermill-kafka/)
- NATS Jetstream Pub/Sub [(`github.com/ThreeDotsLabs/watermill-nats/v2`)](https://github.com/ThreeDotsLabs/watermill-nats/)
- Redis Stream Pub/Sub [(`github.com/ThreeDotsLabs/watermill-redisstream`)](https://github.com/ThreeDotsLabs/watermill-redisstream/)
- SQL (MySQL / PostgreSQL) Pub/Sub [(`github.com/ThreeDotsLabs/watermill-sql/v4`)](https://github.com/ThreeDotsLabs/watermill-sql/)
- SQLite Pub/Sub (Beta) [(`github.com/ThreeDotsLabs/watermill-sqlite/`)](https://github.com/ThreeDotsLabs/watermill-sqlite/)

All Pub/Subs implementation documentation can be found in the [documentation](https://watermill.io/pubsubs/).

## Unofficial libraries

Can't find your favorite Pub/Sub or library integration? Check [Awesome Watermill](https://watermill.io/docs/awesome/).

If you know another library or are an author of one, please [add it to the list](https://github.com/ThreeDotsLabs/watermill/edit/master/docs/content/docs/awesome.md).

## Contributing

Please check our [contributing guide](CONTRIBUTING.md).

## Stability

Watermill v1.0.0 has been released and is production-ready. The public API is stable and will not change without changing the major version.

To ensure that all Pub/Subs are stable and safe to use in production, we created a [set of tests](https://github.com/ThreeDotsLabs/watermill/blob/master/pubsub/tests/test_pubsub.go#L34) that need to pass for each of the implementations before merging to master.
All tests are also executed in [*stress*](https://github.com/ThreeDotsLabs/watermill/blob/master/pubsub/tests/test_pubsub.go#L171) mode - that means that we are running all the tests **20x** in parallel.

All tests are run with the race condition detector enabled (`-race` flag in tests).

For more information about debugging tests, you should check [tests troubleshooting guide](http://watermill.io/docs/troubleshooting/#debugging-pubsub-tests).

## Benchmarks

Initial tools for benchmarking Pub/Subs can be found in [watermill-benchmark](https://github.com/ThreeDotsLabs/watermill-benchmark).

All benchmarks are being done on a single 16 CPU VM instance, running one binary and dependencies in Docker Compose.

These numbers are meant to serve as a rough estimate of how fast messages can be processed by different Pub/Subs.
Keep in mind that the results can be vastly different, depending on the setup and configuration (both much lower and higher).

Here's the short version for message size of 16 bytes.

| Pub/Sub                         | Publish (messages / s) | Subscribe (messages / s) |
|---------------------------------|------------------------|--------------------------|
| GoChannel                       | 315,776                | 138,743                  |
| Redis Streams                   | 59,158                 | 12,134                   |
| NATS Jetstream (16 Subscribers) | 50,668                 | 34,713                   |
| Kafka (one node)                | 41,492                 | 101,669                  |
| SQL (MySQL, batch size=100)     | 6,371                  | 2,794                    |
| SQL (PostgreSQL, batch size=1)  | 2,831                  | 9,460                    |
| Google Cloud Pub/Sub            | 3,027                  | 28,589                   |
| AMQP (RabbitMQ)                 | 2,770                  | 14,604                   |

## Support

If you didn't find the answer to your question in [the documentation](https://watermill.io/), feel free to ask us directly!

Please join us on the `#watermill` channel on the [Three Dots Labs Discord](https://discord.gg/QV6VFg4YQE).

## Why the name?

It processes streams!

## License

[MIT License](./LICENSE)


================================================
FILE: RELEASE-PROCEDURE.md
================================================
# Release procedure

1. Generate clean go.mod: `make generate_gomod`
2. Push to master
3. Update missing documentation
4. Check snippets in documentation (sometimes `first_line_contains` or `last_line_contains` can change position and load too much)
5. Add breaking changes to `UPGRADE-[new-version].md`
6. Push to master
7. [Add release in GitHub](https://github.com/ThreeDotsLabs/watermill/releases)
8. Update Pub/Subs versions
9. Update and validate examples: `make validate_examples`


================================================
FILE: UPGRADE-0.3.md
================================================
# UPGRADE FROM 0.2.x to 0.3

# `watermill/message`

- `message.Message.Ack` and `message.Message.Nack` now return `bool` instead of `error`
- `message.Subscriber.Subscribe` now accepts `context.Context` as the first argument
- `message.Subscriber.Subscribe` now returns `<-chan *Message` instead of `chan *Message`
- `message.Router.AddHandler` and `message.Router.AddNoPublisherHandler` now panic, instead of returning error

# `watermill/message/infrastructure`

- updated all Pub/Subs to new `message.Subscriber` interface
- `gochannel.NewGoChannel` now accepts `gochannel.Config`, instead of positional parameters
- `http.NewSubscriber` now accepts `http.SubscriberConfig`, instead of positional parameters

# `watermill/message/router/middleware`

- `metrics.NewMetrics` is removed, please use the [metrics](components/metrics) component instead

# `watermill`

- `watermill.LoggerAdapter` interface now requires a `With(fields LogFields) LoggerAdapter` method


================================================
FILE: UPGRADE-0.4.md
================================================
# UPGRADE FROM 0.3.x to 0.4

## `watermill/components/cqrs`

### `CommandHandler.HandlerName` and `EventHandler.HandlerName` was added to the interface.

If you are using metrics component, you may want to keep backward capability with handler names. In other cases, you can implement your own method of generating handler name.

Keeping backward capability for **event handlers**:

```
func (h CommandHandler) HandlerName() string {
    return fmt.Sprintf("command_processor-%s", h)
}
```

Keeping backward capability for **command handlers**:

```
func (h EventHandler) HandlerName() string {
    return fmt.Sprintf("event_processor-%s", ObjectName(h))
}
```

### Added `CommandsSubscriberConstructor` and `EventsSubscriberConstructor`

From now on, `CommandsSubscriberConstructor` and `EventsSubscriberConstructor` are passed to constructors in CQRS component.

They allow creating customized subscribers for every handler. For usage examples please check [_examples/cqrs-protobuf](_examples/cqrs-protobuf).


### Added context to `CommandHandler.Handle`, `CommandBus.Send`, `EventHandler.Handle` and `EventBus.Send`

Added missing context, which is passed to Publish function and handlers.

### Other

- `NewCommandProcessor` and `NewEventProcessor` now return an error instead of panic
- `DuplicateCommandHandlerError` is returned instead of panic when two handlers are handling the same command
- `CommandProcessor.routerHandlerFunc` and `EventProcessor.routerHandlerFunc` are now private
- using `GenerateCommandsTopic` and `GenerateEventsTopic` functions instead of constant topic to allow more flexibility


## `watermill/message/infrastructure/amqp`

### `Config.QueueBindConfig.RoutingKey` was replaced with `GenerateRoutingKey`

For backward compatibility, when using the constant value you should use a function:


```
func(topic string) string {
    return "routing_key"
}
```


## `message/router/middleware`

- `PoisonQueue` is now `PoisonQueue(pub message.Publisher, topic string) (message.HandlerMiddleware, error)`, not a struct


## `message/router.go`

- From now on, when all handlers are stopped, the router will also stop (`TestRouter_stop_when_all_handlers_stopped` test)


================================================
FILE: UPGRADE-1.0.md
================================================
# Upgrade instructions from v0.4.X

In v1.0.0 we introduced a couple of breaking changes, to keep a stable API until version v2.

## Migrating Pub/Subs

All Pub/Subs (excluding go-channel implementation) were moved to separated repositories.
You can replace all import paths, with provided `sed`:

	find . -type f -iname '*.go' -exec sed -i -E "s/github\.com\/ThreeDotsLabs\/watermill\/message\/infrastructure\/(amqp|googlecloud|http|io|kafka|nats|sql)/github.com\/ThreeDotsLabs\/watermill-\1\/pkg\/\1/" "{}" +;
	find . -type f -iname '*.go' -exec sed -i -E "s/github\.com\/ThreeDotsLabs\/watermill\/message\/infrastructure\/gochannel/github\.com\/ThreeDotsLabs\/watermill\/pubsub\/gochannel/" "{}" +;

# Breaking changes
- `message.PubSub` interface was removed
- `message.NewPubSub` constructor was removed
- `message.NoPublishHandlerFunc` is now passed to `message.Router.AddNoPublisherHandler`, instead of `message.HandlerFunc`.
- `message.Router.Run` now requires `context.Context` in parameter
- `PrometheusMetricsBuilder.DecoratePubSub` was removed (because of `message.PubSub` interface removal)
- `cars.ObjectName` was renamed to `cqrs.FullyQualifiedStructName`
- `github.com/ThreeDotsLabs/watermill/message/infrastructure/gochannel` was moved to `github.com/ThreeDotsLabs/watermill/pubsub/gochannel`
- `middleware.Retry` configuration parameters have been renamed
- Universal Pub/Sub tests have been moved from `github.com/ThreeDotsLabs/watermill/message/infrastructure` to `github.com/ThreeDotsLabs/watermill/pubsub/tests`
- All universal tests require now `TestContext`.
- Removed `context` from `googlecloud.NewPublisher`


================================================
FILE: _examples/basic/1-your-first-app/.validate_example.yml
================================================
validation_cmd: "docker compose up"
teardown_cmd: "docker compose down"
timeout: 180
expected_output: "received event {ID:[0-9]+}"


================================================
FILE: _examples/basic/1-your-first-app/README.md
================================================
# Your first Watermill app

This example project shows a basic setup of Watermill. The application runs in a loop, consuming events from a Kafka
topic, modifying them and publishing to another topic.

There's a docker-compose file included, so you can run the example and see it in action.

To understand the background and internals, see [getting started guide](https://watermill.io/learn/getting-started/).

## Files

- [main.go](main.go) - example source code, the **most interesting file for you**
- [docker-compose.yml](docker-compose.yml) - local environment Docker Compose configuration, contains Golang, Kafka and Zookeeper
- [go.mod](go.mod) - Go modules dependencies, you can find more information at [Go wiki](https://github.com/golang/go/wiki/Modules)
- [go.sum](go.sum) - Go modules checksums

## Requirements

To run this example you will need Docker and docker-compose installed. See the [installation guide](https://docs.docker.com/compose/install/).

## Running

```bash
> docker-compose up
[some initial logs]
server_1     | 2019/08/29 19:41:23 received event {ID:0}
server_1     | 2019/08/29 19:41:23 received event {ID:1}
server_1     | 2019/08/29 19:41:23 received event {ID:2}
server_1     | 2019/08/29 19:41:23 received event {ID:3}
server_1     | 2019/08/29 19:41:24 received event {ID:4}
server_1     | 2019/08/29 19:41:25 received event {ID:5}
server_1     | 2019/08/29 19:41:26 received event {ID:6}
server_1     | 2019/08/29 19:41:27 received event {ID:7}
server_1     | 2019/08/29 19:41:28 received event {ID:8}
server_1     | 2019/08/29 19:41:29 received event {ID:9}
```

Open another terminal and take a look at Kafka topics to see that all messages are there. The initial events should be present on the `events` topic:

```bash
> docker-compose exec server mill kafka consume -b kafka:9092 --topic events

{"id":12}
{"id":13}
{"id":14}
{"id":15}
{"id":16}
{"id":17}
```

And the processed messages will be stored in the `events-processed` topic:

```bash
> docker-compose exec server mill kafka consume -b kafka:9092 -t events-processed 

{"processed_id":21,"time":"2019-08-29T19:42:31.4464598Z"}
{"processed_id":22,"time":"2019-08-29T19:42:32.4501767Z"}
{"processed_id":23,"time":"2019-08-29T19:42:33.4530692Z"}
{"processed_id":24,"time":"2019-08-29T19:42:34.4561694Z"}
{"processed_id":25,"time":"2019-08-29T19:42:35.4608918Z"}
```


================================================
FILE: _examples/basic/1-your-first-app/docker-compose.yml
================================================
services:
  server:
    image: golang:1.25
    restart: unless-stopped
    volumes:
    - .:/app
    - $GOPATH/pkg/mod:/go/pkg/mod
    working_dir: /app
    command: >
      /bin/sh -c "go install github.com/ThreeDotsLabs/watermill/tools/mill@latest &&
                  go run main.go"

  kafka:
    image: bitnami/kafka:3.5.0
    restart: unless-stopped
    environment:
      ALLOW_PLAINTEXT_LISTENER: yes
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"


================================================
FILE: _examples/basic/1-your-first-app/go.mod
================================================
module main.go

go 1.25

require (
	github.com/ThreeDotsLabs/watermill v1.5.1
	github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0
)

require (
	github.com/IBM/sarama v1.46.0 // indirect
	github.com/cenkalti/backoff/v5 v5.0.3 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect
	github.com/eapache/go-resiliency v1.7.0 // indirect
	github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
	github.com/eapache/queue v1.1.0 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/golang/snappy v1.0.0 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/hashicorp/errwrap v1.1.0 // indirect
	github.com/hashicorp/go-multierror v1.1.1 // indirect
	github.com/hashicorp/go-uuid v1.0.3 // indirect
	github.com/jcmturner/aescts/v2 v2.0.0 // indirect
	github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
	github.com/jcmturner/gofork v1.7.6 // indirect
	github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
	github.com/jcmturner/rpc/v2 v2.0.3 // indirect
	github.com/klauspost/compress v1.18.0 // indirect
	github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
	github.com/oklog/ulid v1.3.1 // indirect
	github.com/pierrec/lz4/v4 v4.1.22 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
	github.com/sony/gobreaker v1.0.0 // indirect
	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
	go.opentelemetry.io/otel v1.38.0 // indirect
	go.opentelemetry.io/otel/metric v1.38.0 // indirect
	go.opentelemetry.io/otel/trace v1.38.0 // indirect
	golang.org/x/crypto v0.41.0 // indirect
	golang.org/x/net v0.43.0 // indirect
)


================================================
FILE: _examples/basic/1-your-first-app/go.sum
================================================
github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=
github.com/IBM/sarama v1.46.0/go.mod h1:0lOcuQziJ1/mBGHkdp5uYrltqQuKQKM5O5FOWUQVVvo=
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0 h1:DfhVM1ieq+rb+bboB7aoymUgfKsEM3UbH3noQZ6+RJ4=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0/go.mod h1:o1GcoF/1CSJ9JSmQzUkULvpZeO635pZe+WWrYNFlJNk=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: _examples/basic/1-your-first-app/main.go
================================================
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/ThreeDotsLabs/watermill"
	"github.com/ThreeDotsLabs/watermill-kafka/v3/pkg/kafka"
	"github.com/ThreeDotsLabs/watermill/message"
	"github.com/ThreeDotsLabs/watermill/message/router/middleware"
	"github.com/ThreeDotsLabs/watermill/message/router/plugin"
)

var (
	brokers      = []string{"kafka:9092"}
	consumeTopic = "events"
	publishTopic = "events-processed"

	logger = watermill.NewStdLogger(
		true,  // debug
		false, // trace
	)
	marshaler = kafka.DefaultMarshaler{}
)

type event struct {
	ID int `json:"id"`
}

type processedEvent struct {
	ProcessedID int       `json:"processed_id"`
	Time        time.Time `json:"time"`
}

func main() {
	publisher := createPublisher()

	// Subscriber is created with consumer group handler_1
	subscriber := createSubscriber("handler_1")

	router, err := message.NewRouter(message.RouterConfig{}, logger)
	if err != nil {
		panic(err)
	}

	router.AddPlugin(plugin.SignalsHandler)
	router.AddMiddleware(middleware.Recoverer)

	// Adding a handler (multiple handlers can be added)
	router.AddHandler(
		"handler_1",  // handler name, must be unique
		consumeTopic, // topic from which messages should be consumed
		subscriber,
		publishTopic, // topic to which messages should be published
		publisher,
		func(msg *message.Message) ([]*message.Message, error) {
			consumedPayload := event{}
			err := json.Unmarshal(msg.Payload, &consumedPayload)
			if err != nil {
				// When a handler returns an error, the default behavior is to send a Nack (negative-acknowledgement).
				// The message will be processed again.
				//
				// You can change the default behaviour by using middlewares, like Retry or PoisonQueue.
				// You can also implement your own middleware.
				return nil, err
			}

			fmt.Printf("received event %+v\n", consumedPayload)

			newPayload, err := json.Marshal(processedEvent{
				ProcessedID: consumedPayload.ID,
				Time:        time.Now(),
			})
			if err != nil {
				return nil, err
			}

			newMessage := message.NewMessage(watermill.NewUUID(), newPayload)

			return []*message.Message{newMessage}, nil
		},
	)

	// Simulate incoming events in the background
	go simulateEvents(publisher)

	if err := router.Run(context.Background()); err != nil {
		panic(err)
	}
}

// createPublisher is a helper function that creates a Publisher, in this case - the Kafka Publisher.
func createPublisher() message.Publisher {
	kafkaPublisher, err := kafka.NewPublisher(
		kafka.PublisherConfig{
			Brokers:   brokers,
			Marshaler: marshaler,
		},
		logger,
	)
	if err != nil {
		panic(err)
	}

	return kafkaPublisher
}

// createSubscriber is a helper function similar to the previous one, but in this case it creates a Subscriber.
func createSubscriber(consumerGroup string) message.Subscriber {
	kafkaSubscriber, err := kafka.NewSubscriber(
		kafka.SubscriberConfig{
			Brokers:       brokers,
			Unmarshaler:   marshaler,
			ConsumerGroup: consumerGroup, // every handler will use a separate consumer group
		},
		logger,
	)
	if err != nil {
		panic(err)
	}

	return kafkaSubscriber
}

// simulateEvents produces events that will be later consumed.
func simulateEvents(publisher message.Publisher) {
	i := 0
	for {
		e := event{
			ID: i,
		}

		payload, err := json.Marshal(e)
		if err != nil {
			panic(err)
		}

		err = publisher.Publish(consumeTopic, message.NewMessage(
			watermill.NewUUID(), // internal uuid of the message, useful for debugging
			payload,
		))
		if err != nil {
			panic(err)
		}

		i++

		time.Sleep(time.Second)
	}
}


================================================
FILE: _examples/basic/2-realtime-feed/.validate_example_subscribing.yml
================================================
validation_cmd: "docker compose up"
teardown_cmd: "docker compose down"
timeout: 180
expected_output: "Adding to feed"


================================================
FILE: _examples/basic/2-realtime-feed/README.md
================================================
# Realtime Feed

This example features a very busy blogging platform, with thousands of messages showing up on your feed.

There are two separate applications (microservices) integrating over a Kafka topic. The [`producer`](producer/) generates
thousands of "posts" and publishes them to the topic. The [`consumer`](consumer/) subscribes to this topic and 
displays each post on the standard output.

The consumer has a throttling middleware enabled, so you have a chance to actually read the posts.

To understand the background and internals, see [getting started guide](https://watermill.io/learn/getting-started/).

## Requirements

To run this example you will need Docker and docker-compose installed. See the [installation guide](https://docs.docker.com/compose/install/).

## Running

```bash
docker-compose up
```

You should see the live feed of posts on the standard output.

## Exercises

1. Peek into the posts counter published on `posts_count` topic.

```
docker-compose exec consumer mill kafka consume -b kafka:9092 -t posts_count
```

2. Add a persistent storage for incoming posts in the consumer service, instead of displaying them.
   Consider using the [SQL Publisher](https://github.com/ThreeDotsLabs/watermill-sql).


================================================
FILE: _examples/basic/2-realtime-feed/consumer/go.mod
================================================
module main.go

go 1.25

require (
	github.com/ThreeDotsLabs/watermill v1.5.1
	github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0
)

require (
	github.com/IBM/sarama v1.46.0 // indirect
	github.com/cenkalti/backoff/v5 v5.0.3 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect
	github.com/eapache/go-resiliency v1.7.0 // indirect
	github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
	github.com/eapache/queue v1.1.0 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/golang/snappy v1.0.0 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/hashicorp/errwrap v1.1.0 // indirect
	github.com/hashicorp/go-multierror v1.1.1 // indirect
	github.com/hashicorp/go-uuid v1.0.3 // indirect
	github.com/jcmturner/aescts/v2 v2.0.0 // indirect
	github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
	github.com/jcmturner/gofork v1.7.6 // indirect
	github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
	github.com/jcmturner/rpc/v2 v2.0.3 // indirect
	github.com/klauspost/compress v1.18.0 // indirect
	github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
	github.com/oklog/ulid v1.3.1 // indirect
	github.com/pierrec/lz4/v4 v4.1.22 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
	github.com/sony/gobreaker v1.0.0 // indirect
	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
	go.opentelemetry.io/otel v1.38.0 // indirect
	go.opentelemetry.io/otel/metric v1.38.0 // indirect
	go.opentelemetry.io/otel/trace v1.38.0 // indirect
	golang.org/x/crypto v0.41.0 // indirect
	golang.org/x/net v0.43.0 // indirect
)


================================================
FILE: _examples/basic/2-realtime-feed/consumer/go.sum
================================================
github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=
github.com/IBM/sarama v1.46.0/go.mod h1:0lOcuQziJ1/mBGHkdp5uYrltqQuKQKM5O5FOWUQVVvo=
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0 h1:DfhVM1ieq+rb+bboB7aoymUgfKsEM3UbH3noQZ6+RJ4=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0/go.mod h1:o1GcoF/1CSJ9JSmQzUkULvpZeO635pZe+WWrYNFlJNk=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: _examples/basic/2-realtime-feed/consumer/main.go
================================================
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"sync/atomic"
	"time"

	"github.com/ThreeDotsLabs/watermill"
	"github.com/ThreeDotsLabs/watermill-kafka/v3/pkg/kafka"
	"github.com/ThreeDotsLabs/watermill/message"
	"github.com/ThreeDotsLabs/watermill/message/router/middleware"
	"github.com/ThreeDotsLabs/watermill/message/router/plugin"
)

var (
	marshaler = kafka.DefaultMarshaler{}
	brokers   = []string{"kafka:9092"}
)

func main() {
	logger := watermill.NewStdLogger(false, false)
	logger.Info("Starting the consumer", nil)

	pub, err := kafka.NewPublisher(
		kafka.PublisherConfig{
			Brokers:   brokers,
			Marshaler: marshaler,
		},
		logger,
	)
	if err != nil {
		panic(err)
	}

	r, err := message.NewRouter(message.RouterConfig{}, logger)
	if err != nil {
		panic(err)
	}

	retryMiddleware := middleware.Retry{
		MaxRetries:      1,
		InitialInterval: time.Millisecond * 10,
	}

	poisonQueue, err := middleware.PoisonQueue(pub, "poison_queue")
	if err != nil {
		panic(err)
	}

	r.AddMiddleware(
		// Recoverer middleware recovers panic from handlers and middlewares
		middleware.Recoverer,

		// Limit incoming messages to 10 per second
		middleware.NewThrottle(10, time.Second).Middleware,

		// If the retries limit is exceeded (see retryMiddleware below), the message is sent
		// to the poison queue (published to poison_queue topic)
		poisonQueue,

		// Retry middleware retries message processing if an error occurred in the handler
		retryMiddleware.Middleware,

		// Correlation ID middleware adds the correlation ID of the consumed message to each produced message.
		// It's useful for debugging.
		middleware.CorrelationID,

		// Simulate errors or panics from handler
		middleware.RandomFail(0.01),
		middleware.RandomPanic(0.01),
	)

	// Close the router when a SIGTERM is sent
	r.AddPlugin(plugin.SignalsHandler)

	// Handler that counts consumed posts
	r.AddHandler(
		"posts_counter",
		"posts_published",
		createSubscriber("posts_counter", logger),
		"posts_count",
		pub,
		PostsCounter{memoryCountStorage{new(int64)}}.Count,
	)

	// Handler that generates "feed" from consumed posts
	//
	// This implementation just prints the posts on stdout,
	// but production ready implementation would save posts to some persistent storage.
	r.AddConsumerHandler(
		"feed_generator",
		"posts_published",
		createSubscriber("feed_generator", logger),
		FeedGenerator{printFeedStorage{}}.UpdateFeed,
	)

	if err = r.Run(context.Background()); err != nil {
		panic(err)
	}
}

func createSubscriber(consumerGroup string, logger watermill.LoggerAdapter) message.Subscriber {
	sub, err := kafka.NewSubscriber(
		kafka.SubscriberConfig{
			Brokers:       brokers,
			Unmarshaler:   marshaler,
			ConsumerGroup: consumerGroup,
		},
		logger,
	)
	if err != nil {
		panic(err)
	}

	return sub
}

type postsCountUpdated struct {
	NewCount int64 `json:"new_count"`
}

type countStorage interface {
	CountAdd() (int64, error)
	Count() (int64, error)
}

type memoryCountStorage struct {
	count *int64
}

func (m memoryCountStorage) CountAdd() (int64, error) {
	return atomic.AddInt64(m.count, 1), nil
}

func (m memoryCountStorage) Count() (int64, error) {
	return atomic.LoadInt64(m.count), nil
}

type PostsCounter struct {
	countStorage countStorage
}

func (p PostsCounter) Count(msg *message.Message) ([]*message.Message, error) {
	// When implementing counter for production use, you'd probably need to add some kind of deduplication here,
	// unless the used Pub/Sub supports exactly-once delivery.

	newCount, err := p.countStorage.CountAdd()
	if err != nil {
		return nil, fmt.Errorf("cannot add count: %w", err)
	}

	producedMsg := postsCountUpdated{NewCount: newCount}
	b, err := json.Marshal(producedMsg)
	if err != nil {
		return nil, err
	}

	return []*message.Message{message.NewMessage(watermill.NewUUID(), b)}, nil
}

// postAdded might look similar to the postAdded type from producer.
// It's intentionally not imported here. We avoid coupling the services at the cost of duplication.
// We don't need all of its data either (content is not displayed on the feed).
type postAdded struct {
	OccurredOn time.Time `json:"occurred_on"`
	Author     string    `json:"author"`
	Title      string    `json:"title"`
}

type feedStorage interface {
	AddToFeed(title, author string, time time.Time) error
}

type printFeedStorage struct{}

func (printFeedStorage) AddToFeed(title, author string, time time.Time) error {
	fmt.Printf("Adding to feed: %s by %s @%s\n", title, author, time)
	return nil
}

type FeedGenerator struct {
	feedStorage feedStorage
}

func (f FeedGenerator) UpdateFeed(message *message.Message) error {
	event := postAdded{}
	if err := json.Unmarshal(message.Payload, &event); err != nil {
		return err
	}

	err := f.feedStorage.AddToFeed(event.Title, event.Author, event.OccurredOn)
	if err != nil {
		return fmt.Errorf("cannot update feed: %w", err)
	}

	return nil
}


================================================
FILE: _examples/basic/2-realtime-feed/docker-compose.yml
================================================
services:
  producer:
    image: golang:1.25
    restart: unless-stopped
    depends_on:
    - kafka
    volumes:
    - .:/app
    - $GOPATH/pkg/mod:/go/pkg/mod
    working_dir: /app/producer/
    command: go run main.go

  consumer:
    image: golang:1.25
    restart: unless-stopped
    depends_on:
    - kafka
    volumes:
    - .:/app
    - $GOPATH/pkg/mod:/go/pkg/mod
    working_dir: /app/consumer/
    command: >
      /bin/sh -c "go install github.com/ThreeDotsLabs/watermill/tools/mill@latest &&
                  go run main.go"

  zookeeper:
    image: confluentinc/cp-zookeeper:7.3.1
    restart: unless-stopped
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
    logging:
      driver: none

  kafka:
    image: confluentinc/cp-kafka:7.3.1
    restart: unless-stopped
    logging:
      driver: none
    depends_on:
    - zookeeper
    environment:
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"


================================================
FILE: _examples/basic/2-realtime-feed/producer/go.mod
================================================
module main.go

go 1.25

require (
	github.com/ThreeDotsLabs/watermill v1.5.1
	github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0
	github.com/brianvoe/gofakeit/v6 v6.28.0
)

require (
	github.com/IBM/sarama v1.46.0 // indirect
	github.com/cenkalti/backoff/v5 v5.0.3 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect
	github.com/eapache/go-resiliency v1.7.0 // indirect
	github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
	github.com/eapache/queue v1.1.0 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/golang/snappy v1.0.0 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/hashicorp/errwrap v1.1.0 // indirect
	github.com/hashicorp/go-multierror v1.1.1 // indirect
	github.com/hashicorp/go-uuid v1.0.3 // indirect
	github.com/jcmturner/aescts/v2 v2.0.0 // indirect
	github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
	github.com/jcmturner/gofork v1.7.6 // indirect
	github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
	github.com/jcmturner/rpc/v2 v2.0.3 // indirect
	github.com/klauspost/compress v1.18.0 // indirect
	github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
	github.com/oklog/ulid v1.3.1 // indirect
	github.com/pierrec/lz4/v4 v4.1.22 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
	github.com/sony/gobreaker v1.0.0 // indirect
	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
	go.opentelemetry.io/otel v1.38.0 // indirect
	go.opentelemetry.io/otel/metric v1.38.0 // indirect
	go.opentelemetry.io/otel/trace v1.38.0 // indirect
	golang.org/x/crypto v0.41.0 // indirect
	golang.org/x/net v0.43.0 // indirect
)


================================================
FILE: _examples/basic/2-realtime-feed/producer/go.sum
================================================
github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=
github.com/IBM/sarama v1.46.0/go.mod h1:0lOcuQziJ1/mBGHkdp5uYrltqQuKQKM5O5FOWUQVVvo=
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0 h1:DfhVM1ieq+rb+bboB7aoymUgfKsEM3UbH3noQZ6+RJ4=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0/go.mod h1:o1GcoF/1CSJ9JSmQzUkULvpZeO635pZe+WWrYNFlJNk=
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: _examples/basic/2-realtime-feed/producer/main.go
================================================
package main

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"os"
	"os/signal"
	"sync"
	"time"

	"github.com/brianvoe/gofakeit/v6"

	"github.com/ThreeDotsLabs/watermill"
	"github.com/ThreeDotsLabs/watermill-kafka/v3/pkg/kafka"
	"github.com/ThreeDotsLabs/watermill/message"
	"github.com/ThreeDotsLabs/watermill/message/router/middleware"
)

var (
	brokers = []string{"kafka:9092"}

	messagesPerSecond = 100
	numWorkers        = 20
)

func main() {
	logger := watermill.NewStdLogger(false, false)
	logger.Info("Starting the producer", watermill.LogFields{})

	publisher, err := kafka.NewPublisher(
		kafka.PublisherConfig{
			Brokers:   brokers,
			Marshaler: kafka.DefaultMarshaler{},
		},
		logger,
	)
	if err != nil {
		panic(err)
	}
	defer publisher.Close()

	closeCh := make(chan struct{})
	workersGroup := &sync.WaitGroup{}
	workersGroup.Add(numWorkers)

	for i := 0; i < numWorkers; i++ {
		go worker(publisher, workersGroup, closeCh)
	}

	// wait for SIGINT
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	<-c

	// signal for the workers to stop publishing
	close(closeCh)

	// Waiting for all messages to be published
	workersGroup.Wait()

	logger.Info("All messages published", nil)
}

// worker publishes messages until closeCh is closed.
func worker(publisher message.Publisher, wg *sync.WaitGroup, closeCh chan struct{}) {
	ticker := time.NewTicker(time.Duration(int(time.Second) / messagesPerSecond))

	for {
		select {
		case <-closeCh:
			ticker.Stop()
			wg.Done()
			return

		case <-ticker.C:
		}

		msgPayload := postAdded{
			OccurredOn: time.Now(),
			Author:     gofakeit.Username(),
			Title:      gofakeit.Sentence(rand.Intn(5) + 1),
			Content:    gofakeit.Sentence(rand.Intn(10) + 5),
		}

		payload, err := json.Marshal(msgPayload)
		if err != nil {
			panic(err)
		}

		msg := message.NewMessage(watermill.NewUUID(), payload)

		// Use a middleware to set the correlation ID, it's useful for debugging
		middleware.SetCorrelationID(watermill.NewShortUUID(), msg)
		err = publisher.Publish("posts_published", msg)
		if err != nil {
			fmt.Println("cannot publish message:", err)
			continue
		}
	}
}

type postAdded struct {
	OccurredOn time.Time `json:"occurred_on"`

	Author string `json:"author"`
	Title  string `json:"title"`

	Content string `json:"content"`
}


================================================
FILE: _examples/basic/3-router/.validate_example.yml
================================================
validation_cmd: "go run main.go"
timeout: 120
expected_output: "Received message: [0-9a-f\\-]+"


================================================
FILE: _examples/basic/3-router/go.mod
================================================
module main.go

go 1.25

require github.com/ThreeDotsLabs/watermill v1.5.1

require (
	github.com/cenkalti/backoff/v5 v5.0.3 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
	github.com/oklog/ulid v1.3.1 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/sony/gobreaker v1.0.0 // indirect
)


================================================
FILE: _examples/basic/3-router/go.sum
================================================
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: _examples/basic/3-router/main.go
================================================
// Sources for https://watermill.io/learn/getting-started/
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/ThreeDotsLabs/watermill"
	"github.com/ThreeDotsLabs/watermill/message"
	"github.com/ThreeDotsLabs/watermill/message/router/middleware"
	"github.com/ThreeDotsLabs/watermill/message/router/plugin"
	"github.com/ThreeDotsLabs/watermill/pubsub/gochannel"
)

var (
	// For this example, we're using just a simple logger implementation,
	// You probably want to ship your own implementation of `watermill.LoggerAdapter`.
	logger = watermill.NewStdLogger(false, false)
)

func main() {
	router, err := message.NewRouter(message.RouterConfig{}, logger)
	if err != nil {
		panic(err)
	}

	// SignalsHandler will gracefully shutdown Router when SIGTERM is received.
	// You can also close the router by just calling `r.Close()`.
	router.AddPlugin(plugin.SignalsHandler)

	// Router level middleware are executed for every message sent to the router
	router.AddMiddleware(
		// CorrelationID will copy the correlation id from the incoming message's metadata to the produced messages
		middleware.CorrelationID,

		// The handler function is retried if it returns an error.
		// After MaxRetries, the message is Nacked and it's up to the PubSub to resend it.
		middleware.Retry{
			MaxRetries:      3,
			InitialInterval: time.Millisecond * 100,
			Logger:          logger,
		}.Middleware,

		// Recoverer handles panics from handlers.
		// In this case, it passes them as errors to the Retry middleware.
		middleware.Recoverer,
	)

	// For simplicity, we are using the gochannel Pub/Sub here,
	// You can replace it with any Pub/Sub implementation, it will work the same.
	pubSub := gochannel.NewGoChannel(gochannel.Config{}, logger)

	// Producing some incoming messages in background
	go publishMessages(pubSub)

	// AddHandler returns a handler which can be used to add handler level middleware
	// or to stop handler.
	handler := router.AddHandler(
		"struct_handler",          // handler name, must be unique
		"incoming_messages_topic", // topic from which we will read events
		pubSub,
		"outgoing_messages_topic", // topic to which we will publish events
		pubSub,
		structHandler{}.Handler,
	)

	// Handler level middleware is only executed for a specific handler
	// Such middleware can be added the same way the router level ones
	handler.AddMiddleware(func(h message.HandlerFunc) message.HandlerFunc {
		return func(message *message.Message) ([]*message.Message, error) {
			log.Println("executing handler specific middleware for ", message.UUID)

			return h(message)
		}
	})

	// just for debug, we are printing all messages received on `incoming_messages_topic`
	router.AddConsumerHandler(
		"print_incoming_messages",
		"incoming_messages_topic",
		pubSub,
		printMessages,
	)

	// just for debug, we are printing all events sent to `outgoing_messages_topic`
	router.AddConsumerHandler(
		"print_outgoing_messages",
		"outgoing_messages_topic",
		pubSub,
		printMessages,
	)

	// Now that all handlers are registered, we're running the Router.
	// Run is blocking while the router is running.
	ctx := context.Background()
	if err := router.Run(ctx); err != nil {
		panic(err)
	}
}

func publishMessages(publisher message.Publisher) {
	for {
		msg := message.NewMessage(watermill.NewUUID(), []byte("Hello, world!"))
		middleware.SetCorrelationID(watermill.NewUUID(), msg)

		log.Printf("sending message %s, correlation id: %s\n", msg.UUID, middleware.MessageCorrelationID(msg))

		if err := publisher.Publish("incoming_messages_topic", msg); err != nil {
			panic(err)
		}

		time.Sleep(time.Second)
	}
}

func printMessages(msg *message.Message) error {
	fmt.Printf(
		"\n> Received message: %s\n> %s\n> metadata: %v\n\n",
		msg.UUID, string(msg.Payload), msg.Metadata,
	)
	return nil
}

type structHandler struct {
	// we can add some dependencies here
}

func (s structHandler) Handler(msg *message.Message) ([]*message.Message, error) {
	log.Println("structHandler received message", msg.UUID)

	msg = message.NewMessage(watermill.NewUUID(), []byte("message produced by structHandler"))
	return message.Messages{msg}, nil
}


================================================
FILE: _examples/basic/4-metrics/.validate_example.yml
================================================
validation_cmd: "docker compose up"
teardown_cmd: "docker compose down"
timeout: 180
expected_output: "msg=\"Message acked\""


================================================
FILE: _examples/basic/4-metrics/README.md
================================================
# Prometheus metrics showcase

This is an example application that showcases how Watermill may be monitored with Prometheus metrics.

The docker-compose bundle contains the following services:

#### Golang

A [Golang](https://hub.docker.com/_/golang) image which runs the [example code](https://github.com/ThreeDotsLabs/watermill/blob/master/_examples/basic/4-metrics/main.go). It consists of a router with a single handler. 
 
The handler consumes messages from a [Gochannel PubSub](https://github.com/ThreeDotsLabs/watermill/tree/master/message/infrastructure/gochannel), and publishes 0-4 copies of the message with a preconfigured random delay.

Additionally, there is one goroutine which produces messages incoming to the handler with a gochannel publisher, and another goroutine which consumes the messages outgoing from the handler.

The router, the standalone publisher and the standalone subscriber are all decorated with the metrics code and their statistics will appear in the dashboard.

#### Prometheus
[Prometheus](https://hub.docker.com/r/prom/prometheus/), to scrape the metrics which the golang application exposes at `:8081/metrics` by default. It is configured by a [prometheus.yml](https://github.com/ThreeDotsLabs/watermill/blob/master/_examples/basic/4-metrics/prometheus.yml) file, which declares the endpoints that Prometheus will scrape.

#### Grafana
[Grafana](https://hub.docker.com/r/grafana/grafana), to visualize the metrics in a dashboard.

#### Running the example

To run the docker-compose bundle, go to `_examples/metrics` and execute:

```
docker-compose up
```

The golang app will start running, producing messages, passing them through the handler, and consuming the copies.

With default settings, the raw Prometheus metrics should appear at your http://localhost:8081/metrics. 

The Prometheus image should expose a more advanced UI at http://localhost:9090/graph, where you can investigate all the scraped metrics.

However, what is the most useful way to monitor is through the use of Grafana, which you can access at http://localhost:3000. 

#### Adding the Prometheus data source to Grafana

The fresh Grafana image should greet you with a login screen:

![Grafana login screen](https://threedots.tech/watermill-io/grafana_login.png)

Just use the default `admin:admin` credentials. You can skip changing the password, if you wish.

The next thing that we need to do is to add the Prometheus data source. Click on `Add data source`.

In the following screen:

1. Enter a name for the Prometheus data source. Let's name this data source `prometheus`.
1. Choose `Prometheus` from the `Type` dropdown.
1. Enter the `http://localhost:9090` value in the HTTP/URL section.
1. You can leave the remaining settings at default and click `Save & Test`.

![Prometheus data source configuration](https://threedots.tech/watermill-io/prometheus_data_source_config.png)

The Prometheus data source is now ready to use in Grafana.

#### Importing the Grafana dashboard

We have prepared a Grafana dashboard that visualizes the metrics exported by this example.

To import the Grafana dashboard, select Dashboard/Manage from the left menu, and then click on `+Import` (or go to http://localhost:3000/dashboard/import).

Enter the dashboard URL https://grafana.com/dashboards/9777 (or just the ID, 9777), and click on Load.

![Importing the dashboard](https://threedots.tech/watermill-io/grafana_import_dashboard.png)

Then select the Prometheus data source created in the previous step. Click on `Import`, and you're done!

### Find out more 

To find out more, about metrics be sure to check out the [Watermill docs](https://watermill.io/docs/metrics).


================================================
FILE: _examples/basic/4-metrics/docker-compose.yml
================================================
services:
  golang:
    image: golang:1.25
    restart: unless-stopped
    ports:
    - 8080:8080
    - 8081:8081
    depends_on:
      - prometheus
    volumes:
    - ../../../:/watermill
    - $GOPATH/pkg/mod:/go/pkg/mod
    working_dir: /watermill/_examples/basic/4-metrics
    command: go run main.go -metrics :8081 -delay 0.1

  prometheus:
    image: prom/prometheus
    restart: unless-stopped
    network_mode: host
    volumes:
     - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana:5.2.4
    network_mode: host



================================================
FILE: _examples/basic/4-metrics/go.mod
================================================
module main.go

go 1.25

require github.com/ThreeDotsLabs/watermill v1.5.1

require (
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/cenkalti/backoff/v5 v5.0.3 // indirect
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
	github.com/go-chi/chi/v5 v5.2.3 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/oklog/ulid v1.3.1 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/prometheus/client_golang v1.23.0 // indirect
	github.com/prometheus/client_model v0.6.2 // indirect
	github.com/prometheus/common v0.65.0 // indirect
	github.com/prometheus/procfs v0.17.0 // indirect
	github.com/sony/gobreaker v1.0.0 // indirect
	golang.org/x/sys v0.35.0 // indirect
	google.golang.org/protobuf v1.36.8 // indirect
)

// uncomment to use local sources
// replace github.com/ThreeDotsLabs/watermill => ../../../../watermill


================================================
FILE: _examples/basic/4-metrics/go.sum
================================================
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: _examples/basic/4-metrics/main.go
================================================
package main

import (
	"context"
	"errors"
	"flag"
	"math"
	"math/rand"
	"time"

	"github.com/ThreeDotsLabs/watermill"
	"github.com/ThreeDotsLabs/watermill/components/metrics"
	"github.com/ThreeDotsLabs/watermill/message"
	"github.com/ThreeDotsLabs/watermill/message/router/middleware"
	"github.com/ThreeDotsLabs/watermill/message/router/plugin"
	"github.com/ThreeDotsLabs/watermill/pubsub/gochannel"
)

var (
	metricsAddr  = flag.String("metrics", ":8081", "The address that will expose /metrics for Prometheus")
	handlerDelay = flag.Float64("delay", 0, "The stdev of normal distribution of delay in handler (in seconds), to simulate load")

	logger = watermill.NewStdLogger(true, true)
	random = rand.New(rand.NewSource(time.Now().Unix()))
)

func delay() {
	seconds := *handlerDelay
	if seconds == 0 {
		return
	}
	delay := math.Abs(random.NormFloat64() * seconds)
	time.Sleep(time.Duration(float64(time.Second) * delay))
}

// handler publishes 0-4 messages with a random delay.
func handler(msg *message.Message) ([]*message.Message, error) {
	delay()

	numOutgoing := random.Intn(4)
	outgoing := make([]*message.Message, numOutgoing)

	for i := 0; i < numOutgoing; i++ {
		outgoing[i] = msg.Copy()
	}
	return outgoing, nil
}

// consumeMessages consumes the messages exiting the handler.
func consumeMessages(subscriber message.Subscriber) {
	messages, err := subscriber.Subscribe(context.Background(), "pub_topic")
	if err != nil {
		panic(err)
	}

	for msg := range messages {
		msg.Ack()
	}
}

// produceMessages produces the incoming messages in delays of 50-100 milliseconds.
func produceMessages(routerClosed chan struct{}, publisher message.Publisher) {
	for {
		select {
		case <-routerClosed:
			return
		default:
			// go on
		}

		time.Sleep(50*time.Millisecond + time.Duration(random.Intn(50))*time.Millisecond)
		msg := message.NewMessage(watermill.NewUUID(), []byte{})
		_ = publisher.Publish("sub_topic", msg)
	}
}

func main() {
	flag.Parse()

	pubSub := gochannel.NewGoChannel(gochannel.Config{}, logger)

	router, err := message.NewRouter(
		message.RouterConfig{},
		logger,
	)
	if err != nil {
		panic(err)
	}

	prometheusRegistry, closeMetricsServer := metrics.CreateRegistryAndServeHTTP(*metricsAddr)
	defer closeMetricsServer()

	// we leave the namespace and subsystem empty
	metricsBuilder := metrics.NewPrometheusMetricsBuilder(prometheusRegistry, "", "")
	metricsBuilder.AddPrometheusRouterMetrics(router)

	router.AddMiddleware(
		middleware.Recoverer,
		middleware.RandomFail(0.1),
		middleware.RandomPanic(0.1),
	)
	router.AddPlugin(plugin.SignalsHandler)

	router.AddHandler(
		"metrics-example",
		"sub_topic",
		pubSub,
		"pub_topic",
		pubSub,
		handler,
	)

	pub := randomFailPublisherDecorator{pubSub, 0.1}

	// The handler's publisher and subscriber will be decorated by `AddPrometheusRouterMetrics`.
	// We are using the same pub/sub to generate messages incoming to the handler
	// and consume the outgoing messages.
	// They will have `handler_name=<no handler>` label in Prometheus.
	subWithMetrics, err := metricsBuilder.DecorateSubscriber(pubSub)
	if err != nil {
		panic(err)
	}
	pubWithMetrics, err := metricsBuilder.DecoratePublisher(pub)
	if err != nil {
		panic(err)
	}

	routerClosed := make(chan struct{})
	go produceMessages(routerClosed, pubWithMetrics)
	go consumeMessages(subWithMetrics)

	_ = router.Run(context.Background())
	close(routerClosed)
}

type randomFailPublisherDecorator struct {
	message.Publisher
	failProbability float64
}

func (r randomFailPublisherDecorator) Publish(topic string, messages ...*message.Message) error {
	if random.Float64() < r.failProbability {
		return errors.New("random publishing failure")
	}
	return r.Publisher.Publish(topic, messages...)
}


================================================
FILE: _examples/basic/4-metrics/prometheus.yml
================================================
# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

scrape_configs:
#  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
#  - job_name: 'prometheus'
#
#    # metrics_path defaults to '/metrics'
#    # scheme defaults to 'http'.
#
#    static_configs:
#    - targets: ['localhost:9090']

  - job_name: 'metrics_example'
    
    static_configs:
    - targets: ['localhost:8081']


================================================
FILE: _examples/basic/5-cqrs-protobuf/.validate_example.yml
================================================
validation_cmd: "docker compose up"
teardown_cmd: "docker compose down"
timeout: 120
expected_outputs:
  - "beers ordered to room \\d+"
  - "Already booked rooms for \\$\\d{2,}"


================================================
FILE: _examples/basic/5-cqrs-protobuf/Makefile
================================================
.PHONY: proto

proto:
	protoc --proto_path=proto --go_out=. --go_opt=paths=source_relative  proto/messages.proto


================================================
FILE: _examples/basic/5-cqrs-protobuf/README.md
================================================
# Example Golang CQRS application

This application is using [Watermill CQRS](http://watermill.io/docs/cqrs) component.

Detailed documentation for CQRS can be found in Watermill's docs: [http://watermill.io/docs/cqrs#usage](http://watermill.io/docs/cqrs).

![CQRS Event Storming](https://threedots.tech/watermill-io/cqrs-example-storming.png)

```mermaid
sequenceDiagram
    participant M as Main
    participant CB as CommandBus
    participant BRH as BookRoomHandler
    participant EB as EventBus
    participant OBRB as OrderBeerOnRoomBooked
    participant OBH as OrderBeerHandler
    participant BFR as BookingsFinancialReport

    Note over M,BFR: Commands use AMQP queue, Events use AMQP pub/sub
    
    M->>CB: Send(BookRoom)<br/>topic: commands.BookRoom
    CB->>BRH: Handle(BookRoom)
    
    BRH->>EB: Publish(RoomBooked)<br/>topic: events.RoomBooked
    
    par Process RoomBooked Event
        EB->>OBRB: Handle(RoomBooked)
        OBRB->>CB: Send(OrderBeer)<br/>topic: commands.OrderBeer
        CB->>OBH: Handle(OrderBeer)
        OBH->>EB: Publish(BeerOrdered)<br/>topic: events.BeerOrdered
        
        EB->>BFR: Handle(RoomBooked)
        Note over BFR: Updates financial report
    end
```


## Running

```bash
docker-compose up
```


================================================
FILE: _examples/basic/5-cqrs-protobuf/docker-compose.yml
================================================
services:
  golang:
    image: golang:1.25
    restart: unless-stopped
    ports:
    - 8080:8080
    depends_on:
      - rabbitmq
    links:
      - rabbitmq
    volumes:
    - .:/app
    - $GOPATH/pkg/mod:/go/pkg/mod
    working_dir: /app
    command: go run .

  rabbitmq:
    image: rabbitmq:3.7
    restart: unless-stopped
    attach: false


================================================
FILE: _examples/basic/5-cqrs-protobuf/go.mod
================================================
module main.go

require (
	github.com/ThreeDotsLabs/watermill v1.5.1
	github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2
	google.golang.org/protobuf v1.36.8
)

require (
	github.com/cenkalti/backoff/v3 v3.2.2 // indirect
	github.com/cenkalti/backoff/v5 v5.0.3 // indirect
	github.com/gogo/protobuf v1.3.2 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/hashicorp/errwrap v1.1.0 // indirect
	github.com/hashicorp/go-multierror v1.1.1 // indirect
	github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
	github.com/oklog/ulid v1.3.1 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/rabbitmq/amqp091-go v1.10.0 // indirect
	github.com/sony/gobreaker v1.0.0 // indirect
)

go 1.25


================================================
FILE: _examples/basic/5-cqrs-protobuf/go.sum
================================================
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2 h1:aeyFSR4SUsbszmocuFiYY13nsHorc6CXIS2Hy7+xgFU=
github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2/go.mod h1:+8tCh6VCuBcQWhfETCwzRINKQ1uyeg9moH3h7jMKxQk=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: _examples/basic/5-cqrs-protobuf/main.go
================================================
package main

import (
	"context"
	"fmt"
	"log/slog"
	"math/rand"
	"sync"
	"time"

	"github.com/ThreeDotsLabs/watermill"
	"github.com/ThreeDotsLabs/watermill-amqp/v3/pkg/amqp"
	"github.com/ThreeDotsLabs/watermill/components/cqrs"
	"github.com/ThreeDotsLabs/watermill/message"
	"github.com/ThreeDotsLabs/watermill/message/router/middleware"
	"google.golang.org/protobuf/types/known/timestamppb"
)

// BookRoomHandler is a command handler, which handles BookRoom command and emits RoomBooked.
//
// In CQRS, one command must be handled by only one handler.
// When another handler with this command is added to command processor, error will be returned.
type BookRoomHandler struct {
	eventBus *cqrs.EventBus
}

func (b BookRoomHandler) Handle(ctx context.Context, cmd *BookRoom) error {
	// some random price, in production you probably will calculate in wiser way
	price := (rand.Int63n(40) + 1) * 10

	slog.Info(
		"Booked room",
		"room_id", cmd.RoomId,
		"guest_name", cmd.GuestName,
		"start_date", time.Unix(cmd.StartDate.Seconds, int64(cmd.StartDate.Nanos)),
		"end_date", time.Unix(cmd.EndDate.Seconds, int64(cmd.EndDate.Nanos)),
	)

	// RoomBooked will be handled by OrderBeerOnRoomBooked event handler,
	// in future RoomBooked may be handled by multiple event handler
	if err := b.eventBus.Publish(ctx, &RoomBooked{
		ReservationId: watermill.NewUUID(),
		RoomId:        cmd.RoomId,
		GuestName:     cmd.GuestName,
		Price:         price,
		StartDate:     cmd.StartDate,
		EndDate:       cmd.EndDate,
	}); err != nil {
		return err
	}

	return nil
}

// OrderBeerOnRoomBooked is an event handler, which handles RoomBooked event and emits OrderBeer command.
type OrderBeerOnRoomBooked struct {
	commandBus *cqrs.CommandBus
}

func (o OrderBeerOnRoomBooked) Handle(ctx context.Context, event *RoomBooked) error {
	orderBeerCmd := &OrderBeer{
		RoomId: event.RoomId,
		Count:  rand.Int63n(10) + 1,
	}

	return o.commandBus.Send(ctx, orderBeerCmd)
}

// OrderBeerHandler is a command handler, which handles OrderBeer command and emits BeerOrdered.
// BeerOrdered is not handled by any event handler, but we may use persistent Pub/Sub to handle it in the future.
type OrderBeerHandler struct {
	eventBus *cqrs.EventBus
}

func (o OrderBeerHandler) Handle(ctx context.Context, cmd *OrderBeer) error {
	if rand.Int63n(10) == 0 {
		// sometimes there is no beer left, command will be retried
		return fmt.Errorf("no beer left for room %s, please try later", cmd.RoomId)
	}

	if err := o.eventBus.Publish(ctx, &BeerOrdered{
		RoomId: cmd.RoomId,
		Count:  cmd.Count,
	}); err != nil {
		return err
	}

	slog.Info(fmt.Sprintf("%d beers ordered to room %s", cmd.Count, cmd.RoomId))
	return nil
}

// BookingsFinancialReport is a read model, which calculates how much money we may earn from bookings.
// Like OrderBeerOnRoomBooked, it listens for RoomBooked event.
//
// This implementation is just writing to the memory. In production, you will probably will use some persistent storage.
type BookingsFinancialReport struct {
	handledBookings map[string]struct{}
	totalCharge     int64
	lock            sync.Mutex
}

func NewBookingsFinancialReport() *BookingsFinancialReport {
	return &BookingsFinancialReport{handledBookings: map[string]struct{}{}}
}

func (b *BookingsFinancialReport) Handle(ctx context.Context, event *RoomBooked) error {
	// Handle may be called concurrently, so it need to be thread safe.
	b.lock.Lock()
	defer b.lock.Unlock()

	// When we are using Pub/Sub which doesn't provide exactly-once delivery semantics, we need to deduplicate messages.
	// GoChannel Pub/Sub provides exactly-once delivery,
	// but let's make this example ready for other Pub/Sub implementations.
	if _, ok := b.handledBookings[event.ReservationId]; ok {
		return nil
	}
	b.handledBookings[event.ReservationId] = struct{}{}

	b.totalCharge += event.Price

	slog.Info(fmt.Sprintf(">>> Already booked rooms for $%d\n", b.totalCharge))
	return nil
}

var amqpAddress = "amqp://guest:guest@rabbitmq:5672/"

func main() {
	logger := watermill.NewSlogLoggerWithLevelMapping(nil, map[slog.Level]slog.Level{
		slog.LevelInfo: slog.LevelDebug,
	})

	cqrsMarshaler := cqrs.ProtoMarshaler{
		// It will generate topic names based on the event/command type.
		// So for example, for "RoomBooked" name will be "RoomBooked".
		//
		// This value is used to generate topic names with "generateEventsTopic" and "generateCommandsTopic" functions.
		GenerateName: cqrs.StructName,
	}

	generateEventsTopic := func(eventName string) string {
		return "events." + eventName
	}

	generateCommandsTopic := func(commandName string) string {
		return "commands." + commandName
	}

	// You can use any Pub/Sub implementation from here: https://watermill.io/pubsubs/
	// Detailed RabbitMQ implementation: https://watermill.io/pubsubs/amqp/
	// Commands will be sent to queue, because they need to be consumed once.
	commandsAMQPConfig := amqp.NewDurableQueueConfig(amqpAddress)
	commandsPublisher, err := amqp.NewPublisher(commandsAMQPConfig, logger)
	if err != nil {
		panic(err)
	}
	commandsSubscriber, err := amqp.NewSubscriber(commandsAMQPConfig, logger)
	if err != nil {
		panic(err)
	}

	// Events will be published to PubSub configured Rabbit, because they may be consumed by multiple consumers.
	// (in that case BookingsFinancialReport and OrderBeerOnRoomBooked).
	eventsPublisher, err := amqp.NewPublisher(amqp.NewDurablePubSubConfig(amqpAddress, nil), logger)
	if err != nil {
		panic(err)
	}

	// CQRS is built on messages router. Detailed documentation: https://watermill.io/docs/messages-router/
	router, err := message.NewRouter(message.RouterConfig{}, logger)
	if err != nil {
		panic(err)
	}

	// Simple middleware which will recover panics from event or command handlers.
	// More about router middlewares you can find in the documentation:
	// https://watermill.io/docs/messages-router/#middleware
	//
	// List of available middlewares you can find in message/router/middleware.
	router.AddMiddleware(middleware.Recoverer)

	commandBus, err := cqrs.NewCommandBusWithConfig(commandsPublisher, cqrs.CommandBusConfig{
		GeneratePublishTopic: func(params cqrs.CommandBusGeneratePublishTopicParams) (string, error) {
			return generateCommandsTopic(params.CommandName), nil
		},
		OnSend: func(params cqrs.CommandBusOnSendParams) error {
			logger.Info("Sending command", watermill.LogFields{
				"command_name": params.CommandName,
			})

			params.Message.Metadata.Set("sent_at", time.Now().String())

			return nil
		},
		Marshaler: cqrsMarshaler,
		Logger:    logger,
	})
	if err != nil {
		panic(err)
	}

	commandProcessor, err := cqrs.NewCommandProcessorWithConfig(
		router,
		cqrs.CommandProcessorConfig{
			GenerateSubscribeTopic: func(params cqrs.CommandProcessorGenerateSubscribeTopicParams) (string, error) {
				return generateCommandsTopic(params.CommandName), nil
			},
			SubscriberConstructor: func(params cqrs.CommandProcessorSubscriberConstructorParams) (message.Subscriber, error) {
				// we can reuse subscriber, because all commands have separated topics
				return commandsSubscriber, nil
			},
			OnHandle: func(params cqrs.CommandProcessorOnHandleParams) error {
				start := time.Now()

				err := params.Handler.Handle(params.Message.Context(), params.Command)

				logger.Info("Command handled", watermill.LogFields{
					"command_name": params.CommandName,
					"duration":     time.Since(start),
					"err":          err,
				})

				return err
			},
			Marshaler: cqrsMarshaler,
			Logger:    logger,
		},
	)
	if err != nil {
		panic(err)
	}

	eventBus, err := cqrs.NewEventBusWithConfig(eventsPublisher, cqrs.EventBusConfig{
		GeneratePublishTopic: func(params cqrs.GenerateEventPublishTopicParams) (string, error) {
			return generateEventsTopic(params.EventName), nil
		},

		OnPublish: func(params cqrs.OnEventSendParams) error {
			logger.Info("Publishing event", watermill.LogFields{
				"event_name": params.EventName,
			})

			params.Message.Metadata.Set("published_at", time.Now().String())

			return nil
		},

		Marshaler: cqrsMarshaler,
		Logger:    logger,
	})
	if err != nil {
		panic(err)
	}

	eventProcessor, err := cqrs.NewEventProcessorWithConfig(
		router,
		cqrs.EventProcessorConfig{
			GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
				return generateEventsTopic(params.EventName), nil
			},
			SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
				config := amqp.NewDurablePubSubConfig(
					amqpAddress,
					amqp.GenerateQueueNameTopicNameWithSuffix(params.HandlerName),
				)

				return amqp.NewSubscriber(config, logger)
			},

			OnHandle: func(params cqrs.EventProcessorOnHandleParams) error {
				start := time.Now()

				err := params.Handler.Handle(params.Message.Context(), params.Event)

				logger.Info("Event handled", watermill.LogFields{
					"event_name": params.EventName,
					"duration":   time.Since(start),
					"err":        err,
				})

				return err
			},

			Marshaler: cqrsMarshaler,
			Logger:    logger,
		},
	)
	if err != nil {
		panic(err)
	}

	err = commandProcessor.AddHandlers(
		cqrs.NewCommandHandler("BookRoomHandler", BookRoomHandler{eventBus}.Handle),
		cqrs.NewCommandHandler("OrderBeerHandler", OrderBeerHandler{eventBus}.Handle),
	)
	if err != nil {
		panic(err)
	}

	err = eventProcessor.AddHandlers(
		cqrs.NewEventHandler(
			"OrderBeerOnRoomBooked",
			OrderBeerOnRoomBooked{commandBus}.Handle,
		),
		cqrs.NewEventHandler(
			"LogBeerOrdered",
			func(ctx context.Context, event *BeerOrdered) error {
				logger.Info("Beer ordered", watermill.LogFields{
					"room_id": event.RoomId,
				})
				return nil
			},
		),
		cqrs.NewEventHandler(
			"BookingsFinancialReport",
			NewBookingsFinancialReport().Handle,
		),
	)
	if err != nil {
		panic(err)
	}

	// publish BookRoom commands every second to simulate incoming traffic
	go publishCommands(commandBus)

	// processors are based on router, so they will work when router will start
	if err := router.Run(context.Background()); err != nil {
		panic(err)
	}
}

func publishCommands(commandBus *cqrs.CommandBus) func() {
	i := 0
	for {
		i++

		startDate := timestamppb.New(time.Now())
		endDate := timestamppb.New(time.Now().Add(time.Hour * 24 * 3))

		bookRoomCmd := &BookRoom{
			RoomId:    fmt.Sprintf("%d", i),
			GuestName: "John",
			StartDate: startDate,
			EndDate:   endDate,
		}
		if err := commandBus.Send(context.Background(), bookRoomCmd); err != nil {
			panic(err)
		}

		time.Sleep(time.Second)
	}
}


================================================
FILE: _examples/basic/5-cqrs-protobuf/messages.pb.go
================================================
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.31.0
// 	protoc        v4.24.4
// source: messages.proto

package main

import (
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

type BookRoom struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	RoomId    string                 `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
	GuestName string                 `protobuf:"bytes,2,opt,name=guest_name,json=guestName,proto3" json:"guest_name,omitempty"`
	StartDate *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"`
	EndDate   *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"`
}

func (x *BookRoom) Reset() {
	*x = BookRoom{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *BookRoom) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*BookRoom) ProtoMessage() {}

func (x *BookRoom) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use BookRoom.ProtoReflect.Descriptor instead.
func (*BookRoom) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{0}
}

func (x *BookRoom) GetRoomId() string {
	if x != nil {
		return x.RoomId
	}
	return ""
}

func (x *BookRoom) GetGuestName() string {
	if x != nil {
		return x.GuestName
	}
	return ""
}

func (x *BookRoom) GetStartDate() *timestamppb.Timestamp {
	if x != nil {
		return x.StartDate
	}
	return nil
}

func (x *BookRoom) GetEndDate() *timestamppb.Timestamp {
	if x != nil {
		return x.EndDate
	}
	return nil
}

type RoomBooked struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	ReservationId string                 `protobuf:"bytes,1,opt,name=reservation_id,json=reservationId,proto3" json:"reservation_id,omitempty"`
	RoomId        string                 `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
	GuestName     string                 `protobuf:"bytes,3,opt,name=guest_name,json=guestName,proto3" json:"guest_name,omitempty"`
	Price         int64                  `protobuf:"varint,4,opt,name=price,proto3" json:"price,omitempty"`
	StartDate     *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"`
	EndDate       *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"`
}

func (x *RoomBooked) Reset() {
	*x = RoomBooked{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[1]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *RoomBooked) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*RoomBooked) ProtoMessage() {}

func (x *RoomBooked) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[1]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use RoomBooked.ProtoReflect.Descriptor instead.
func (*RoomBooked) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{1}
}

func (x *RoomBooked) GetReservationId() string {
	if x != nil {
		return x.ReservationId
	}
	return ""
}

func (x *RoomBooked) GetRoomId() string {
	if x != nil {
		return x.RoomId
	}
	return ""
}

func (x *RoomBooked) GetGuestName() string {
	if x != nil {
		return x.GuestName
	}
	return ""
}

func (x *RoomBooked) GetPrice() int64 {
	if x != nil {
		return x.Price
	}
	return 0
}

func (x *RoomBooked) GetStartDate() *timestamppb.Timestamp {
	if x != nil {
		return x.StartDate
	}
	return nil
}

func (x *RoomBooked) GetEndDate() *timestamppb.Timestamp {
	if x != nil {
		return x.EndDate
	}
	return nil
}

type OrderBeer struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
	Count  int64  `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
}

func (x *OrderBeer) Reset() {
	*x = OrderBeer{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[2]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *OrderBeer) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*OrderBeer) ProtoMessage() {}

func (x *OrderBeer) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[2]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use OrderBeer.ProtoReflect.Descriptor instead.
func (*OrderBeer) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{2}
}

func (x *OrderBeer) GetRoomId() string {
	if x != nil {
		return x.RoomId
	}
	return ""
}

func (x *OrderBeer) GetCount() int64 {
	if x != nil {
		return x.Count
	}
	return 0
}

type BeerOrdered struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
	Count  int64  `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
}

func (x *BeerOrdered) Reset() {
	*x = BeerOrdered{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[3]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *BeerOrdered) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*BeerOrdered) ProtoMessage() {}

func (x *BeerOrdered) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[3]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use BeerOrdered.ProtoReflect.Descriptor instead.
func (*BeerOrdered) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{3}
}

func (x *BeerOrdered) GetRoomId() string {
	if x != nil {
		return x.RoomId
	}
	return ""
}

func (x *BeerOrdered) GetCount() int64 {
	if x != nil {
		return x.Count
	}
	return 0
}

var File_messages_proto protoreflect.FileDescriptor

var file_messages_proto_rawDesc = []byte{
	0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
	0x12, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
	0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb4, 0x01, 0x0a, 0x08, 0x42, 0x6f, 0x6f, 0x6b,
	0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18,
	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a,
	0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x09, 0x67, 0x75, 0x65, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0a,
	0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
	0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74,
	0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64,
	0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0xf3,
	0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x6f, 0x6f, 0x6b, 0x65, 0x64, 0x12, 0x25, 0x0a,
	0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69,
	0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18,
	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a,
	0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x09, 0x67, 0x75, 0x65, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05,
	0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x70, 0x72, 0x69,
	0x63, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65,
	0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
	0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a,
	0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32,
	0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
	0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64,
	0x44, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x0a, 0x09, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x65, 0x65,
	0x72, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
	0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f,
	0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74,
	0x22, 0x3c, 0x0a, 0x0b, 0x42, 0x65, 0x65, 0x72, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x12,
	0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
	0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e,
	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x08,
	0x5a, 0x06, 0x2e, 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_messages_proto_rawDescOnce sync.Once
	file_messages_proto_rawDescData = file_messages_proto_rawDesc
)

func file_messages_proto_rawDescGZIP() []byte {
	file_messages_proto_rawDescOnce.Do(func() {
		file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_proto_rawDescData)
	})
	return file_messages_proto_rawDescData
}

var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_messages_proto_goTypes = []interface{}{
	(*BookRoom)(nil),              // 0: main.BookRoom
	(*RoomBooked)(nil),            // 1: main.RoomBooked
	(*OrderBeer)(nil),             // 2: main.OrderBeer
	(*BeerOrdered)(nil),           // 3: main.BeerOrdered
	(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
}
var file_messages_proto_depIdxs = []int32{
	4, // 0: main.BookRoom.start_date:type_name -> google.protobuf.Timestamp
	4, // 1: main.BookRoom.end_date:type_name -> google.protobuf.Timestamp
	4, // 2: main.RoomBooked.start_date:type_name -> google.protobuf.Timestamp
	4, // 3: main.RoomBooked.end_date:type_name -> google.protobuf.Timestamp
	4, // [4:4] is the sub-list for method output_type
	4, // [4:4] is the sub-list for method input_type
	4, // [4:4] is the sub-list for extension type_name
	4, // [4:4] is the sub-list for extension extendee
	0, // [0:4] is the sub-list for field type_name
}

func init() { file_messages_proto_init() }
func file_messages_proto_init() {
	if File_messages_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*BookRoom); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*RoomBooked); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_messages_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*OrderBeer); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_messages_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*BeerOrdered); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_messages_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   4,
			NumExtensions: 0,
			NumServices:   0,
		},
		GoTypes:           file_messages_proto_goTypes,
		DependencyIndexes: file_messages_proto_depIdxs,
		MessageInfos:      file_messages_proto_msgTypes,
	}.Build()
	File_messages_proto = out.File
	file_messages_proto_rawDesc = nil
	file_messages_proto_goTypes = nil
	file_messages_proto_depIdxs = nil
}


================================================
FILE: _examples/basic/5-cqrs-protobuf/proto/messages.proto
================================================
syntax = "proto3";
package main;

option go_package = "./main";

import "google/protobuf/timestamp.proto";

message BookRoom {
    string room_id = 1;
    string guest_name = 2;

    google.protobuf.Timestamp start_date = 4;
    google.protobuf.Timestamp end_date = 5;
}

message RoomBooked {
    string reservation_id = 1;
    string room_id = 2;
    string guest_name = 3;
    int64 price = 4;

    google.protobuf.Timestamp start_date = 5;
    google.protobuf.Timestamp end_date = 6;
}

message OrderBeer {
    string room_id = 1;
    int64 count = 2;
}


message BeerOrdered {
    string room_id = 1;
    int64 count = 2;
}

================================================
FILE: _examples/basic/6-cqrs-ordered-events/.validate_example.yml
================================================
validation_cmd: "docker compose up"
teardown_cmd: "docker compose down"
timeout: 120
expected_outputs:
  - "Subscriber added subscriber_id"
  - "Subscriber updated subscriber_id"
  - "Subscriber removed subscriber_id"
  - "\\[ACTIVITY\\] activity_type=SUBSCRIBED"
  - "\\[ACTIVITY\\] activity_type=UNSUBSCRIBED"
  - "\\[ACTIVITY\\] activity_type=EMAIL_UPDATED"


================================================
FILE: _examples/basic/6-cqrs-ordered-events/Makefile
================================================
.PHONY: proto

proto:
	protoc --proto_path=proto --go_out=. --go_opt=paths=source_relative  proto/messages.proto


================================================
FILE: _examples/basic/6-cqrs-ordered-events/README.md
================================================
# Example Golang CQRS application - ordered events with Kafka

This application is using [Watermill CQRS](http://watermill.io/docs/cqrs) component.

Detailed documentation for CQRS can be found in Watermill's docs: [http://watermill.io/docs/cqrs#usage](http://watermill.io/docs/cqrs).

This example, uses event groups to keep order for multiple events. You can read more about them in the [Watermill documentation](https://watermill.io/docs/cqrs/).
We are also using Kafka's partitioning keys to increase processing throughput without losing order of events.


## What does this application do?

This application manages an email subscription system where users can:

1. Subscribe to receive emails by providing their email address
2. Update their email address after subscribing
3. Unsubscribe from the mailing list

The system maintains:
- A current list of all active subscribers
- A timeline of all subscription-related activities

In this example, keeping order of events is crucial.
If events won't be ordered, and `SubscriberSubscribed` would arrive after `SubscriberUnsubscribed` event,
the subscriber will be still subscribed.

## Possible improvements

In this example, we are using global `events` and `commands` topics.
You can consider splitting them into smaller topics, for example, per aggregate type.

Thanks to that, you can scale your application horizontally and increase the throughput and processing less events.

## Running

```bash
docker-compose up
```


================================================
FILE: _examples/basic/6-cqrs-ordered-events/activity.go
================================================
package main

import (
	"context"
	"fmt"
	"log/slog"
	"sync"
	"time"
)

// ActivityEntry represents a single event in the timeline
type ActivityEntry struct {
	Timestamp    time.Time
	SubscriberID string
	ActivityType string
	Details      string
}

// ActivityTimelineReadModel maintains a chronological log of all subscription-related events
type ActivityTimelineReadModel struct {
	activities []ActivityEntry
	lock       sync.RWMutex
}

func NewActivityTimelineModel() *ActivityTimelineReadModel {
	return &ActivityTimelineReadModel{
		activities: make([]ActivityEntry, 0),
	}
}

// OnSubscribed handles subscription events
func (m *ActivityTimelineReadModel) OnSubscribed(ctx context.Context, event *SubscriberSubscribed) error {
	m.lock.Lock()
	defer m.lock.Unlock()

	entry := ActivityEntry{
		Timestamp:    time.Now(),
		SubscriberID: event.SubscriberId,
		ActivityType: "SUBSCRIBED",
		Details:      fmt.Sprintf("Subscribed with email: %s", event.Email),
	}

	m.activities = append(m.activities, entry)
	m.logActivity(entry)
	return nil
}

// OnUnsubscribed handles unsubscription events
func (m *ActivityTimelineReadModel) OnUnsubscribed(ctx context.Context, event *SubscriberUnsubscribed) error {
	m.lock.Lock()
	defer m.lock.Unlock()

	entry := ActivityEntry{
		Timestamp:    time.Now(),
		SubscriberID: event.SubscriberId,
		ActivityType: "UNSUBSCRIBED",
		Details:      "Subscriber unsubscribed",
	}

	m.activities = append(m.activities, entry)
	m.logActivity(entry)
	return nil
}

// OnEmailUpdated handles email update events
func (m *ActivityTimelineReadModel) OnEmailUpdated(ctx context.Context, event *SubscriberEmailUpdated) error {
	m.lock.Lock()
	defer m.lock.Unlock()

	entry := ActivityEntry{
		Timestamp:    time.Now(),
		SubscriberID: event.SubscriberId,
		ActivityType: "EMAIL_UPDATED",
		Details:      fmt.Sprintf("Email updated to: %s", event.NewEmail),
	}

	m.activities = append(m.activities, entry)
	m.logActivity(entry)
	return nil
}

func (m *ActivityTimelineReadModel) logActivity(entry ActivityEntry) {
	slog.Info(
		"[ACTIVITY]",
		"activity_type", entry.ActivityType,
		"subscriber_id", entry.SubscriberID,
		"details", entry.Details,
	)
}


================================================
FILE: _examples/basic/6-cqrs-ordered-events/docker-compose.yml
================================================
services:
  golang:
    image: golang:1.25
    restart: unless-stopped
    ports:
    - 8080:8080
    depends_on:
      - kafka
      - zookeeper
    links:
      - kafka
      - zookeeper
    volumes:
    - .:/app
    - $GOPATH/pkg/mod:/go/pkg/mod
    working_dir: /app
    command: go run .

  zookeeper:
    container_name: zk
    attach: false
    image: confluentinc/cp-zookeeper:7.7.1
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    ports:
      - 2181:2181

  kafka:
    container_name: kafka
    attach: false
    image: confluentinc/cp-kafka:7.7.1
    depends_on:
      - zookeeper
    ports:
      - 9093:9093
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zk:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:9093
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

================================================
FILE: _examples/basic/6-cqrs-ordered-events/go.mod
================================================
module main.go

require (
	github.com/ThreeDotsLabs/watermill v1.5.1
	github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0
	google.golang.org/protobuf v1.36.8
)

require (
	github.com/IBM/sarama v1.46.0 // indirect
	github.com/cenkalti/backoff/v5 v5.0.3 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect
	github.com/eapache/go-resiliency v1.7.0 // indirect
	github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
	github.com/eapache/queue v1.1.0 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/gogo/protobuf v1.3.2 // indirect
	github.com/golang/snappy v1.0.0 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/hashicorp/errwrap v1.1.0 // indirect
	github.com/hashicorp/go-multierror v1.1.1 // indirect
	github.com/hashicorp/go-uuid v1.0.3 // indirect
	github.com/jcmturner/aescts/v2 v2.0.0 // indirect
	github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
	github.com/jcmturner/gofork v1.7.6 // indirect
	github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
	github.com/jcmturner/rpc/v2 v2.0.3 // indirect
	github.com/klauspost/compress v1.18.0 // indirect
	github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
	github.com/oklog/ulid v1.3.1 // indirect
	github.com/pierrec/lz4/v4 v4.1.22 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
	github.com/sony/gobreaker v1.0.0 // indirect
	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
	go.opentelemetry.io/otel v1.38.0 // indirect
	go.opentelemetry.io/otel/metric v1.38.0 // indirect
	go.opentelemetry.io/otel/trace v1.38.0 // indirect
	golang.org/x/crypto v0.41.0 // indirect
	golang.org/x/net v0.43.0 // indirect
)

go 1.25


================================================
FILE: _examples/basic/6-cqrs-ordered-events/go.sum
================================================
github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=
github.com/IBM/sarama v1.46.0/go.mod h1:0lOcuQziJ1/mBGHkdp5uYrltqQuKQKM5O5FOWUQVVvo=
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0 h1:DfhVM1ieq+rb+bboB7aoymUgfKsEM3UbH3noQZ6+RJ4=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0/go.mod h1:o1GcoF/1CSJ9JSmQzUkULvpZeO635pZe+WWrYNFlJNk=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: _examples/basic/6-cqrs-ordered-events/main.go
================================================
package main

import (
	"context"
	"fmt"
	"log/slog"
	"time"

	"github.com/ThreeDotsLabs/watermill"
	"github.com/ThreeDotsLabs/watermill-kafka/v3/pkg/kafka"
	"github.com/ThreeDotsLabs/watermill/components/cqrs"
	"github.com/ThreeDotsLabs/watermill/message"
	"github.com/ThreeDotsLabs/watermill/message/router/middleware"
)

func main() {
	slog.SetLogLoggerLevel(slog.LevelDebug)

	logger := watermill.NewSlogLoggerWithLevelMapping(nil, map[slog.Level]slog.Level{
		slog.LevelInfo: slog.LevelDebug,
	})

	// We are decorating ProtobufMarshaler to add extra metadata to the message.
	cqrsMarshaler := CqrsMarshalerDecorator{
		cqrs.ProtoMarshaler{
			// It will generate topic names based on the event/command type.
			// So for example, for "RoomBooked" name will be "RoomBooked".
			GenerateName: cqrs.StructName,
		},
	}

	watermillLogger := watermill.NewSlogLoggerWithLevelMapping(
		slog.With("watermill", true),
		map[slog.Level]slog.Level{
			slog.LevelInfo: slog.LevelDebug,
		},
	)

	// This marshaler converts Watermill messages to Kafka messages.
	// We are using it to add partition key to the Kafka message.
	kafkaMarshaler := kafka.NewWithPartitioningMarshaler(GenerateKafkaPartitionKey)

	// You can use any Pub/Sub implementation from here: https://watermill.io/pubsubs/
	publisher, err := kafka.NewPublisher(
		kafka.PublisherConfig{
			Brokers:   []string{"kafka:9092"},
			Marshaler: kafkaMarshaler,
		},
		watermillLogger,
	)
	if err != nil {
		panic(err)
	}

	// CQRS is built on messages router. Detailed documentation: https://watermill.io/docs/messages-router/
	router, err := message.NewRouter(message.RouterConfig{}, logger)
	if err != nil {
		panic(err)
	}

	// Simple middleware which will recover panics from event or command handlers.
	// More about router middlewares you can find in the documentation:
	// https://watermill.io/docs/messages-router/#middleware
	//
	// List of available middlewares you can find in message/router/middleware.
	router.AddMiddleware(middleware.Recoverer)
	router.AddMiddleware(func(h message.HandlerFunc) message.HandlerFunc {
		return func(msg *message.Message) ([]*message.Message, error) {
			slog.Debug("Received message", "metadata", msg.Metadata)
			return h(msg)
		}
	})

	commandBus, err := cqrs.NewCommandBusWithConfig(publisher, cqrs.CommandBusConfig{
		GeneratePublishTopic: func(params cqrs.CommandBusGeneratePublishTopicParams) (string, error) {
			// We are using one topic for all commands to maintain the order of commands.
			return "commands", nil
		},
		Marshaler: cqrsMarshaler,
		Logger:    logger,
	})
	if err != nil {
		panic(err)
	}

	eventBus, err := cqrs.NewEventBusWithConfig(publisher, cqrs.EventBusConfig{
		GeneratePublishTopic: func(params cqrs.GenerateEventPublishTopicParams) (string, error) {
			// We are using one topic for all events to maintain the order of events.
			return "events", nil
		},
		Marshaler: cqrsMarshaler,
		Logger:    logger,
	})
	if err != nil {
		panic(err)
	}

	commandProcessor, err := cqrs.NewCommandProcessorWithConfig(
		router,
		cqrs.CommandProcessorConfig{
			GenerateSubscribeTopic: func(params cqrs.CommandProcessorGenerateSubscribeTopicParams) (string, error) {
				return "commands", nil
			},
			SubscriberConstructor: func(params cqrs.CommandProcessorSubscriberConstructorParams) (message.Subscriber, error) {
				return kafka.NewSubscriber(
					kafka.SubscriberConfig{
						Brokers:       []string{"kafka:9092"},
						ConsumerGroup: params.HandlerName,
						Unmarshaler:   kafkaMarshaler,
					},
					watermillLogger,
				)
			},
			Marshaler: cqrsMarshaler,
			Logger:    logger,
		},
	)
	if err != nil {
		panic(err)
	}

	eventProcessor, err := cqrs.NewEventGroupProcessorWithConfig(
		router,
		cqrs.EventGroupProcessorConfig{
			GenerateSubscribeTopic: func(params cqrs.EventGroupProcessorGenerateSubscribeTopicParams) (string, error) {
				return "events", nil
			},
			SubscriberConstructor: func(params cqrs.EventGroupProcessorSubscriberConstructorParams) (message.Subscriber, error) {
				return kafka.NewSubscriber(
					kafka.SubscriberConfig{
						Brokers:       []string{"kafka:9092"},
						ConsumerGroup: params.EventGroupName,
						Unmarshaler:   kafkaMarshaler,
					},
					watermillLogger,
				)
			},
			Marshaler: cqrsMarshaler,
			Logger:    logger,
		},
	)
	if err != nil {
		panic(err)
	}

	err = commandProcessor.AddHandlers(
		cqrs.NewCommandHandler("SubscribeHandler", SubscribeHandler{eventBus}.Handle),
		cqrs.NewCommandHandler("UnsubscribeHandler", UnsubscribeHandler{eventBus}.Handle),
		cqrs.NewCommandHandler("UpdateEmailHandler", UpdateEmailHandler{eventBus}.Handle),
	)
	if err != nil {
		panic(err)
	}

	subscribersReadModel := NewSubscriberReadModel()

	// All messages from this group will have one subscription.
	// When message arrives, Watermill will match it with the correct handler.
	err = eventProcessor.AddHandlersGroup(
		"SubscriberReadModel",
		cqrs.NewGroupEventHandler(subscribersReadModel.OnSubscribed),
		cqrs.NewGroupEventHandler(subscribersReadModel.OnUnsubscribed),
		cqrs.NewGroupEventHandler(subscribersReadModel.OnEmailUpdated),
	)
	if err != nil {
		panic(err)
	}

	activityReadModel := NewActivityTimelineModel()

	// All messages from this group will have one subscription.
	// When message arrives, Watermill will match it with the correct handler.
	err = eventProcessor.AddHandlersGroup(
		"ActivityTimelineReadModel",
		cqrs.NewGroupEventHandler(activityReadModel.OnSubscribed),
		cqrs.NewGroupEventHandler(activityReadModel.OnUnsubscribed),
		cqrs.NewGroupEventHandler(activityReadModel.OnEmailUpdated),
	)
	if err != nil {
		panic(err)
	}

	slog.Info("Starting service")

	go simulateTraffic(commandBus)

	if err := router.Run(context.Background()); err != nil {
		panic(err)
	}
}

func simulateTraffic(commandBus *cqrs.CommandBus) {
	for i := 0; ; i++ {
		subscriberID := watermill.NewUUID()

		err := commandBus.Send(context.Background(), &Subscribe{
			Metadata:     GenerateMessageMetadata(subscriberID),
			SubscriberId: subscriberID,
			Email:        fmt.Sprintf("user%d@example.com", i),
		})
		if err != nil {
			slog.Error("Error sending Subscribe command", "err", err)
		}
		time.Sleep(time.Millisecond * 500)

		err = commandBus.Send(context.Background(), &UpdateEmail{
			Metadata:     GenerateMessageMetadata(subscriberID),
			SubscriberId: subscriberID,
			NewEmail:     fmt.Sprintf("updated%d@example.com", i),
		})
		if err != nil {
			slog.Error("Error sending UpdateEmail command", "err", err)
		}
		time.Sleep(time.Millisecond * 500)

		if i%3 == 0 {
			err = commandBus.Send(context.Background(), &Unsubscribe{
				Metadata:     GenerateMessageMetadata(subscriberID),
				SubscriberId: subscriberID,
			})
			if err != nil {
				slog.Error("Error sending Unsubscribe command", "err", err)
			}
		}
		time.Sleep(time.Millisecond * 500)
	}
}

type SubscribeHandler struct {
	eventBus *cqrs.EventBus
}

func (h SubscribeHandler) Handle(ctx context.Context, cmd *Subscribe) error {
	return h.eventBus.Publish(ctx, &SubscriberSubscribed{
		Metadata:     GenerateMessageMetadata(cmd.SubscriberId),
		SubscriberId: cmd.SubscriberId,
		Email:        cmd.Email,
	})
}

type UnsubscribeHandler struct {
	eventBus *cqrs.EventBus
}

func (h UnsubscribeHandler) Handle(ctx context.Context, cmd *Unsubscribe) error {
	return h.eventBus.Publish(ctx, &SubscriberUnsubscribed{
		Metadata:     GenerateMessageMetadata(cmd.SubscriberId),
		SubscriberId: cmd.SubscriberId,
	})
}

type UpdateEmailHandler struct {
	eventBus *cqrs.EventBus
}

func (h UpdateEmailHandler) Handle(ctx context.Context, cmd *UpdateEmail) error {
	return h.eventBus.Publish(ctx, &SubscriberEmailUpdated{
		Metadata:     GenerateMessageMetadata(cmd.SubscriberId),
		SubscriberId: cmd.SubscriberId,
		NewEmail:     cmd.NewEmail,
	})
}


================================================
FILE: _examples/basic/6-cqrs-ordered-events/message.go
================================================
package main

import (
	"fmt"
	"log/slog"

	"github.com/ThreeDotsLabs/watermill/components/cqrs"
	"github.com/ThreeDotsLabs/watermill/message"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func GenerateMessageMetadata(partitionKey string) *MessageMetadata {
	return &MessageMetadata{
		PartitionKey: partitionKey,
		CreatedAt:    timestamppb.Now(),
	}
}

type CqrsMarshalerDecorator struct {
	cqrs.ProtoMarshaler
}

const PartitionKeyMetadataField = "partition_key"

func (c CqrsMarshalerDecorator) Marshal(v interface{}) (*message.Message, error) {
	msg, err := c.ProtoMarshaler.Marshal(v)
	if err != nil {
		return nil, err
	}

	pm, ok := v.(ProtoMessage)
	if !ok {
		return nil, fmt.Errorf("%T does not implement ProtoMessage and can't be marshaled", v)
	}

	metadata := pm.GetMetadata()
	if metadata == nil {
		return nil, fmt.Errorf("%T.GetMetadata returned nil", v)
	}

	msg.Metadata.Set(PartitionKeyMetadataField, metadata.PartitionKey)
	msg.Metadata.Set("created_at", metadata.CreatedAt.AsTime().String())

	return msg, nil
}

type ProtoMessage interface {
	GetMetadata() *MessageMetadata
}

// GenerateKafkaPartitionKey is a function that generates a partition key for Kafka messages.
func GenerateKafkaPartitionKey(topic string, msg *message.Message) (string, error) {
	slog.Debug("Setting partition key", "topic", topic, "msg_metadata", msg.Metadata)

	return msg.Metadata.Get(PartitionKeyMetadataField), nil
}


================================================
FILE: _examples/basic/6-cqrs-ordered-events/messages.pb.go
================================================
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.31.0
// 	protoc        v4.24.4
// source: messages.proto

package main

import (
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

type MessageMetadata struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	PartitionKey string                 `protobuf:"bytes,1,opt,name=partition_key,json=partitionKey,proto3" json:"partition_key,omitempty"`
	CreatedAt    *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
}

func (x *MessageMetadata) Reset() {
	*x = MessageMetadata{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *MessageMetadata) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*MessageMetadata) ProtoMessage() {}

func (x *MessageMetadata) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use MessageMetadata.ProtoReflect.Descriptor instead.
func (*MessageMetadata) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{0}
}

func (x *MessageMetadata) GetPartitionKey() string {
	if x != nil {
		return x.PartitionKey
	}
	return ""
}

func (x *MessageMetadata) GetCreatedAt() *timestamppb.Timestamp {
	if x != nil {
		return x.CreatedAt
	}
	return nil
}

// Commands
type Subscribe struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Metadata     *MessageMetadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
	SubscriberId string           `protobuf:"bytes,2,opt,name=subscriber_id,json=subscriberId,proto3" json:"subscriber_id,omitempty"`
	Email        string           `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
}

func (x *Subscribe) Reset() {
	*x = Subscribe{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[1]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *Subscribe) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*Subscribe) ProtoMessage() {}

func (x *Subscribe) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[1]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use Subscribe.ProtoReflect.Descriptor instead.
func (*Subscribe) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{1}
}

func (x *Subscribe) GetMetadata() *MessageMetadata {
	if x != nil {
		return x.Metadata
	}
	return nil
}

func (x *Subscribe) GetSubscriberId() string {
	if x != nil {
		return x.SubscriberId
	}
	return ""
}

func (x *Subscribe) GetEmail() string {
	if x != nil {
		return x.Email
	}
	return ""
}

type Unsubscribe struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Metadata     *MessageMetadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
	SubscriberId string           `protobuf:"bytes,2,opt,name=subscriber_id,json=subscriberId,proto3" json:"subscriber_id,omitempty"`
}

func (x *Unsubscribe) Reset() {
	*x = Unsubscribe{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[2]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *Unsubscribe) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*Unsubscribe) ProtoMessage() {}

func (x *Unsubscribe) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[2]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use Unsubscribe.ProtoReflect.Descriptor instead.
func (*Unsubscribe) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{2}
}

func (x *Unsubscribe) GetMetadata() *MessageMetadata {
	if x != nil {
		return x.Metadata
	}
	return nil
}

func (x *Unsubscribe) GetSubscriberId() string {
	if x != nil {
		return x.SubscriberId
	}
	return ""
}

type UpdateEmail struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Metadata     *MessageMetadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
	SubscriberId string           `protobuf:"bytes,2,opt,name=subscriber_id,json=subscriberId,proto3" json:"subscriber_id,omitempty"`
	NewEmail     string           `protobuf:"bytes,3,opt,name=new_email,json=newEmail,proto3" json:"new_email,omitempty"`
}

func (x *UpdateEmail) Reset() {
	*x = UpdateEmail{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[3]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *UpdateEmail) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*UpdateEmail) ProtoMessage() {}

func (x *UpdateEmail) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[3]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use UpdateEmail.ProtoReflect.Descriptor instead.
func (*UpdateEmail) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{3}
}

func (x *UpdateEmail) GetMetadata() *MessageMetadata {
	if x != nil {
		return x.Metadata
	}
	return nil
}

func (x *UpdateEmail) GetSubscriberId() string {
	if x != nil {
		return x.SubscriberId
	}
	return ""
}

func (x *UpdateEmail) GetNewEmail() string {
	if x != nil {
		return x.NewEmail
	}
	return ""
}

// Events
type SubscriberSubscribed struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Metadata     *MessageMetadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
	SubscriberId string           `protobuf:"bytes,2,opt,name=subscriber_id,json=subscriberId,proto3" json:"subscriber_id,omitempty"`
	Email        string           `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
}

func (x *SubscriberSubscribed) Reset() {
	*x = SubscriberSubscribed{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[4]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *SubscriberSubscribed) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*SubscriberSubscribed) ProtoMessage() {}

func (x *SubscriberSubscribed) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[4]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use SubscriberSubscribed.ProtoReflect.Descriptor instead.
func (*SubscriberSubscribed) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{4}
}

func (x *SubscriberSubscribed) GetMetadata() *MessageMetadata {
	if x != nil {
		return x.Metadata
	}
	return nil
}

func (x *SubscriberSubscribed) GetSubscriberId() string {
	if x != nil {
		return x.SubscriberId
	}
	return ""
}

func (x *SubscriberSubscribed) GetEmail() string {
	if x != nil {
		return x.Email
	}
	return ""
}

type SubscriberUnsubscribed struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Metadata     *MessageMetadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
	SubscriberId string           `protobuf:"bytes,2,opt,name=subscriber_id,json=subscriberId,proto3" json:"subscriber_id,omitempty"`
}

func (x *SubscriberUnsubscribed) Reset() {
	*x = SubscriberUnsubscribed{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[5]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *SubscriberUnsubscribed) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*SubscriberUnsubscribed) ProtoMessage() {}

func (x *SubscriberUnsubscribed) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[5]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use SubscriberUnsubscribed.ProtoReflect.Descriptor instead.
func (*SubscriberUnsubscribed) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{5}
}

func (x *SubscriberUnsubscribed) GetMetadata() *MessageMetadata {
	if x != nil {
		return x.Metadata
	}
	return nil
}

func (x *SubscriberUnsubscribed) GetSubscriberId() string {
	if x != nil {
		return x.SubscriberId
	}
	return ""
}

type SubscriberEmailUpdated struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Metadata     *MessageMetadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
	SubscriberId string           `protobuf:"bytes,2,opt,name=subscriber_id,json=subscriberId,proto3" json:"subscriber_id,omitempty"`
	NewEmail     string           `protobuf:"bytes,3,opt,name=new_email,json=newEmail,proto3" json:"new_email,omitempty"`
}

func (x *SubscriberEmailUpdated) Reset() {
	*x = SubscriberEmailUpdated{}
	if protoimpl.UnsafeEnabled {
		mi := &file_messages_proto_msgTypes[6]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *SubscriberEmailUpdated) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*SubscriberEmailUpdated) ProtoMessage() {}

func (x *SubscriberEmailUpdated) ProtoReflect() protoreflect.Message {
	mi := &file_messages_proto_msgTypes[6]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use SubscriberEmailUpdated.ProtoReflect.Descriptor instead.
func (*SubscriberEmailUpdated) Descriptor() ([]byte, []int) {
	return file_messages_proto_rawDescGZIP(), []int{6}
}

func (x *SubscriberEmailUpdated) GetMetadata() *MessageMetadata {
	if x != nil {
		return x.Metadata
	}
	return nil
}

func (x *SubscriberEmailUpdated) GetSubscriberId() string {
	if x != nil {
		return x.SubscriberId
	}
	return ""
}

func (x *SubscriberEmailUpdated) GetNewEmail() string {
	if x != nil {
		return x.NewEmail
	}
	return ""
}

var File_messages_proto protoreflect.FileDescriptor

var file_messages_proto_rawDesc = []byte{
	0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
	0x12, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
	0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x71, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x73, 0x61,
	0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61,
	0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12,
	0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20,
	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
	0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x79, 0x0a, 0x09, 0x53, 0x75,
	0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
	0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x69, 0x6e,
	0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
	0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75,
	0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x49, 0x64, 0x12,
	0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
	0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x65, 0x0a, 0x0b, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63,
	0x72, 0x69, 0x62, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x65,
	0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d,
	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63,
	0x72, 0x69, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
	0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x49, 0x64, 0x22, 0x82, 0x01, 0x0a,
	0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x31, 0x0a, 0x08,
	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15,
	0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74,
	0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
	0x23, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64,
	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
	0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x65, 0x6d, 0x61, 0x69,
	0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x45, 0x6d, 0x61, 0x69,
	0x6c, 0x22, 0x84, 0x01, 0x0a, 0x14, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72,
	0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65,
	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d,
	0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64,
	0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a,
	0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02,
	0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72,
	0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x70, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73,
	0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
	0x65, 0x64, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01,
	0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x65, 0x73, 0x73,
	0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74,
	0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
	0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x75,
	0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x49, 0x64, 0x22, 0x8d, 0x01, 0x0a, 0x16, 0x53,
	0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x55, 0x70,
	0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
	0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d,
	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08,
	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73,
	0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
	0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a,
	0x09, 0x6e, 0x65, 0x77, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
	0x52, 0x08, 0x6e, 0x65, 0x77, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f,
	0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_messages_proto_rawDescOnce sync.Once
	file_messages_proto_rawDescData = file_messages_proto_rawDesc
)

func file_messages_proto_rawDescGZIP() []byte {
	file_messages_proto_rawDescOnce.Do(func() {
		file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_proto_rawDescData)
	})
	return file_messages_proto_rawDescData
}

var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_messages_proto_goTypes = []interface{}{
	(*MessageMetadata)(nil),        // 0: main.MessageMetadata
	(*Subscribe)(nil),              // 1: main.Subscribe
	(*Unsubscribe)(nil),            // 2: main.Unsubscribe
	(*UpdateEmail)(nil),            // 3: main.UpdateEmail
	(*SubscriberSubscribed)(nil),   // 4: main.SubscriberSubscribed
	(*SubscriberUnsubscribed)(nil), // 5: main.SubscriberUnsubscribed
	(*SubscriberEmailUpdated)(nil), // 6: main.SubscriberEmailUpdated
	(*timestamppb.Timestamp)(nil),  // 7: google.protobuf.Timestamp
}
var file_messages_proto_depIdxs = []int32{
	7, // 0: main.MessageMetadata.created_at:type_name -> google.protobuf.Timestamp
	0, // 1: main.Subscribe.metadata:type_name -> main.MessageMetadata
	0, // 2: main.Unsubscribe.metadata:type_name -> main.MessageMetadata
	0, // 3: main.UpdateEmail.metadata:type_name -> main.MessageMetadata
	0, // 4: main.SubscriberSubscribed.metadata:type_name -> main.MessageMetadata
	0, // 5: main.SubscriberUnsubscribed.metadata:type_name -> main.MessageMetadata
	0, // 6: main.SubscriberEmailUpdated.metadata:type_name -> main.MessageMetadata
	7, // [7:7] is the sub-list for method output_type
	7, // [7:7] is the sub-list for method input_type
	7, // [7:7] is the sub-list for extension type_name
	7, // [7:7] is the sub-list for extension extendee
	0, // [0:7] is the sub-list for field type_name
}

func init() { file_messages_proto_init() }
func file_messages_proto_init() {
	if File_messages_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*MessageMetadata); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*Subscribe); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_messages_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*Unsubscribe); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_messages_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*UpdateEmail); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_messages_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*SubscriberSubscribed); i {
			cas
Download .txt
gitextract_cdt0elb2/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   └── workflows/
│       ├── master.yml
│       ├── pr-examples.yml
│       ├── pr.yml
│       └── tests.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── RELEASE-PROCEDURE.md
├── UPGRADE-0.3.md
├── UPGRADE-0.4.md
├── UPGRADE-1.0.md
├── _examples/
│   ├── basic/
│   │   ├── 1-your-first-app/
│   │   │   ├── .validate_example.yml
│   │   │   ├── README.md
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── 2-realtime-feed/
│   │   │   ├── .validate_example_subscribing.yml
│   │   │   ├── README.md
│   │   │   ├── consumer/
│   │   │   │   ├── go.mod
│   │   │   │   ├── go.sum
│   │   │   │   └── main.go
│   │   │   ├── docker-compose.yml
│   │   │   └── producer/
│   │   │       ├── go.mod
│   │   │       ├── go.sum
│   │   │       └── main.go
│   │   ├── 3-router/
│   │   │   ├── .validate_example.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── 4-metrics/
│   │   │   ├── .validate_example.yml
│   │   │   ├── README.md
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   ├── main.go
│   │   │   └── prometheus.yml
│   │   ├── 5-cqrs-protobuf/
│   │   │   ├── .validate_example.yml
│   │   │   ├── Makefile
│   │   │   ├── README.md
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   ├── main.go
│   │   │   ├── messages.pb.go
│   │   │   └── proto/
│   │   │       └── messages.proto
│   │   └── 6-cqrs-ordered-events/
│   │       ├── .validate_example.yml
│   │       ├── Makefile
│   │       ├── README.md
│   │       ├── activity.go
│   │       ├── docker-compose.yml
│   │       ├── go.mod
│   │       ├── go.sum
│   │       ├── main.go
│   │       ├── message.go
│   │       ├── messages.pb.go
│   │       ├── proto/
│   │       │   └── messages.proto
│   │       └── subscribers.go
│   ├── pubsubs/
│   │   ├── amqp/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── aws-sns/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── aws-sqs/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── go-channel/
│   │   │   ├── .validate_example.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── googlecloud/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── kafka/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── nats-core/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── nats-jetstream/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── nats-streaming/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── redisstream/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── sql/
│   │   │   ├── .validate_example.yml
│   │   │   ├── docker-compose.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   └── main.go
│   │   ├── sqlite/
│   │   │   ├── .gitignore
│   │   │   ├── .validate_example.yml
│   │   │   ├── go.mod
│   │   │   ├── go.sum
│   │   │   ├── main.go
│   │   │   └── transaction.go
│   │   └── sqlite-zombiezen/
│   │       ├── .gitignore
│   │       ├── .validate_example.yml
│   │       ├── go.mod
│   │       ├── go.sum
│   │       ├── main.go
│   │       └── transaction.go
│   └── real-world-examples/
│       ├── consumer-groups/
│       │   ├── README.md
│       │   ├── api/
│       │   │   ├── http.go
│       │   │   ├── main.go
│       │   │   ├── public/
│       │   │   │   └── index.html
│       │   │   └── storage.go
│       │   ├── common/
│       │   │   ├── events.go
│       │   │   └── messaging.go
│       │   ├── crm-service/
│       │   │   └── main.go
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── newsletter-service/
│       │       └── main.go
│       ├── delayed-messages/
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       ├── delayed-requeue/
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       ├── exactly-once-delivery-counter/
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── run.go
│       │   ├── schema.sql
│       │   ├── server/
│       │   │   ├── go.mod
│       │   │   ├── go.sum
│       │   │   └── main.go
│       │   └── worker/
│       │       ├── go.mod
│       │       ├── go.sum
│       │       └── main.go
│       ├── persistent-event-log/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       ├── receiving-webhooks/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       ├── sending-webhooks/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── producer/
│       │   │   ├── go.mod
│       │   │   ├── go.sum
│       │   │   └── main.go
│       │   ├── router/
│       │   │   ├── go.mod
│       │   │   ├── go.sum
│       │   │   └── main.go
│       │   └── webhooks-server/
│       │       └── main.go
│       ├── server-sent-events/
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── schema.sql
│       │   └── server/
│       │       ├── event_handlers.go
│       │       ├── feeds_storage.go
│       │       ├── go.mod
│       │       ├── go.sum
│       │       ├── http.go
│       │       ├── main.go
│       │       ├── models.go
│       │       ├── posts_storage.go
│       │       └── public/
│       │           └── index.html
│       ├── server-sent-events-htmx/
│       │   ├── Dockerfile
│       │   ├── README.md
│       │   ├── docker/
│       │   │   ├── Dockerfile
│       │   │   └── reflex.conf
│       │   ├── docker-compose.yml
│       │   ├── events.go
│       │   ├── go.mod
│       │   ├── go.sum
│       │   ├── http.go
│       │   ├── main.go
│       │   ├── models.go
│       │   ├── repository.go
│       │   └── views/
│       │       ├── base.templ
│       │       ├── base_templ.go
│       │       ├── pages.templ
│       │       └── pages_templ.go
│       ├── synchronizing-databases/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   ├── main.go
│       │   ├── mysql.go
│       │   └── postgres.go
│       ├── transactional-events/
│       │   ├── .validate_example.yml
│       │   ├── README.md
│       │   ├── docker-compose.yml
│       │   ├── go.mod
│       │   ├── go.sum
│       │   └── main.go
│       └── transactional-events-forwarder/
│           ├── .validate_example.yml
│           ├── README.md
│           ├── docker-compose.yml
│           ├── go.mod
│           ├── go.sum
│           └── main.go
├── codecov.yml
├── components/
│   ├── cqrs/
│   │   ├── command_bus.go
│   │   ├── command_bus_test.go
│   │   ├── command_handler.go
│   │   ├── command_handler_test.go
│   │   ├── command_processor.go
│   │   ├── command_processor_test.go
│   │   ├── cqrs.go
│   │   ├── cqrs_test.go
│   │   ├── ctx.go
│   │   ├── doc.go
│   │   ├── event_bus.go
│   │   ├── event_bus_test.go
│   │   ├── event_handler.go
│   │   ├── event_handler_test.go
│   │   ├── event_processor.go
│   │   ├── event_processor_group.go
│   │   ├── event_processor_group_test.go
│   │   ├── event_processor_test.go
│   │   ├── marshaler.go
│   │   ├── marshaler_json.go
│   │   ├── marshaler_json_test.go
│   │   ├── marshaler_protobuf.go
│   │   ├── marshaler_protobuf_events_new_test.go
│   │   ├── marshaler_protobuf_events_test.go
│   │   ├── marshaler_protobuf_gogo.go
│   │   ├── marshaler_protobuf_gogo_test.go
│   │   ├── marshaler_protobuf_test.go
│   │   ├── name.go
│   │   ├── name_test.go
│   │   ├── object.go
│   │   └── testdata/
│   │       └── events.proto
│   ├── delay/
│   │   ├── delay.go
│   │   ├── publisher.go
│   │   └── publisher_test.go
│   ├── fanin/
│   │   ├── fanin.go
│   │   └── fanin_test.go
│   ├── forwarder/
│   │   ├── envelope.go
│   │   ├── envelope_test.go
│   │   ├── forwarder.go
│   │   ├── forwarder_test.go
│   │   └── publisher.go
│   ├── metrics/
│   │   ├── builder.go
│   │   ├── ctx.go
│   │   ├── handler.go
│   │   ├── http.go
│   │   ├── http_test.go
│   │   ├── labels.go
│   │   ├── publisher.go
│   │   └── subscriber.go
│   ├── requestreply/
│   │   ├── backend_pubsub.go
│   │   ├── backend_pubsub_marshaler.go
│   │   ├── command_bus.go
│   │   ├── handler.go
│   │   ├── requestreply.go
│   │   └── requestreply_test.go
│   └── requeuer/
│       ├── requeuer.go
│       └── requeuer_test.go
├── dev/
│   ├── consolidate-gomods/
│   │   └── main.go
│   ├── coverage.sh
│   ├── prometheus.yml
│   ├── update-examples-deps/
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── main.go
│   └── validate-examples/
│       ├── go.mod
│       ├── go.sum
│       └── main.go
├── doc.go
├── docs/
│   ├── .npmignore
│   ├── .npmrc
│   ├── .prettierignore
│   ├── .prettierrc.yaml
│   ├── DEVELOP.md
│   ├── assets/
│   │   ├── images/
│   │   │   └── .gitkeep
│   │   ├── js/
│   │   │   └── custom.js
│   │   ├── jsconfig.json
│   │   ├── scss/
│   │   │   └── common/
│   │   │       ├── _custom.scss
│   │   │       └── _variables-custom.scss
│   │   └── svgs/
│   │       └── .gitkeep
│   ├── build.sh
│   ├── config/
│   │   ├── _default/
│   │   │   ├── hugo.toml
│   │   │   ├── languages.toml
│   │   │   ├── markup.toml
│   │   │   ├── menus/
│   │   │   │   └── menus.en.toml
│   │   │   ├── module.toml
│   │   │   └── params.toml
│   │   ├── babel.config.js
│   │   ├── next/
│   │   │   └── hugo.toml
│   │   ├── postcss.config.js
│   │   └── production/
│   │       └── hugo.toml
│   ├── content/
│   │   ├── _index.md
│   │   ├── advanced/
│   │   │   ├── delayed-messages.md
│   │   │   ├── fanin.md
│   │   │   ├── fanout.md
│   │   │   ├── forwarder.md
│   │   │   ├── metrics.md
│   │   │   └── requeuing-after-error.md
│   │   ├── development/
│   │   │   ├── benchmark.md
│   │   │   ├── contributing.md
│   │   │   ├── pub-sub-implementing.md
│   │   │   └── releases.md
│   │   ├── docs/
│   │   │   ├── _index.md
│   │   │   ├── articles.md
│   │   │   ├── awesome.md
│   │   │   ├── cqrs.md
│   │   │   ├── message/
│   │   │   │   ├── .validate_example.yml
│   │   │   │   ├── go.mod
│   │   │   │   ├── go.sum
│   │   │   │   └── receiving-ack.go
│   │   │   ├── message.md
│   │   │   ├── messages-router.md
│   │   │   ├── middlewares.md
│   │   │   ├── pub-sub.md
│   │   │   ├── snippets/
│   │   │   │   ├── amqp-consumer-groups/
│   │   │   │   │   ├── .validate_example.yml
│   │   │   │   │   ├── docker-compose.yml
│   │   │   │   │   ├── go.mod
│   │   │   │   │   ├── go.sum
│   │   │   │   │   └── main.go
│   │   │   │   └── tail-log-file/
│   │   │   │       ├── go.mod
│   │   │   │       ├── go.sum
│   │   │   │       └── main.go
│   │   │   └── troubleshooting.md
│   │   ├── learn/
│   │   │   ├── _index.md
│   │   │   ├── getting-started.md
│   │   │   └── quickstart.md
│   │   ├── pubsubs/
│   │   │   ├── _index.md
│   │   │   ├── amqp.md
│   │   │   ├── aws.md
│   │   │   ├── bolt.md
│   │   │   ├── firestore.md
│   │   │   ├── gochannel.md
│   │   │   ├── googlecloud.md
│   │   │   ├── http.md
│   │   │   ├── io.md
│   │   │   ├── kafka.md
│   │   │   ├── nats.md
│   │   │   ├── redisstream.md
│   │   │   ├── sql.md
│   │   │   └── sqlite.md
│   │   └── support.md
│   ├── extract_middleware_godocs.py
│   ├── layouts/
│   │   ├── _default/
│   │   │   ├── _markup/
│   │   │   │   └── render-link.html
│   │   │   ├── learn.html
│   │   │   └── quickstart.html
│   │   ├── index.html
│   │   ├── partials/
│   │   │   ├── footer/
│   │   │   │   ├── footer.html
│   │   │   │   └── script-footer-custom.html
│   │   │   ├── head/
│   │   │   │   ├── custom-head.html
│   │   │   │   ├── resource-hints.html
│   │   │   │   └── script-header.html
│   │   │   ├── header/
│   │   │   │   └── header.html
│   │   │   ├── main/
│   │   │   │   └── edit-page.html
│   │   │   ├── private/
│   │   │   │   └── has-headings.html
│   │   │   ├── seo/
│   │   │   │   ├── opengraph.html
│   │   │   │   └── twitter.html
│   │   │   └── sidebar/
│   │   │       └── section-menu.html
│   │   └── shortcodes/
│   │       ├── load-snippet-partial.html
│   │       ├── load-snippet.html
│   │       ├── readfile.html
│   │       ├── tab.html
│   │       └── tabs.html
│   ├── package.json
│   ├── resources/
│   │   └── _gen/
│   │       └── assets/
│   │           └── scss/
│   │               ├── app.scss_901a6e181e810c5c7347a10d84f037ab.content
│   │               ├── app.scss_901a6e181e810c5c7347a10d84f037ab.json
│   │               ├── app.scss_cdf9d7c9eb97e4550ded64a8776dd9e8.content
│   │               └── app.scss_cdf9d7c9eb97e4550ded64a8776dd9e8.json
│   └── static/
│       └── .gitkeep
├── go.mod
├── go.sum
├── internal/
│   ├── channel.go
│   ├── channel_test.go
│   ├── name.go
│   ├── name_test.go
│   ├── norace.go
│   ├── publisher/
│   │   ├── errors.go
│   │   ├── retry.go
│   │   └── retry_test.go
│   ├── race.go
│   └── subscriber/
│       └── multiplier.go
├── log.go
├── log_test.go
├── message/
│   ├── decorator.go
│   ├── decorator_bench_test.go
│   ├── decorator_test.go
│   ├── message.go
│   ├── message_test.go
│   ├── messages.go
│   ├── messages_test.go
│   ├── metadata.go
│   ├── pubsub.go
│   ├── router/
│   │   ├── middleware/
│   │   │   ├── circuit_breaker.go
│   │   │   ├── circuit_breaker_test.go
│   │   │   ├── correlation.go
│   │   │   ├── correlation_test.go
│   │   │   ├── deduplicator.go
│   │   │   ├── deduplicator_test.go
│   │   │   ├── delay_on_error.go
│   │   │   ├── delay_on_error_test.go
│   │   │   ├── duplicator.go
│   │   │   ├── duplicator_test.go
│   │   │   ├── ignore_errors.go
│   │   │   ├── ignore_errors_test.go
│   │   │   ├── instant_ack.go
│   │   │   ├── instant_ack_test.go
│   │   │   ├── message_test.go
│   │   │   ├── poison.go
│   │   │   ├── poison_test.go
│   │   │   ├── randomfail.go
│   │   │   ├── randomfail_test.go
│   │   │   ├── recoverer.go
│   │   │   ├── recoverer_test.go
│   │   │   ├── retry.go
│   │   │   ├── retry_test.go
│   │   │   ├── throttle.go
│   │   │   ├── throttle_test.go
│   │   │   ├── timeout.go
│   │   │   └── timeout_test.go
│   │   └── plugin/
│   │       └── signals.go
│   ├── router.go
│   ├── router_context.go
│   ├── router_context_test.go
│   ├── router_test.go
│   └── subscriber/
│       ├── read.go
│       └── read_test.go
├── netlify.toml
├── pubsub/
│   ├── doc.go
│   ├── gochannel/
│   │   ├── doc.go
│   │   ├── fanout.go
│   │   ├── fanout_test.go
│   │   ├── pubsub.go
│   │   ├── pubsub_bench_test.go
│   │   ├── pubsub_internal_test.go
│   │   ├── pubsub_stress_test.go
│   │   └── pubsub_test.go
│   ├── sync/
│   │   ├── waitgroup.go
│   │   └── waitgroup_test.go
│   └── tests/
│       ├── bench_pubsub.go
│       ├── test_asserts.go
│       ├── test_pubsub.go
│       └── test_pubsub_stress.go
├── slog.go
├── slog_test.go
├── tools/
│   ├── mill/
│   │   ├── .default-config.yml
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── cmd/
│   │   │   ├── amqp.go
│   │   │   ├── consume.go
│   │   │   ├── googlecloud.go
│   │   │   ├── internal/
│   │   │   │   └── indent.go
│   │   │   ├── kafka.go
│   │   │   ├── produce.go
│   │   │   └── root.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── main.go
│   └── pq/
│       ├── README.md
│       ├── backend/
│       │   └── postgres.go
│       ├── cli/
│       │   ├── backend.go
│       │   ├── message.go
│       │   └── model.go
│       ├── go.mod
│       ├── go.sum
│       └── main.go
├── uuid.go
└── uuid_test.go
Download .txt
SYMBOL INDEX (1538 symbols across 211 files)

FILE: _examples/basic/1-your-first-app/main.go
  type event (line 28) | type event struct
  type processedEvent (line 32) | type processedEvent struct
  function main (line 37) | func main() {
  function createPublisher (line 95) | func createPublisher() message.Publisher {
  function createSubscriber (line 111) | func createSubscriber(consumerGroup string) message.Subscriber {
  function simulateEvents (line 128) | func simulateEvents(publisher message.Publisher) {

FILE: _examples/basic/2-realtime-feed/consumer/main.go
  function main (line 22) | func main() {
  function createSubscriber (line 104) | func createSubscriber(consumerGroup string, logger watermill.LoggerAdapt...
  type postsCountUpdated (line 120) | type postsCountUpdated struct
  type countStorage (line 124) | type countStorage interface
  type memoryCountStorage (line 129) | type memoryCountStorage struct
    method CountAdd (line 133) | func (m memoryCountStorage) CountAdd() (int64, error) {
    method Count (line 137) | func (m memoryCountStorage) Count() (int64, error) {
  type PostsCounter (line 141) | type PostsCounter struct
    method Count (line 145) | func (p PostsCounter) Count(msg *message.Message) ([]*message.Message,...
  type postAdded (line 166) | type postAdded struct
  type feedStorage (line 172) | type feedStorage interface
  type printFeedStorage (line 176) | type printFeedStorage struct
    method AddToFeed (line 178) | func (printFeedStorage) AddToFeed(title, author string, time time.Time...
  type FeedGenerator (line 183) | type FeedGenerator struct
    method UpdateFeed (line 187) | func (f FeedGenerator) UpdateFeed(message *message.Message) error {

FILE: _examples/basic/2-realtime-feed/producer/main.go
  function main (line 27) | func main() {
  function worker (line 66) | func worker(publisher message.Publisher, wg *sync.WaitGroup, closeCh cha...
  type postAdded (line 103) | type postAdded struct

FILE: _examples/basic/3-router/main.go
  function main (line 23) | func main() {
  function publishMessages (line 103) | func publishMessages(publisher message.Publisher) {
  function printMessages (line 118) | func printMessages(msg *message.Message) error {
  type structHandler (line 126) | type structHandler struct
    method Handler (line 130) | func (s structHandler) Handler(msg *message.Message) ([]*message.Messa...

FILE: _examples/basic/4-metrics/main.go
  function delay (line 27) | func delay() {
  function handler (line 37) | func handler(msg *message.Message) ([]*message.Message, error) {
  function consumeMessages (line 50) | func consumeMessages(subscriber message.Subscriber) {
  function produceMessages (line 62) | func produceMessages(routerClosed chan struct{}, publisher message.Publi...
  function main (line 77) | func main() {
  type randomFailPublisherDecorator (line 136) | type randomFailPublisherDecorator struct
    method Publish (line 141) | func (r randomFailPublisherDecorator) Publish(topic string, messages ....

FILE: _examples/basic/5-cqrs-protobuf/main.go
  type BookRoomHandler (line 23) | type BookRoomHandler struct
    method Handle (line 27) | func (b BookRoomHandler) Handle(ctx context.Context, cmd *BookRoom) er...
  type OrderBeerOnRoomBooked (line 56) | type OrderBeerOnRoomBooked struct
    method Handle (line 60) | func (o OrderBeerOnRoomBooked) Handle(ctx context.Context, event *Room...
  type OrderBeerHandler (line 71) | type OrderBeerHandler struct
    method Handle (line 75) | func (o OrderBeerHandler) Handle(ctx context.Context, cmd *OrderBeer) ...
  type BookingsFinancialReport (line 96) | type BookingsFinancialReport struct
    method Handle (line 106) | func (b *BookingsFinancialReport) Handle(ctx context.Context, event *R...
  function NewBookingsFinancialReport (line 102) | func NewBookingsFinancialReport() *BookingsFinancialReport {
  function main (line 127) | func main() {
  function publishCommands (line 331) | func publishCommands(commandBus *cqrs.CommandBus) func() {

FILE: _examples/basic/5-cqrs-protobuf/messages.pb.go
  constant _ (line 19) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
  constant _ (line 21) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
  type BookRoom (line 24) | type BookRoom struct
    method Reset (line 35) | func (x *BookRoom) Reset() {
    method String (line 44) | func (x *BookRoom) String() string {
    method ProtoMessage (line 48) | func (*BookRoom) ProtoMessage() {}
    method ProtoReflect (line 50) | func (x *BookRoom) ProtoReflect() protoreflect.Message {
    method Descriptor (line 63) | func (*BookRoom) Descriptor() ([]byte, []int) {
    method GetRoomId (line 67) | func (x *BookRoom) GetRoomId() string {
    method GetGuestName (line 74) | func (x *BookRoom) GetGuestName() string {
    method GetStartDate (line 81) | func (x *BookRoom) GetStartDate() *timestamppb.Timestamp {
    method GetEndDate (line 88) | func (x *BookRoom) GetEndDate() *timestamppb.Timestamp {
  type RoomBooked (line 95) | type RoomBooked struct
    method Reset (line 108) | func (x *RoomBooked) Reset() {
    method String (line 117) | func (x *RoomBooked) String() string {
    method ProtoMessage (line 121) | func (*RoomBooked) ProtoMessage() {}
    method ProtoReflect (line 123) | func (x *RoomBooked) ProtoReflect() protoreflect.Message {
    method Descriptor (line 136) | func (*RoomBooked) Descriptor() ([]byte, []int) {
    method GetReservationId (line 140) | func (x *RoomBooked) GetReservationId() string {
    method GetRoomId (line 147) | func (x *RoomBooked) GetRoomId() string {
    method GetGuestName (line 154) | func (x *RoomBooked) GetGuestName() string {
    method GetPrice (line 161) | func (x *RoomBooked) GetPrice() int64 {
    method GetStartDate (line 168) | func (x *RoomBooked) GetStartDate() *timestamppb.Timestamp {
    method GetEndDate (line 175) | func (x *RoomBooked) GetEndDate() *timestamppb.Timestamp {
  type OrderBeer (line 182) | type OrderBeer struct
    method Reset (line 191) | func (x *OrderBeer) Reset() {
    method String (line 200) | func (x *OrderBeer) String() string {
    method ProtoMessage (line 204) | func (*OrderBeer) ProtoMessage() {}
    method ProtoReflect (line 206) | func (x *OrderBeer) ProtoReflect() protoreflect.Message {
    method Descriptor (line 219) | func (*OrderBeer) Descriptor() ([]byte, []int) {
    method GetRoomId (line 223) | func (x *OrderBeer) GetRoomId() string {
    method GetCount (line 230) | func (x *OrderBeer) GetCount() int64 {
  type BeerOrdered (line 237) | type BeerOrdered struct
    method Reset (line 246) | func (x *BeerOrdered) Reset() {
    method String (line 255) | func (x *BeerOrdered) String() string {
    method ProtoMessage (line 259) | func (*BeerOrdered) ProtoMessage() {}
    method ProtoReflect (line 261) | func (x *BeerOrdered) ProtoReflect() protoreflect.Message {
    method Descriptor (line 274) | func (*BeerOrdered) Descriptor() ([]byte, []int) {
    method GetRoomId (line 278) | func (x *BeerOrdered) GetRoomId() string {
    method GetCount (line 285) | func (x *BeerOrdered) GetCount() int64 {
  function file_messages_proto_rawDescGZIP (line 341) | func file_messages_proto_rawDescGZIP() []byte {
  function init (line 368) | func init() { file_messages_proto_init() }
  function file_messages_proto_init (line 369) | func file_messages_proto_init() {

FILE: _examples/basic/6-cqrs-ordered-events/activity.go
  type ActivityEntry (line 12) | type ActivityEntry struct
  type ActivityTimelineReadModel (line 20) | type ActivityTimelineReadModel struct
    method OnSubscribed (line 32) | func (m *ActivityTimelineReadModel) OnSubscribed(ctx context.Context, ...
    method OnUnsubscribed (line 49) | func (m *ActivityTimelineReadModel) OnUnsubscribed(ctx context.Context...
    method OnEmailUpdated (line 66) | func (m *ActivityTimelineReadModel) OnEmailUpdated(ctx context.Context...
    method logActivity (line 82) | func (m *ActivityTimelineReadModel) logActivity(entry ActivityEntry) {
  function NewActivityTimelineModel (line 25) | func NewActivityTimelineModel() *ActivityTimelineReadModel {

FILE: _examples/basic/6-cqrs-ordered-events/main.go
  function main (line 16) | func main() {
  function simulateTraffic (line 192) | func simulateTraffic(commandBus *cqrs.CommandBus) {
  type SubscribeHandler (line 229) | type SubscribeHandler struct
    method Handle (line 233) | func (h SubscribeHandler) Handle(ctx context.Context, cmd *Subscribe) ...
  type UnsubscribeHandler (line 241) | type UnsubscribeHandler struct
    method Handle (line 245) | func (h UnsubscribeHandler) Handle(ctx context.Context, cmd *Unsubscri...
  type UpdateEmailHandler (line 252) | type UpdateEmailHandler struct
    method Handle (line 256) | func (h UpdateEmailHandler) Handle(ctx context.Context, cmd *UpdateEma...

FILE: _examples/basic/6-cqrs-ordered-events/message.go
  function GenerateMessageMetadata (line 12) | func GenerateMessageMetadata(partitionKey string) *MessageMetadata {
  type CqrsMarshalerDecorator (line 19) | type CqrsMarshalerDecorator struct
    method Marshal (line 25) | func (c CqrsMarshalerDecorator) Marshal(v interface{}) (*message.Messa...
  constant PartitionKeyMetadataField (line 23) | PartitionKeyMetadataField = "partition_key"
  type ProtoMessage (line 47) | type ProtoMessage interface
  function GenerateKafkaPartitionKey (line 52) | func GenerateKafkaPartitionKey(topic string, msg *message.Message) (stri...

FILE: _examples/basic/6-cqrs-ordered-events/messages.pb.go
  constant _ (line 19) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
  constant _ (line 21) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
  type MessageMetadata (line 24) | type MessageMetadata struct
    method Reset (line 33) | func (x *MessageMetadata) Reset() {
    method String (line 42) | func (x *MessageMetadata) String() string {
    method ProtoMessage (line 46) | func (*MessageMetadata) ProtoMessage() {}
    method ProtoReflect (line 48) | func (x *MessageMetadata) ProtoReflect() protoreflect.Message {
    method Descriptor (line 61) | func (*MessageMetadata) Descriptor() ([]byte, []int) {
    method GetPartitionKey (line 65) | func (x *MessageMetadata) GetPartitionKey() string {
    method GetCreatedAt (line 72) | func (x *MessageMetadata) GetCreatedAt() *timestamppb.Timestamp {
  type Subscribe (line 80) | type Subscribe struct
    method Reset (line 90) | func (x *Subscribe) Reset() {
    method String (line 99) | func (x *Subscribe) String() string {
    method ProtoMessage (line 103) | func (*Subscribe) ProtoMessage() {}
    method ProtoReflect (line 105) | func (x *Subscribe) ProtoReflect() protoreflect.Message {
    method Descriptor (line 118) | func (*Subscribe) Descriptor() ([]byte, []int) {
    method GetMetadata (line 122) | func (x *Subscribe) GetMetadata() *MessageMetadata {
    method GetSubscriberId (line 129) | func (x *Subscribe) GetSubscriberId() string {
    method GetEmail (line 136) | func (x *Subscribe) GetEmail() string {
  type Unsubscribe (line 143) | type Unsubscribe struct
    method Reset (line 152) | func (x *Unsubscribe) Reset() {
    method String (line 161) | func (x *Unsubscribe) String() string {
    method ProtoMessage (line 165) | func (*Unsubscribe) ProtoMessage() {}
    method ProtoReflect (line 167) | func (x *Unsubscribe) ProtoReflect() protoreflect.Message {
    method Descriptor (line 180) | func (*Unsubscribe) Descriptor() ([]byte, []int) {
    method GetMetadata (line 184) | func (x *Unsubscribe) GetMetadata() *MessageMetadata {
    method GetSubscriberId (line 191) | func (x *Unsubscribe) GetSubscriberId() string {
  type UpdateEmail (line 198) | type UpdateEmail struct
    method Reset (line 208) | func (x *UpdateEmail) Reset() {
    method String (line 217) | func (x *UpdateEmail) String() string {
    method ProtoMessage (line 221) | func (*UpdateEmail) ProtoMessage() {}
    method ProtoReflect (line 223) | func (x *UpdateEmail) ProtoReflect() protoreflect.Message {
    method Descriptor (line 236) | func (*UpdateEmail) Descriptor() ([]byte, []int) {
    method GetMetadata (line 240) | func (x *UpdateEmail) GetMetadata() *MessageMetadata {
    method GetSubscriberId (line 247) | func (x *UpdateEmail) GetSubscriberId() string {
    method GetNewEmail (line 254) | func (x *UpdateEmail) GetNewEmail() string {
  type SubscriberSubscribed (line 262) | type SubscriberSubscribed struct
    method Reset (line 272) | func (x *SubscriberSubscribed) Reset() {
    method String (line 281) | func (x *SubscriberSubscribed) String() string {
    method ProtoMessage (line 285) | func (*SubscriberSubscribed) ProtoMessage() {}
    method ProtoReflect (line 287) | func (x *SubscriberSubscribed) ProtoReflect() protoreflect.Message {
    method Descriptor (line 300) | func (*SubscriberSubscribed) Descriptor() ([]byte, []int) {
    method GetMetadata (line 304) | func (x *SubscriberSubscribed) GetMetadata() *MessageMetadata {
    method GetSubscriberId (line 311) | func (x *SubscriberSubscribed) GetSubscriberId() string {
    method GetEmail (line 318) | func (x *SubscriberSubscribed) GetEmail() string {
  type SubscriberUnsubscribed (line 325) | type SubscriberUnsubscribed struct
    method Reset (line 334) | func (x *SubscriberUnsubscribed) Reset() {
    method String (line 343) | func (x *SubscriberUnsubscribed) String() string {
    method ProtoMessage (line 347) | func (*SubscriberUnsubscribed) ProtoMessage() {}
    method ProtoReflect (line 349) | func (x *SubscriberUnsubscribed) ProtoReflect() protoreflect.Message {
    method Descriptor (line 362) | func (*SubscriberUnsubscribed) Descriptor() ([]byte, []int) {
    method GetMetadata (line 366) | func (x *SubscriberUnsubscribed) GetMetadata() *MessageMetadata {
    method GetSubscriberId (line 373) | func (x *SubscriberUnsubscribed) GetSubscriberId() string {
  type SubscriberEmailUpdated (line 380) | type SubscriberEmailUpdated struct
    method Reset (line 390) | func (x *SubscriberEmailUpdated) Reset() {
    method String (line 399) | func (x *SubscriberEmailUpdated) String() string {
    method ProtoMessage (line 403) | func (*SubscriberEmailUpdated) ProtoMessage() {}
    method ProtoReflect (line 405) | func (x *SubscriberEmailUpdated) ProtoReflect() protoreflect.Message {
    method Descriptor (line 418) | func (*SubscriberEmailUpdated) Descriptor() ([]byte, []int) {
    method GetMetadata (line 422) | func (x *SubscriberEmailUpdated) GetMetadata() *MessageMetadata {
    method GetSubscriberId (line 429) | func (x *SubscriberEmailUpdated) GetSubscriberId() string {
    method GetNewEmail (line 436) | func (x *SubscriberEmailUpdated) GetNewEmail() string {
  function file_messages_proto_rawDescGZIP (line 512) | func file_messages_proto_rawDescGZIP() []byte {
  function init (line 545) | func init() { file_messages_proto_init() }
  function file_messages_proto_init (line 546) | func file_messages_proto_init() {

FILE: _examples/basic/6-cqrs-ordered-events/subscribers.go
  type SubscriberReadModel (line 9) | type SubscriberReadModel struct
    method OnSubscribed (line 20) | func (m *SubscriberReadModel) OnSubscribed(ctx context.Context, event ...
    method OnUnsubscribed (line 35) | func (m *SubscriberReadModel) OnUnsubscribed(ctx context.Context, even...
    method OnEmailUpdated (line 49) | func (m *SubscriberReadModel) OnEmailUpdated(ctx context.Context, even...
    method GetSubscriberCount (line 64) | func (m *SubscriberReadModel) GetSubscriberCount() int {
  function NewSubscriberReadModel (line 14) | func NewSubscriberReadModel() *SubscriberReadModel {

FILE: _examples/pubsubs/amqp/main.go
  function main (line 16) | func main() {
  function publishMessages (line 47) | func publishMessages(publisher message.Publisher) {
  function process (line 59) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/aws-sns/main.go
  function main (line 23) | func main() {
  function publishMessages (line 113) | func publishMessages(publisher message.Publisher) {
  function process (line 125) | func process(prefix string, messages <-chan *message.Message) {

FILE: _examples/pubsubs/aws-sqs/main.go
  function main (line 20) | func main() {
  function publishMessages (line 65) | func publishMessages(publisher message.Publisher) {
  function process (line 77) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/go-channel/main.go
  function main (line 14) | func main() {
  function publishMessages (line 30) | func publishMessages(publisher message.Publisher) {
  function process (line 42) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/googlecloud/main.go
  function main (line 14) | func main() {
  function publishMessages (line 49) | func publishMessages(publisher message.Publisher) {
  function process (line 61) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/kafka/main.go
  function main (line 16) | func main() {
  function publishMessages (line 55) | func publishMessages(publisher message.Publisher) {
  function process (line 67) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/nats-core/main.go
  function main (line 18) | func main() {
  function publishMessages (line 77) | func publishMessages(publisher message.Publisher) {
  function process (line 89) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/nats-jetstream/main.go
  function main (line 18) | func main() {
  function processJS (line 97) | func processJS(messages <-chan *message.Message) {

FILE: _examples/pubsubs/nats-streaming/main.go
  function main (line 16) | func main() {
  function publishMessages (line 62) | func publishMessages(publisher message.Publisher) {
  function process (line 74) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/redisstream/main.go
  function main (line 15) | func main() {
  function publishMessages (line 57) | func publishMessages(publisher message.Publisher) {
  function process (line 69) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/sql/main.go
  function main (line 17) | func main() {
  function createDB (line 55) | func createDB() *stdSQL.DB {
  function publishMessages (line 75) | func publishMessages(publisher message.Publisher) {
  function process (line 87) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/sqlite-zombiezen/main.go
  function main (line 16) | func main() {
  function publishMessages (line 53) | func publishMessages(publisher message.Publisher) {
  function process (line 65) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/sqlite-zombiezen/transaction.go
  function publishWithInTransaction (line 11) | func publishWithInTransaction(connectionDSN string) {

FILE: _examples/pubsubs/sqlite/main.go
  function main (line 16) | func main() {
  function createDB (line 52) | func createDB() *sql.DB {
  function publishMessages (line 70) | func publishMessages(publisher message.Publisher) {
  function process (line 82) | func process(messages <-chan *message.Message) {

FILE: _examples/pubsubs/sqlite/transaction.go
  function publishWithInTransaction (line 12) | func publishWithInTransaction(db *sql.DB) {

FILE: _examples/real-world-examples/consumer-groups/api/http.go
  type Handler (line 17) | type Handler struct
    method Mux (line 27) | func (h Handler) Mux() *chi.Mux {
    method SendMessage (line 65) | func (h Handler) SendMessage(w http.ResponseWriter, r *http.Request) {
  type messagesStream (line 98) | type messagesStream struct
    method GetResponse (line 103) | func (p messagesStream) GetResponse(w http.ResponseWriter, r *http.Req...
    method Validate (line 109) | func (p messagesStream) Validate(r *http.Request, msg *message.Message...
  function fileServer (line 121) | func fileServer(r chi.Router, path string, root http.FileSystem) {
  function logAndWriteError (line 139) | func logAndWriteError(logger watermill.LoggerAdapter, w http.ResponseWri...
  function requestIDMiddleware (line 144) | func requestIDMiddleware(next http.Handler) http.Handler {

FILE: _examples/real-world-examples/consumer-groups/api/main.go
  function main (line 16) | func main() {

FILE: _examples/real-world-examples/consumer-groups/api/storage.go
  type storage (line 9) | type storage struct
    method Append (line 14) | func (s *storage) Append(message common.MessageReceived) {
    method PopAll (line 23) | func (s *storage) PopAll(key string) []common.MessageReceived {

FILE: _examples/real-world-examples/consumer-groups/common/events.go
  type UserSignedUp (line 3) | type UserSignedUp struct
  type Consents (line 8) | type Consents struct
  type MessageReceived (line 13) | type MessageReceived struct

FILE: _examples/real-world-examples/consumer-groups/common/messaging.go
  constant UpdatesTopic (line 11) | UpdatesTopic = "updates"
  function NotifyMiddleware (line 13) | func NotifyMiddleware(pub message.Publisher, serviceName string) func(me...

FILE: _examples/real-world-examples/consumer-groups/crm-service/main.go
  function main (line 25) | func main() {
  function HandleCRM (line 153) | func HandleCRM(ctx context.Context, e *common.UserSignedUp) error {
  function HandleSupport (line 159) | func HandleSupport(ctx context.Context, e *common.UserSignedUp) error {

FILE: _examples/real-world-examples/consumer-groups/newsletter-service/main.go
  function main (line 25) | func main() {
  function HandleNews (line 377) | func HandleNews(ctx context.Context, e *common.UserSignedUp) error {
  function HandlePromotions (line 387) | func HandlePromotions(ctx context.Context, e *common.UserSignedUp) error {

FILE: _examples/real-world-examples/delayed-messages/main.go
  function main (line 24) | func main() {
  type Customer (line 207) | type Customer struct
  type OrderPlaced (line 212) | type OrderPlaced struct
  type SendFeedbackForm (line 217) | type SendFeedbackForm struct

FILE: _examples/real-world-examples/delayed-requeue/main.go
  function main (line 23) | func main() {
  function newFakeOrderPlaced (line 145) | func newFakeOrderPlaced() OrderPlaced {
  type OrderPlaced (line 173) | type OrderPlaced struct
  type Customer (line 180) | type Customer struct
  type Address (line 187) | type Address struct
  type Product (line 194) | type Product struct

FILE: _examples/real-world-examples/exactly-once-delivery-counter/run.go
  constant messagesCount (line 16) | messagesCount = 5000
  constant senderGoroutines (line 34) | senderGoroutines = 5
  function main (line 36) | func main() {
  function getDbCounterValue (line 111) | func getDbCounterValue(db *stdSQL.DB, counterUUID string) (int, error) {
  function restartWorker (line 122) | func restartWorker() {
  function restartMySQL (line 130) | func restartMySQL() {
  function sendCountRequest (line 138) | func sendCountRequest(counterUUID string) {
  function createDB (line 151) | func createDB() *stdSQL.DB {

FILE: _examples/real-world-examples/exactly-once-delivery-counter/schema.sql
  type counter (line 1) | CREATE TABLE counter (

FILE: _examples/real-world-examples/exactly-once-delivery-counter/server/main.go
  constant topic (line 17) | topic = "counter"
  function main (line 19) | func main() {
  type messagePayload (line 64) | type messagePayload struct
  function createDB (line 68) | func createDB() *stdSQL.DB {

FILE: _examples/real-world-examples/exactly-once-delivery-counter/worker/main.go
  constant topic (line 19) | topic = "counter"
  function main (line 21) | func main() {
  type messagePayload (line 34) | type messagePayload struct
  function runWatermillRouter (line 38) | func runWatermillRouter(db *stdSQL.DB, logger watermill.LoggerAdapter) {
  function processMessage (line 69) | func processMessage(msg *message.Message) error {
  function updateDbCounter (line 96) | func updateDbCounter(ctx context.Context, tx sql.Tx, counterUUD string, ...
  function dbCounterValue (line 111) | func dbCounterValue(ctx context.Context, tx sql.Tx, counterUUID string) ...
  function createDB (line 130) | func createDB() *stdSQL.DB {

FILE: _examples/real-world-examples/persistent-event-log/main.go
  type event (line 26) | type event struct
  function main (line 31) | func main() {
  function createDB (line 71) | func createDB() *stdSQL.DB {
  function createSubscriber (line 91) | func createSubscriber() message.Subscriber {
  function createPublisher (line 105) | func createPublisher(db *stdSQL.DB) message.Publisher {
  function simulateEvents (line 121) | func simulateEvents() {

FILE: _examples/real-world-examples/receiving-webhooks/main.go
  type Webhook (line 26) | type Webhook struct
  function main (line 30) | func main() {

FILE: _examples/real-world-examples/sending-webhooks/producer/main.go
  type eventType (line 18) | type eventType
  constant Foo (line 21) | Foo eventType = "Foo"
  constant Bar (line 22) | Bar eventType = "Bar"
  constant Baz (line 23) | Baz eventType = "Baz"
  function main (line 26) | func main() {

FILE: _examples/real-world-examples/sending-webhooks/router/main.go
  function filterMessages (line 18) | func filterMessages(acceptedTypes ...string) message.HandlerFunc {
  function main (line 34) | func main() {

FILE: _examples/real-world-examples/sending-webhooks/webhooks-server/main.go
  function handler (line 11) | func handler(w http.ResponseWriter, r *http.Request) {
  function main (line 27) | func main() {

FILE: _examples/real-world-examples/server-sent-events-htmx/events.go
  type PostViewed (line 18) | type PostViewed struct
  type PostReactionAdded (line 22) | type PostReactionAdded struct
  type PostStatsUpdated (line 27) | type PostStatsUpdated struct
  type Routers (line 35) | type Routers struct
  function NewRouters (line 41) | func NewRouters(cfg config, repo *Repository) (Routers, error) {

FILE: _examples/real-world-examples/server-sent-events-htmx/http.go
  type Handler (line 21) | type Handler struct
    method Index (line 64) | func (h Handler) Index(c echo.Context) error {
    method Posts (line 73) | func (h Handler) Posts(c echo.Context) error {
    method Idle (line 82) | func (h Handler) Idle(c echo.Context) error {
    method allPosts (line 86) | func (h Handler) allPosts(c echo.Context) ([]views.Post, error) {
    method AddReaction (line 111) | func (h Handler) AddReaction(c echo.Context) error {
  function NewHandler (line 26) | func NewHandler(repo *Repository, eventBus *cqrs.EventBus, sseRouter wat...
  type statsStream (line 146) | type statsStream struct
    method InitialStreamResponse (line 150) | func (s *statsStream) InitialStreamResponse(w http.ResponseWriter, r *...
    method NextStreamResponse (line 182) | func (s *statsStream) NextStreamResponse(r *http.Request, msg *message...
  function newPostStatsView (line 218) | func newPostStatsView(ctx context.Context, stats PostStats) (interface{}...
  function newPostView (line 248) | func newPostView(p Post) views.Post {
  type sseHandlersCounter (line 257) | type sseHandlersCounter struct
    method Middleware (line 261) | func (s *sseHandlersCounter) Middleware(next echo.HandlerFunc) echo.Ha...

FILE: _examples/real-world-examples/server-sent-events-htmx/main.go
  type config (line 14) | type config struct
  function main (line 20) | func main() {

FILE: _examples/real-world-examples/server-sent-events-htmx/models.go
  function mustReactionByID (line 28) | func mustReactionByID(id string) Reaction {
  type Reaction (line 38) | type Reaction struct
  type Post (line 43) | type Post struct
  type PostStats (line 52) | type PostStats struct

FILE: _examples/real-world-examples/server-sent-events-htmx/repository.go
  constant migration (line 10) | migration = `
  function MigrateDB (line 26) | func MigrateDB(db *sql.DB) error {
  type Repository (line 31) | type Repository struct
    method PostByID (line 41) | func (s *Repository) PostByID(ctx context.Context, id int) (Post, erro...
    method AllPosts (line 51) | func (s *Repository) AllPosts(ctx context.Context) ([]Post, error) {
    method UpdatePost (line 69) | func (s *Repository) UpdatePost(ctx context.Context, id int, updateFn ...
  function NewRepository (line 35) | func NewRepository(db *sql.DB) *Repository {
  type scanner (line 107) | type scanner interface
  function scanPost (line 111) | func scanPost(s scanner) (Post, error) {

FILE: _examples/real-world-examples/server-sent-events-htmx/views/base_templ.go
  function base (line 13) | func base() templ.Component {

FILE: _examples/real-world-examples/server-sent-events-htmx/views/pages_templ.go
  type Post (line 13) | type Post struct
  type Reaction (line 20) | type Reaction struct
  type PostStats (line 27) | type PostStats struct
  type PostViews (line 33) | type PostViews struct
  function Index (line 38) | func Index(posts []Post) templ.Component {
  function Posts (line 77) | func Posts(posts []Post) templ.Component {
  function Idle (line 111) | func Idle() templ.Component {
  function postView (line 135) | func postView(post Post) templ.Component {
  function PostStatsView (line 211) | func PostStatsView(stats PostStats) templ.Component {
  function reactionButton (line 276) | func reactionButton(postID string, reaction Reaction) templ.Component {
  function UpdatedButton (line 374) | func UpdatedButton(label string) templ.Component {

FILE: _examples/real-world-examples/server-sent-events/schema.sql
  type example (line 1) | CREATE TABLE example.posts (

FILE: _examples/real-world-examples/server-sent-events/server/event_handlers.go
  constant PostCreatedTopic (line 16) | PostCreatedTopic = "post-created"
  constant PostUpdatedTopic (line 17) | PostUpdatedTopic = "post-updated"
  constant FeedUpdatedTopic (line 18) | FeedUpdatedTopic = "feed-updated"
  function SetupMessageRouter (line 21) | func SetupMessageRouter(
  function createFeedUpdatedEvents (line 145) | func createFeedUpdatedEvents(tags []string) ([]*message.Message, error) {
  type Publisher (line 167) | type Publisher struct
    method Publish (line 171) | func (p Publisher) Publish(topic string, event interface{}) error {

FILE: _examples/real-world-examples/server-sent-events/server/feeds_storage.go
  constant collectionName (line 13) | collectionName = "feeds"
  type FeedsStorage (line 15) | type FeedsStorage struct
    method Add (line 59) | func (s FeedsStorage) Add(ctx context.Context, name string) error {
    method All (line 75) | func (s FeedsStorage) All(ctx context.Context) ([]Feed, error) {
    method ByName (line 91) | func (s FeedsStorage) ByName(ctx context.Context, name string) (Feed, ...
    method AppendPost (line 105) | func (s FeedsStorage) AppendPost(ctx context.Context, post Post) error {
    method UpdatePost (line 109) | func (s FeedsStorage) UpdatePost(ctx context.Context, post Post) error {
    method updatePostIfPresent (line 128) | func (s FeedsStorage) updatePostIfPresent(ctx context.Context, post Po...
    method appendPostIfNotPresent (line 150) | func (s FeedsStorage) appendPostIfNotPresent(ctx context.Context, post...
    method removePostIfNotInFeed (line 177) | func (s FeedsStorage) removePostIfNotInFeed(ctx context.Context, post ...
  function NewFeedsStorage (line 19) | func NewFeedsStorage() FeedsStorage {
  function isDuplicateError (line 206) | func isDuplicateError(err error) bool {

FILE: _examples/real-world-examples/server-sent-events/server/http.go
  type Router (line 23) | type Router struct
    method Mux (line 31) | func (router Router) Mux() *chi.Mux {
    method CreatePost (line 136) | func (router Router) CreatePost(w http.ResponseWriter, r *http.Request) {
    method GeneratePost (line 160) | func (router Router) GeneratePost(w http.ResponseWriter, r *http.Reque...
    method addPost (line 186) | func (router Router) addPost(ctx context.Context, post Post) error {
    method UpdatePost (line 211) | func (router Router) UpdatePost(w http.ResponseWriter, r *http.Request) {
  type feedSummary (line 77) | type feedSummary struct
  type AllFeedsResponse (line 82) | type AllFeedsResponse struct
  type allFeedsStreamAdapter (line 86) | type allFeedsStreamAdapter struct
    method InitialStreamResponse (line 91) | func (f allFeedsStreamAdapter) InitialStreamResponse(w http.ResponseWr...
    method NextStreamResponse (line 101) | func (f allFeedsStreamAdapter) NextStreamResponse(r *http.Request, msg...
    method getResponse (line 110) | func (f allFeedsStreamAdapter) getResponse(r *http.Request) (interface...
  type CreatePostRequest (line 130) | type CreatePostRequest struct
  type UpdatePostRequest (line 205) | type UpdatePostRequest struct
  type feedStreamAdapter (line 253) | type feedStreamAdapter struct
    method InitialStreamResponse (line 258) | func (f feedStreamAdapter) InitialStreamResponse(w http.ResponseWriter...
    method NextStreamResponse (line 268) | func (f feedStreamAdapter) NextStreamResponse(r *http.Request, msg *me...
    method getResponse (line 290) | func (f feedStreamAdapter) getResponse(r *http.Request) (response inte...
  type postStreamAdapter (line 301) | type postStreamAdapter struct
    method InitialStreamResponse (line 306) | func (p postStreamAdapter) InitialStreamResponse(w http.ResponseWriter...
    method NextStreamResponse (line 316) | func (p postStreamAdapter) NextStreamResponse(r *http.Request, msg *me...
    method getResponse (line 338) | func (p postStreamAdapter) getResponse(r *http.Request) (response inte...
  function FileServer (line 349) | func FileServer(r chi.Router, path string, root http.FileSystem) {
  function logAndWriteError (line 367) | func logAndWriteError(logger watermill.LoggerAdapter, w http.ResponseWri...

FILE: _examples/real-world-examples/server-sent-events/server/main.go
  function main (line 9) | func main() {

FILE: _examples/real-world-examples/server-sent-events/server/models.go
  type Post (line 13) | type Post struct
  function NewPost (line 21) | func NewPost(id, title, content, author string) Post {
  type Feed (line 49) | type Feed struct
  type PostCreated (line 54) | type PostCreated struct
  type PostUpdated (line 60) | type PostUpdated struct
  type FeedUpdated (line 67) | type FeedUpdated struct

FILE: _examples/real-world-examples/server-sent-events/server/posts_storage.go
  type PostsStorage (line 12) | type PostsStorage struct
    method ByID (line 43) | func (s PostsStorage) ByID(ctx context.Context, id string) (Post, erro...
    method Add (line 56) | func (s PostsStorage) Add(ctx context.Context, post Post) error {
    method Update (line 62) | func (s PostsStorage) Update(ctx context.Context, post Post) error {
  function NewPostsStorage (line 16) | func NewPostsStorage() PostsStorage {

FILE: _examples/real-world-examples/synchronizing-databases/main.go
  function main (line 29) | func main() {
  function createMySQLConnection (line 88) | func createMySQLConnection() *stdSQL.DB {
  function createPostgresConnection (line 109) | func createPostgresConnection() *stdSQL.DB {
  function createSubscriber (line 124) | func createSubscriber(db *stdSQL.DB) message.Subscriber {
  function createPublisher (line 141) | func createPublisher(db *stdSQL.DB) message.Publisher {
  function simulateEvents (line 157) | func simulateEvents(db *stdSQL.DB) {

FILE: _examples/real-world-examples/synchronizing-databases/mysql.go
  type mysqlUser (line 15) | type mysqlUser struct
  type mysqlSchemaAdapter (line 23) | type mysqlSchemaAdapter struct
    method SchemaInitializingQueries (line 27) | func (m mysqlSchemaAdapter) SchemaInitializingQueries(params sql.Schem...
    method InsertQuery (line 41) | func (m mysqlSchemaAdapter) InsertQuery(params sql.InsertQueryParams) ...
    method SelectQuery (line 64) | func (m mysqlSchemaAdapter) SelectQuery(params sql.SelectQueryParams) ...
    method UnmarshalMessage (line 84) | func (m mysqlSchemaAdapter) UnmarshalMessage(params sql.UnmarshalMessa...

FILE: _examples/real-world-examples/synchronizing-databases/postgres.go
  type postgresUser (line 14) | type postgresUser struct
  type postgresSchemaAdapter (line 21) | type postgresSchemaAdapter struct
    method SchemaInitializingQueries (line 25) | func (p postgresSchemaAdapter) SchemaInitializingQueries(params sql.Sc...
    method InsertQuery (line 38) | func (p postgresSchemaAdapter) InsertQuery(params sql.InsertQueryParam...
    method SelectQuery (line 61) | func (p postgresSchemaAdapter) SelectQuery(params sql.SelectQueryParam...
    method UnmarshalMessage (line 66) | func (p postgresSchemaAdapter) UnmarshalMessage(params sql.UnmarshalMe...

FILE: _examples/real-world-examples/transactional-events-forwarder/main.go
  constant projectID (line 21) | projectID             = "transactional-events"
  constant forwarderSQLTopic (line 22) | forwarderSQLTopic     = "eventsToForward"
  constant googleCloudEventTopic (line 23) | googleCloudEventTopic = "lottery-concluded"
  constant simulatedErrorProbability (line 25) | simulatedErrorProbability = 0.5
  type LotteryConcludedEvent (line 33) | type LotteryConcludedEvent struct
  function main (line 37) | func main() {
  function runLotteryService (line 75) | func runLotteryService(logger watermill.LoggerAdapter) {
  function publishEventAndPersistData (line 105) | func publishEventAndPersistData(lotteryID int, pickedUser string, logger...
  function persistDataAndPublishEvent (line 141) | func persistDataAndPublishEvent(lotteryID int, pickedUser string, logger...
  function persistDataAndPublishEventInTransaction (line 179) | func persistDataAndPublishEventInTransaction(lotteryID int, pickedUser s...
  function runPrizeSenderService (line 236) | func runPrizeSenderService(logger watermill.LoggerAdapter) {
  function expectNoErr (line 272) | func expectNoErr(err error) {
  function simulateError (line 278) | func simulateError() error {
  function createDB (line 286) | func createDB() *stdSQL.DB {

FILE: _examples/real-world-examples/transactional-events/main.go
  function main (line 26) | func main() {
  function createDB (line 69) | func createDB() *stdSQL.DB {
  function createSubscriber (line 89) | func createSubscriber(db *stdSQL.DB) message.Subscriber {
  function createPublisher (line 106) | func createPublisher() message.Publisher {
  type event (line 121) | type event struct
  function simulateEvents (line 126) | func simulateEvents(db *stdSQL.DB) {
  function publishEvent (line 158) | func publishEvent(tx *stdSQL.Tx) error {

FILE: components/cqrs/command_bus.go
  type CommandBusConfig (line 13) | type CommandBusConfig struct
    method setDefaults (line 32) | func (c *CommandBusConfig) setDefaults() {
    method Validate (line 38) | func (c CommandBusConfig) Validate() error {
  type CommandBusGeneratePublishTopicFn (line 52) | type CommandBusGeneratePublishTopicFn
  type CommandBusGeneratePublishTopicParams (line 54) | type CommandBusGeneratePublishTopicParams struct
  type CommandBusOnSendFn (line 59) | type CommandBusOnSendFn
  type CommandBusOnSendParams (line 61) | type CommandBusOnSendParams struct
  type CommandBus (line 70) | type CommandBus struct
    method Send (line 116) | func (c CommandBus) Send(ctx context.Context, cmd any) error {
    method SendWithModifiedMessage (line 120) | func (c CommandBus) SendWithModifiedMessage(ctx context.Context, cmd a...
    method newMessage (line 139) | func (c CommandBus) newMessage(ctx context.Context, command any) (*mes...
  function NewCommandBusWithConfig (line 77) | func NewCommandBusWithConfig(publisher message.Publisher, config Command...
  function NewCommandBus (line 92) | func NewCommandBus(

FILE: components/cqrs/command_bus_test.go
  function TestCommandBusConfig_Validate (line 15) | func TestCommandBusConfig_Validate(t *testing.T) {
  function TestNewCommandBus (line 66) | func TestNewCommandBus(t *testing.T) {
  type contextKey (line 90) | type contextKey
  function TestCommandBus_Send_ContextPropagation (line 92) | func TestCommandBus_Send_ContextPropagation(t *testing.T) {
  function TestCommandBus_Send_topic_name (line 114) | func TestCommandBus_Send_topic_name(t *testing.T) {
  function TestCommandBus_Send_OnSend (line 130) | func TestCommandBus_Send_OnSend(t *testing.T) {
  function TestCommandBus_SendWithModifiedMessage (line 154) | func TestCommandBus_SendWithModifiedMessage(t *testing.T) {
  function TestCommandBus_SendWithModifiedMessage_modify_error (line 177) | func TestCommandBus_SendWithModifiedMessage_modify_error(t *testing.T) {
  function TestCommandBus_Send_OnSend_error (line 203) | func TestCommandBus_Send_OnSend_error(t *testing.T) {

FILE: components/cqrs/command_handler.go
  type CommandHandler (line 15) | type CommandHandler interface
  type genericCommandHandler (line 30) | type genericCommandHandler struct
  function NewCommandHandler (line 37) | func NewCommandHandler[Command any](
  method HandlerName (line 47) | func (c genericCommandHandler[Command]) HandlerName() string {
  method NewCommand (line 51) | func (c genericCommandHandler[Command]) NewCommand() any {
  method Handle (line 56) | func (c genericCommandHandler[Command]) Handle(ctx context.Context, cmd ...

FILE: components/cqrs/command_handler_test.go
  type SomeCommand (line 12) | type SomeCommand struct
  function TestNewCommandHandler (line 16) | func TestNewCommandHandler(t *testing.T) {

FILE: components/cqrs/command_processor.go
  type CommandProcessorConfig (line 13) | type CommandProcessorConfig struct
    method setDefaults (line 63) | func (c *CommandProcessorConfig) setDefaults() {
    method Validate (line 69) | func (c CommandProcessorConfig) Validate() error {
  type CommandProcessorGenerateSubscribeTopicFn (line 86) | type CommandProcessorGenerateSubscribeTopicFn
  type CommandProcessorGenerateSubscribeTopicParams (line 88) | type CommandProcessorGenerateSubscribeTopicParams struct
  type CommandProcessorSubscriberConstructorFn (line 95) | type CommandProcessorSubscriberConstructorFn
  type CommandProcessorSubscriberConstructorParams (line 97) | type CommandProcessorSubscriberConstructorParams struct
  type CommandProcessorOnHandleFn (line 103) | type CommandProcessorOnHandleFn
  type CommandProcessorOnHandleParams (line 105) | type CommandProcessorOnHandleParams struct
  type CommandProcessor (line 116) | type CommandProcessor struct
    method AddHandlers (line 194) | func (p *CommandProcessor) AddHandlers(handlers ...CommandHandler) err...
    method AddHandler (line 222) | func (p *CommandProcessor) AddHandler(handler CommandHandler) (*messag...
    method AddHandlersToRouter (line 253) | func (p CommandProcessor) AddHandlersToRouter(r *message.Router) error {
    method addHandlerToRouter (line 269) | func (p CommandProcessor) addHandlerToRouter(r *message.Router, handle...
    method Handlers (line 311) | func (p CommandProcessor) Handlers() []CommandHandler {
    method routerHandlerFunc (line 315) | func (p CommandProcessor) routerHandlerFunc(handler CommandHandler, lo...
    method validateCommand (line 375) | func (p CommandProcessor) validateCommand(cmd interface{}) error {
  function NewCommandProcessorWithConfig (line 124) | func NewCommandProcessorWithConfig(router *message.Router, config Comman...
  function NewCommandProcessor (line 143) | func NewCommandProcessor(
  type CommandsSubscriberConstructor (line 191) | type CommandsSubscriberConstructor
  type DuplicateCommandHandlerError (line 240) | type DuplicateCommandHandlerError struct
    method Error (line 244) | func (d DuplicateCommandHandlerError) Error() string {

FILE: components/cqrs/command_processor_test.go
  function TestCommandProcessorConfig_Validate (line 19) | func TestCommandProcessorConfig_Validate(t *testing.T) {
  function TestNewCommandProcessor (line 80) | func TestNewCommandProcessor(t *testing.T) {
  type nonPointerCommandHandler (line 107) | type nonPointerCommandHandler struct
    method HandlerName (line 110) | func (nonPointerCommandHandler) HandlerName() string {
    method NewCommand (line 114) | func (nonPointerCommandHandler) NewCommand() interface{} {
    method Handle (line 118) | func (nonPointerCommandHandler) Handle(ctx context.Context, cmd interf...
  function TestCommandProcessor_non_pointer_command (line 122) | func TestCommandProcessor_non_pointer_command(t *testing.T) {
  function TestCommandProcessor_multiple_same_command_handlers (line 150) | func TestCommandProcessor_multiple_same_command_handlers(t *testing.T) {
  type mockSubscriber (line 180) | type mockSubscriber struct
    method Subscribe (line 187) | func (m *mockSubscriber) Subscribe(ctx context.Context, topic string) ...
    method Close (line 203) | func (m mockSubscriber) Close() error {
  function TestCommandProcessor_AckCommandHandlingErrors_option_true (line 208) | func TestCommandProcessor_AckCommandHandlingErrors_option_true(t *testin...
  function TestCommandProcessor_AckCommandHandlingErrors_option_false (line 285) | func TestCommandProcessor_AckCommandHandlingErrors_option_false(t *testi...
  function TestNewCommandProcessor_OnHandle (line 345) | func TestNewCommandProcessor_OnHandle(t *testing.T) {
  function TestCommandProcessor_AddHandlersToRouter_without_disableRouterAutoAddHandlers (line 435) | func TestCommandProcessor_AddHandlersToRouter_without_disableRouterAutoA...
  function TestCommandProcessor_original_msg_set_to_ctx (line 460) | func TestCommandProcessor_original_msg_set_to_ctx(t *testing.T) {

FILE: components/cqrs/cqrs.go
  type FacadeConfig (line 12) | type FacadeConfig struct
    method Validate (line 56) | func (c FacadeConfig) Validate() error {
    method EventsEnabled (line 95) | func (c FacadeConfig) EventsEnabled() bool {
    method CommandsEnabled (line 99) | func (c FacadeConfig) CommandsEnabled() bool {
  type Facade (line 108) | type Facade struct
    method CommandBus (line 118) | func (f Facade) CommandBus() *CommandBus {
    method EventBus (line 122) | func (f Facade) EventBus() *EventBus {
    method CommandEventMarshaler (line 126) | func (f Facade) CommandEventMarshaler() CommandEventMarshaler {
  function NewFacade (line 131) | func NewFacade(config FacadeConfig) (*Facade, error) {

FILE: components/cqrs/cqrs_test.go
  function TestCQRS (line 19) | func TestCQRS(t *testing.T) {
  function createCqrsComponents (line 73) | func createCqrsComponents(t *testing.T, commandHandler *CaptureCommandHa...
  function createRouterAndFacade (line 185) | func createRouterAndFacade(t *testing.T, commandHandler *CaptureCommandH...
  type TestServices (line 243) | type TestServices struct
  function NewTestServices (line 250) | func NewTestServices() TestServices {
  type TestCommand (line 267) | type TestCommand struct
  type CaptureCommandHandler (line 271) | type CaptureCommandHandler struct
    method HandlerName (line 275) | func (h CaptureCommandHandler) HandlerName() string {
    method HandledCommands (line 279) | func (h CaptureCommandHandler) HandledCommands() []interface{} {
    method Reset (line 283) | func (h *CaptureCommandHandler) Reset() {
    method NewCommand (line 287) | func (CaptureCommandHandler) NewCommand() interface{} {
    method Handle (line 291) | func (h *CaptureCommandHandler) Handle(ctx context.Context, cmd interf...
  type TestEvent (line 296) | type TestEvent struct
  type AnotherTestEvent (line 301) | type AnotherTestEvent struct
  type CaptureEventHandler (line 305) | type CaptureEventHandler struct
    method HandlerName (line 309) | func (h CaptureEventHandler) HandlerName() string {
    method HandledEvents (line 313) | func (h CaptureEventHandler) HandledEvents() []interface{} {
    method Reset (line 317) | func (h *CaptureEventHandler) Reset() {
    method NewEvent (line 321) | func (CaptureEventHandler) NewEvent() interface{} {
    method Handle (line 325) | func (h *CaptureEventHandler) Handle(ctx context.Context, event interf...
  type assertPublishTopicPublisher (line 330) | type assertPublishTopicPublisher struct
    method Publish (line 335) | func (a assertPublishTopicPublisher) Publish(topic string, messages .....
    method Close (line 340) | func (assertPublishTopicPublisher) Close() error {
  type publisherStub (line 344) | type publisherStub struct
    method Close (line 356) | func (*publisherStub) Close() error {
    method Publish (line 360) | func (p *publisherStub) Publish(topic string, messages ...*message.Mes...
  function newPublisherStub (line 350) | func newPublisherStub() *publisherStub {
  function TestFacadeConfig_Validate (line 369) | func TestFacadeConfig_Validate(t *testing.T) {
  function transformConfig (line 487) | func transformConfig(config cqrs.FacadeConfig, transformFn func(config *...

FILE: components/cqrs/ctx.go
  type ctxKey (line 9) | type ctxKey
  constant originalMessage (line 12) | originalMessage ctxKey = "original_message"
  function OriginalMessageFromCtx (line 16) | func OriginalMessageFromCtx(ctx context.Context) *message.Message {
  function CtxWithOriginalMessage (line 25) | func CtxWithOriginalMessage(ctx context.Context, msg *message.Message) c...

FILE: components/cqrs/event_bus.go
  type EventBusConfig (line 12) | type EventBusConfig struct
    method setDefaults (line 31) | func (c *EventBusConfig) setDefaults() {
    method Validate (line 37) | func (c EventBusConfig) Validate() error {
  type GenerateEventPublishTopicFn (line 51) | type GenerateEventPublishTopicFn
  type GenerateEventPublishTopicParams (line 53) | type GenerateEventPublishTopicParams struct
  type OnEventSendFn (line 58) | type OnEventSendFn
  type OnEventSendParams (line 60) | type OnEventSendParams struct
  type EventBus (line 69) | type EventBus struct
    method Publish (line 117) | func (c EventBus) Publish(ctx context.Context, event any) error {
  function NewEventBus (line 76) | func NewEventBus(
  function NewEventBusWithConfig (line 103) | func NewEventBusWithConfig(publisher message.Publisher, config EventBusC...

FILE: components/cqrs/event_bus_test.go
  function TestEventBusConfig_Validate (line 14) | func TestEventBusConfig_Validate(t *testing.T) {
  function TestNewEventBus (line 65) | func TestNewEventBus(t *testing.T) {
  function TestEventBus_Send_ContextPropagation (line 89) | func TestEventBus_Send_ContextPropagation(t *testing.T) {
  function TestEventBus_Send_topic_name (line 109) | func TestEventBus_Send_topic_name(t *testing.T) {
  function TestEventBus_Send_OnPublish (line 123) | func TestEventBus_Send_OnPublish(t *testing.T) {
  function TestEventBus_Send_OnPublish_error (line 147) | func TestEventBus_Send_OnPublish_error(t *testing.T) {

FILE: components/cqrs/event_handler.go
  type EventHandler (line 16) | type EventHandler interface
  type genericEventHandler (line 31) | type genericEventHandler struct
  function NewEventHandler (line 38) | func NewEventHandler[T any](
  method HandlerName (line 48) | func (c genericEventHandler[T]) HandlerName() string {
  method NewEvent (line 52) | func (c genericEventHandler[T]) NewEvent() any {
  method Handle (line 57) | func (c genericEventHandler[T]) Handle(ctx context.Context, e any) error {
  type GroupEventHandler (line 62) | type GroupEventHandler interface
  function NewGroupEventHandler (line 69) | func NewGroupEventHandler[T any](handleFunc func(ctx context.Context, ev...

FILE: components/cqrs/event_handler_test.go
  type SomeEvent (line 12) | type SomeEvent struct
  function TestNewEventHandler (line 16) | func TestNewEventHandler(t *testing.T) {
  function TestNewGroupEventHandler (line 34) | func TestNewGroupEventHandler(t *testing.T) {

FILE: components/cqrs/event_processor.go
  type EventProcessorConfig (line 13) | type EventProcessorConfig struct
    method setDefaults (line 61) | func (c *EventProcessorConfig) setDefaults() {
    method Validate (line 67) | func (c EventProcessorConfig) Validate() error {
  type EventProcessorGenerateSubscribeTopicFn (line 84) | type EventProcessorGenerateSubscribeTopicFn
  type EventProcessorGenerateSubscribeTopicParams (line 86) | type EventProcessorGenerateSubscribeTopicParams struct
  type EventProcessorSubscriberConstructorFn (line 91) | type EventProcessorSubscriberConstructorFn
  type EventProcessorSubscriberConstructorParams (line 93) | type EventProcessorSubscriberConstructorParams struct
  type EventProcessorOnHandleFn (line 99) | type EventProcessorOnHandleFn
  type EventProcessorOnHandleParams (line 101) | type EventProcessorOnHandleParams struct
  type EventProcessor (line 112) | type EventProcessor struct
    method AddHandlers (line 196) | func (p *EventProcessor) AddHandlers(handlers ...EventHandler) error {
    method AddHandler (line 214) | func (p *EventProcessor) AddHandler(handler EventHandler) (*message.Ha...
    method AddHandlersToRouter (line 236) | func (p EventProcessor) AddHandlersToRouter(r *message.Router) error {
    method addHandlerToRouter (line 252) | func (p EventProcessor) addHandlerToRouter(r *message.Router, handler ...
    method Handlers (line 294) | func (p EventProcessor) Handlers() []EventHandler {
    method routerHandlerFunc (line 314) | func (p EventProcessor) routerHandlerFunc(handler EventHandler, logger...
  function NewEventProcessorWithConfig (line 119) | func NewEventProcessorWithConfig(router *message.Router, config EventPro...
  function NewEventProcessor (line 137) | func NewEventProcessor(
  type EventsSubscriberConstructor (line 193) | type EventsSubscriberConstructor
  function addHandlerToRouter (line 298) | func addHandlerToRouter(logger watermill.LoggerAdapter, r *message.Route...
  function validateEvent (line 373) | func validateEvent(event interface{}) error {

FILE: components/cqrs/event_processor_group.go
  type EventGroupProcessorConfig (line 12) | type EventGroupProcessorConfig struct
    method setDefaults (line 54) | func (c *EventGroupProcessorConfig) setDefaults() {
    method Validate (line 60) | func (c EventGroupProcessorConfig) Validate() error {
  type EventGroupProcessorGenerateSubscribeTopicFn (line 77) | type EventGroupProcessorGenerateSubscribeTopicFn
  type EventGroupProcessorGenerateSubscribeTopicParams (line 79) | type EventGroupProcessorGenerateSubscribeTopicParams struct
  type EventGroupProcessorSubscriberConstructorFn (line 84) | type EventGroupProcessorSubscriberConstructorFn
  type EventGroupProcessorSubscriberConstructorParams (line 86) | type EventGroupProcessorSubscriberConstructorParams struct
  type EventGroupProcessorOnHandleFn (line 91) | type EventGroupProcessorOnHandleFn
  type EventGroupProcessorOnHandleParams (line 93) | type EventGroupProcessorOnHandleParams struct
  type EventGroupProcessor (line 106) | type EventGroupProcessor struct
    method AddHandlersGroup (line 143) | func (p *EventGroupProcessor) AddHandlersGroup(groupName string, handl...
    method addHandlerToRouter (line 160) | func (p EventGroupProcessor) addHandlerToRouter(r *message.Router, gro...
    method routerHandlerGroupFunc (line 204) | func (p EventGroupProcessor) routerHandlerGroupFunc(handlers []GroupEv...
  function NewEventGroupProcessorWithConfig (line 115) | func NewEventGroupProcessorWithConfig(router *message.Router, config Eve...

FILE: components/cqrs/event_processor_group_test.go
  function TestEventGroupProcessorConfig_Validate (line 17) | func TestEventGroupProcessorConfig_Validate(t *testing.T) {
  function TestNewEventProcessor_OnGroupHandle (line 82) | func TestNewEventProcessor_OnGroupHandle(t *testing.T) {
  function TestNewEventProcessor_AckOnUnknownEvent_handler_group (line 174) | func TestNewEventProcessor_AckOnUnknownEvent_handler_group(t *testing.T) {
  function TestNewEventProcessor_AckOnUnknownEvent_disabled_handler_group (line 229) | func TestNewEventProcessor_AckOnUnknownEvent_disabled_handler_group(t *t...
  function TestEventProcessor_handler_group (line 283) | func TestEventProcessor_handler_group(t *testing.T) {
  function TestEventGroupProcessor_original_msg_set_to_ctx (line 395) | func TestEventGroupProcessor_original_msg_set_to_ctx(t *testing.T) {

FILE: components/cqrs/event_processor_test.go
  function TestEventProcessorConfig_Validate (line 19) | func TestEventProcessorConfig_Validate(t *testing.T) {
  function TestNewEventProcessor (line 80) | func TestNewEventProcessor(t *testing.T) {
  type nonPointerEventProcessor (line 107) | type nonPointerEventProcessor struct
    method HandlerName (line 110) | func (nonPointerEventProcessor) HandlerName() string {
    method NewEvent (line 114) | func (nonPointerEventProcessor) NewEvent() interface{} {
    method Handle (line 118) | func (nonPointerEventProcessor) Handle(ctx context.Context, cmd interf...
  function TestEventProcessor_non_pointer_event (line 122) | func TestEventProcessor_non_pointer_event(t *testing.T) {
  type duplicateTestEventHandler1 (line 149) | type duplicateTestEventHandler1 struct
    method HandlerName (line 151) | func (h duplicateTestEventHandler1) HandlerName() string {
    method NewEvent (line 155) | func (duplicateTestEventHandler1) NewEvent() interface{} {
    method Handle (line 159) | func (h *duplicateTestEventHandler1) Handle(ctx context.Context, event...
  type duplicateTestEventHandler2 (line 161) | type duplicateTestEventHandler2 struct
    method HandlerName (line 163) | func (h duplicateTestEventHandler2) HandlerName() string {
    method NewEvent (line 167) | func (duplicateTestEventHandler2) NewEvent() interface{} {
    method Handle (line 171) | func (h *duplicateTestEventHandler2) Handle(ctx context.Context, event...
  function TestEventProcessor_multiple_same_event_handlers (line 173) | func TestEventProcessor_multiple_same_event_handlers(t *testing.T) {
  function TestNewEventProcessor_OnHandle (line 201) | func TestNewEventProcessor_OnHandle(t *testing.T) {
  type UnknownEvent (line 291) | type UnknownEvent struct
  function TestNewEventProcessor_AckOnUnknownEvent (line 294) | func TestNewEventProcessor_AckOnUnknownEvent(t *testing.T) {
  function TestNewEventProcessor_AckOnUnknownEvent_disabled (line 348) | func TestNewEventProcessor_AckOnUnknownEvent_disabled(t *testing.T) {
  function TestNewEventProcessor_backward_compatibility_of_AckOnUnknownEvent (line 402) | func TestNewEventProcessor_backward_compatibility_of_AckOnUnknownEvent(t...
  function TestEventProcessor_AddHandlersToRouter_without_disableRouterAutoAddHandlers (line 453) | func TestEventProcessor_AddHandlersToRouter_without_disableRouterAutoAdd...
  function TestEventProcessor_original_msg_set_to_ctx (line 479) | func TestEventProcessor_original_msg_set_to_ctx(t *testing.T) {

FILE: components/cqrs/marshaler.go
  type CommandEventMarshaler (line 12) | type CommandEventMarshaler interface
  type CommandEventMarshalerDecorator (line 32) | type CommandEventMarshalerDecorator struct
    method Marshal (line 40) | func (c CommandEventMarshalerDecorator) Marshal(v any) (*message.Messa...

FILE: components/cqrs/marshaler_json.go
  type JSONMarshaler (line 10) | type JSONMarshaler struct
    method Marshal (line 15) | func (m JSONMarshaler) Marshal(v interface{}) (*message.Message, error) {
    method newUUID (line 30) | func (m JSONMarshaler) newUUID() string {
    method Unmarshal (line 39) | func (JSONMarshaler) Unmarshal(msg *message.Message, v interface{}) (e...
    method Name (line 43) | func (m JSONMarshaler) Name(cmdOrEvent interface{}) string {
    method NameFromMessage (line 51) | func (m JSONMarshaler) NameFromMessage(msg *message.Message) string {

FILE: components/cqrs/marshaler_json_test.go
  function TestJsonMarshaler (line 19) | func TestJsonMarshaler(t *testing.T) {
  function TestJSONMarshaler_Marshal_new_uuid_set (line 32) | func TestJSONMarshaler_Marshal_new_uuid_set(t *testing.T) {
  function TestJSONMarshaler_Marshal_generate_name (line 45) | func TestJSONMarshaler_Marshal_generate_name(t *testing.T) {

FILE: components/cqrs/marshaler_protobuf.go
  type ProtoMarshaler (line 14) | type ProtoMarshaler struct
    method Marshal (line 34) | func (m ProtoMarshaler) Marshal(v interface{}) (*message.Message, erro...
    method newUUID (line 54) | func (m ProtoMarshaler) newUUID() string {
    method Unmarshal (line 64) | func (ProtoMarshaler) Unmarshal(msg *message.Message, v interface{}) (...
    method Name (line 74) | func (m ProtoMarshaler) Name(cmdOrEvent interface{}) string {
    method NameFromMessage (line 83) | func (m ProtoMarshaler) NameFromMessage(msg *message.Message) string {
  type NoProtoMessageError (line 20) | type NoProtoMessageError struct
    method Error (line 24) | func (e NoProtoMessageError) Error() string {

FILE: components/cqrs/marshaler_protobuf_events_new_test.go
  constant _ (line 19) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
  constant _ (line 21) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
  type Status (line 24) | type Status
    method Enum (line 46) | func (x Status) Enum() *Status {
    method String (line 52) | func (x Status) String() string {
    method Descriptor (line 56) | func (Status) Descriptor() protoreflect.EnumDescriptor {
    method Type (line 60) | func (Status) Type() protoreflect.EnumType {
    method Number (line 64) | func (x Status) Number() protoreflect.EnumNumber {
    method EnumDescriptor (line 69) | func (Status) EnumDescriptor() ([]byte, []int) {
  constant Status_STATUS_UNSPECIFIED (line 27) | Status_STATUS_UNSPECIFIED Status = 0
  constant Status_ACTIVE (line 28) | Status_ACTIVE             Status = 1
  constant Status_DELETED (line 29) | Status_DELETED            Status = 2
  type TestProtobufLegacyEvent (line 73) | type TestProtobufLegacyEvent struct
    method Reset (line 82) | func (x *TestProtobufLegacyEvent) Reset() {
    method String (line 91) | func (x *TestProtobufLegacyEvent) String() string {
    method ProtoMessage (line 95) | func (*TestProtobufLegacyEvent) ProtoMessage() {}
    method ProtoReflect (line 97) | func (x *TestProtobufLegacyEvent) ProtoReflect() protoreflect.Message {
    method Descriptor (line 110) | func (*TestProtobufLegacyEvent) Descriptor() ([]byte, []int) {
    method GetId (line 114) | func (x *TestProtobufLegacyEvent) GetId() string {
    method GetWhen (line 121) | func (x *TestProtobufLegacyEvent) GetWhen() *timestamppb.Timestamp {
  type SubEvent (line 128) | type SubEvent struct
    method Reset (line 137) | func (x *SubEvent) Reset() {
    method String (line 146) | func (x *SubEvent) String() string {
    method ProtoMessage (line 150) | func (*SubEvent) ProtoMessage() {}
    method ProtoReflect (line 152) | func (x *SubEvent) ProtoReflect() protoreflect.Message {
    method Descriptor (line 165) | func (*SubEvent) Descriptor() ([]byte, []int) {
    method GetTags (line 169) | func (x *SubEvent) GetTags() []string {
    method GetFlags (line 176) | func (x *SubEvent) GetFlags() map[string]bool {
  type TestComplexProtobufEvent (line 183) | type TestComplexProtobufEvent struct
    method Reset (line 202) | func (x *TestComplexProtobufEvent) Reset() {
    method String (line 211) | func (x *TestComplexProtobufEvent) String() string {
    method ProtoMessage (line 215) | func (*TestComplexProtobufEvent) ProtoMessage() {}
    method ProtoReflect (line 217) | func (x *TestComplexProtobufEvent) ProtoReflect() protoreflect.Message {
    method Descriptor (line 230) | func (*TestComplexProtobufEvent) Descriptor() ([]byte, []int) {
    method GetId (line 234) | func (x *TestComplexProtobufEvent) GetId() string {
    method GetData (line 241) | func (x *TestComplexProtobufEvent) GetData() []byte {
    method GetWhen (line 248) | func (x *TestComplexProtobufEvent) GetWhen() *timestamppb.Timestamp {
    method GetNestedMap (line 255) | func (x *TestComplexProtobufEvent) GetNestedMap() map[string]*SubEvent {
    method GetEvents (line 262) | func (x *TestComplexProtobufEvent) GetEvents() []*SubEvent {
    method GetResult (line 269) | func (m *TestComplexProtobufEvent) GetResult() isTestComplexProtobufEv...
    method GetSuccess (line 276) | func (x *TestComplexProtobufEvent) GetSuccess() *SubEvent {
    method GetError (line 283) | func (x *TestComplexProtobufEvent) GetError() string {
    method GetFallback (line 290) | func (x *TestComplexProtobufEvent) GetFallback() Status {
  type isTestComplexProtobufEvent_Result (line 297) | type isTestComplexProtobufEvent_Result interface
  type TestComplexProtobufEvent_Success (line 301) | type TestComplexProtobufEvent_Success struct
    method isTestComplexProtobufEvent_Result (line 313) | func (*TestComplexProtobufEvent_Success) isTestComplexProtobufEvent_Re...
  type TestComplexProtobufEvent_Error (line 305) | type TestComplexProtobufEvent_Error struct
    method isTestComplexProtobufEvent_Result (line 315) | func (*TestComplexProtobufEvent_Error) isTestComplexProtobufEvent_Resu...
  type TestComplexProtobufEvent_Fallback (line 309) | type TestComplexProtobufEvent_Fallback struct
    method isTestComplexProtobufEvent_Result (line 317) | func (*TestComplexProtobufEvent_Fallback) isTestComplexProtobufEvent_R...
  function file_events_proto_rawDescGZIP (line 382) | func file_events_proto_rawDescGZIP() []byte {
  function init (line 416) | func init() { file_events_proto_init() }
  function file_events_proto_init (line 417) | func file_events_proto_init() {

FILE: components/cqrs/marshaler_protobuf_events_test.go
  constant _ (line 23) | _ = proto.ProtoPackageIsVersion3
  type TestProtobufEvent (line 25) | type TestProtobufEvent struct
    method Reset (line 33) | func (m *TestProtobufEvent) Reset()         { *m = TestProtobufEvent{} }
    method String (line 34) | func (m *TestProtobufEvent) String() string { return proto.CompactText...
    method ProtoMessage (line 35) | func (*TestProtobufEvent) ProtoMessage()    {}
    method Descriptor (line 36) | func (*TestProtobufEvent) Descriptor() ([]byte, []int) {
    method XXX_Unmarshal (line 40) | func (m *TestProtobufEvent) XXX_Unmarshal(b []byte) error {
    method XXX_Marshal (line 43) | func (m *TestProtobufEvent) XXX_Marshal(b []byte, deterministic bool) ...
    method XXX_Merge (line 46) | func (m *TestProtobufEvent) XXX_Merge(src proto.Message) {
    method XXX_Size (line 49) | func (m *TestProtobufEvent) XXX_Size() int {
    method XXX_DiscardUnknown (line 52) | func (m *TestProtobufEvent) XXX_DiscardUnknown() {
    method GetId (line 58) | func (m *TestProtobufEvent) GetId() string {
    method GetWhen (line 65) | func (m *TestProtobufEvent) GetWhen() *timestamp.Timestamp {
  function init (line 72) | func init() {
  function init (line 76) | func init() { proto.RegisterFile("testdata/events.proto", fileDescriptor...

FILE: components/cqrs/marshaler_protobuf_gogo.go
  type ProtobufMarshaler (line 24) | type ProtobufMarshaler struct
    method Marshal (line 35) | func (m ProtobufMarshaler) Marshal(v interface{}) (msg *message.Messag...
    method newUUID (line 74) | func (m ProtobufMarshaler) newUUID() string {
    method Unmarshal (line 84) | func (m ProtobufMarshaler) Unmarshal(msg *message.Message, v interface...
    method ToProtoMarshaler (line 108) | func (m ProtobufMarshaler) ToProtoMarshaler() ProtoMarshaler {
    method Name (line 116) | func (m ProtobufMarshaler) Name(cmdOrEvent interface{}) string {
    method NameFromMessage (line 125) | func (m ProtobufMarshaler) NameFromMessage(msg *message.Message) string {

FILE: components/cqrs/marshaler_protobuf_gogo_test.go
  function TestProtobufMarshaler_with_fallback (line 11) | func TestProtobufMarshaler_with_fallback(t *testing.T) {
  function TestProtobufMarshaler_without_fallback_legacy_event (line 34) | func TestProtobufMarshaler_without_fallback_legacy_event(t *testing.T) {
  function TestProtobufMarshaler_Marshal_generated_name (line 51) | func TestProtobufMarshaler_Marshal_generated_name(t *testing.T) {
  function TestProtobufMarshaler_catch_panic (line 64) | func TestProtobufMarshaler_catch_panic(t *testing.T) {
  function TestProtobufMarshaler_compatible_with_ProtoMarshaler (line 96) | func TestProtobufMarshaler_compatible_with_ProtoMarshaler(t *testing.T) {

FILE: components/cqrs/marshaler_protobuf_test.go
  function TestProtoMarshaler (line 17) | func TestProtoMarshaler(t *testing.T) {
  function TestProtoMarshaler_Marshal_generated_name (line 28) | func TestProtoMarshaler_Marshal_generated_name(t *testing.T) {
  function newProtoLegacyTestEvent (line 42) | func newProtoLegacyTestEvent() (*TestProtobufEvent, *TestProtobufLegacyE...
  function newProtoTestComplexEvent (line 59) | func newProtoTestComplexEvent() *TestComplexProtobufEvent {
  function assertProtoMarshalUnmarshal (line 90) | func assertProtoMarshalUnmarshal[T1, T2 fmt.Stringer](

FILE: components/cqrs/name.go
  function FullyQualifiedStructName (line 17) | func FullyQualifiedStructName(v interface{}) string {
  function StructName (line 33) | func StructName(v interface{}) string {
  type namedStruct (line 39) | type namedStruct interface
  function NamedStruct (line 50) | func NamedStruct(fallback func(v interface{}) string) func(v interface{}...

FILE: components/cqrs/name_test.go
  function TestFullyQualifiedStructName (line 11) | func TestFullyQualifiedStructName(t *testing.T) {
  function BenchmarkFullyQualifiedStructName (line 18) | func BenchmarkFullyQualifiedStructName(b *testing.B) {
  function TestStructName (line 27) | func TestStructName(t *testing.T) {
  function TestNamedStruct (line 34) | func TestNamedStruct(t *testing.T) {
  type namedObject (line 45) | type namedObject struct
    method Name (line 47) | func (namedObject) Name() string {

FILE: components/cqrs/object.go
  function isPointer (line 7) | func isPointer(v interface{}) error {
  type NonPointerError (line 17) | type NonPointerError struct
    method Error (line 21) | func (e NonPointerError) Error() string {

FILE: components/delay/delay.go
  type Delay (line 16) | type Delay struct
    method IsZero (line 21) | func (d Delay) IsZero() bool {
  function Until (line 26) | func Until(delayedUntil time.Time) Delay {
  function For (line 34) | func For(delayedFor time.Duration) Delay {
  type contextKey (line 41) | type contextKey
  function WithContext (line 52) | func WithContext(ctx context.Context, delay Delay) context.Context {
  constant DelayedUntilKey (line 57) | DelayedUntilKey = "_watermill_delayed_until"
  constant DelayedForKey (line 58) | DelayedForKey   = "_watermill_delayed_for"
  function Message (line 65) | func Message(msg *message.Message, delay Delay) {

FILE: components/delay/publisher.go
  type DefaultDelayGeneratorParams (line 9) | type DefaultDelayGeneratorParams struct
  type PublisherConfig (line 15) | type PublisherConfig struct
  function NewPublisher (line 28) | func NewPublisher(pub message.Publisher, config PublisherConfig) (messag...
  type publisher (line 35) | type publisher struct
    method Publish (line 40) | func (p *publisher) Publish(topic string, messages ...*message.Message...
    method Close (line 50) | func (p *publisher) Close() error {
    method applyDelay (line 54) | func (p *publisher) applyDelay(topic string, msg *message.Message) err...

FILE: components/delay/publisher_test.go
  function TestPublisher (line 17) | func TestPublisher(t *testing.T) {
  function assertMessage (line 154) | func assertMessage(t *testing.T, messages <-chan *message.Message, expec...

FILE: components/fanin/fanin.go
  type Config (line 15) | type Config struct
    method setDefaults (line 34) | func (c *Config) setDefaults() {
    method Validate (line 40) | func (c *Config) Validate() error {
  type FanIn (line 28) | type FanIn struct
    method Run (line 113) | func (f *FanIn) Run(ctx context.Context) error {
    method Running (line 118) | func (f *FanIn) Running() chan struct{} {
    method Close (line 123) | func (f *FanIn) Close() error {
  function NewFanIn (line 61) | func NewFanIn(

FILE: components/fanin/fanin_test.go
  function TestFanIn (line 18) | func TestFanIn(t *testing.T) {
  function TestNewFanIn (line 123) | func TestNewFanIn(t *testing.T) {

FILE: components/forwarder/envelope.go
  type messageEnvelope (line 12) | type messageEnvelope struct
    method validate (line 35) | func (e *messageEnvelope) validate() error {
  function newMessageEnvelope (line 20) | func newMessageEnvelope(destTopic string, msg *message.Message) (*messag...
  function wrapMessageInEnvelope (line 43) | func wrapMessageInEnvelope(destinationTopic string, msg *message.Message...
  function unwrapMessageFromEnvelope (line 60) | func unwrapMessageFromEnvelope(msg *message.Message) (destinationTopic s...

FILE: components/forwarder/envelope_test.go
  type contextKey (line 13) | type contextKey
  function TestEnvelope (line 15) | func TestEnvelope(t *testing.T) {

FILE: components/forwarder/forwarder.go
  constant defaultForwarderTopic (line 12) | defaultForwarderTopic = "forwarder_topic"
  type Config (line 14) | type Config struct
    method setDefaults (line 35) | func (c *Config) setDefaults() {
    method Validate (line 44) | func (c *Config) Validate() error {
  type Forwarder (line 53) | type Forwarder struct
    method Run (line 105) | func (f *Forwarder) Run(ctx context.Context) error {
    method Close (line 110) | func (f *Forwarder) Close() error {
    method Running (line 115) | func (f *Forwarder) Running() chan struct{} {
    method forwardMessage (line 119) | func (f *Forwarder) forwardMessage(msg *message.Message) error {
  function NewForwarder (line 67) | func NewForwarder(subscriberIn message.Subscriber, publisherOut message....

FILE: components/forwarder/forwarder_test.go
  function TestForwarder (line 22) | func TestForwarder(t *testing.T) {
  type ForwarderSuite (line 27) | type ForwarderSuite struct
    method SetupTest (line 43) | func (s *ForwarderSuite) SetupTest() {
    method TearDownTest (line 56) | func (s *ForwarderSuite) TearDownTest() {
    method TestForwarder_publish_using_decorated_publisher (line 64) | func (s *ForwarderSuite) TestForwarder_publish_using_decorated_publish...
    method TestForwarder_publish_using_non_decorated_publisher (line 77) | func (s *ForwarderSuite) TestForwarder_publish_using_non_decorated_pub...
    method TestForwarder_publish_using_non_decorated_publisher_acking_enabled (line 94) | func (s *ForwarderSuite) TestForwarder_publish_using_non_decorated_pub...
    method setupForwarder (line 136) | func (s *ForwarderSuite) setupForwarder(config forwarder.Config) *forw...
    method listenOnOutTopic (line 153) | func (s *ForwarderSuite) listenOnOutTopic() {
    method requireFirstMessage (line 159) | func (s *ForwarderSuite) requireFirstMessage(expectedMessage *message....
    method setupMessageAckedDetectorMiddleware (line 170) | func (s *ForwarderSuite) setupMessageAckedDetectorMiddleware() (messag...
    method requireFirstAckingResult (line 185) | func (s *ForwarderSuite) requireFirstAckingResult(msgAckedCh <-chan bo...
    method sampleMessage (line 194) | func (s *ForwarderSuite) sampleMessage() *message.Message {
  type PubSubInPublisher (line 112) | type PubSubInPublisher struct
  type PubSubInSubscriber (line 115) | type PubSubInSubscriber struct
  type PubSubOutPublisher (line 119) | type PubSubOutPublisher struct
  type PubSubOutSubscriber (line 122) | type PubSubOutSubscriber struct
  function newPubSubIn (line 126) | func newPubSubIn() (PubSubInPublisher, PubSubInSubscriber) {
  function newPubSubOut (line 131) | func newPubSubOut() (PubSubOutPublisher, PubSubOutSubscriber) {

FILE: components/forwarder/publisher.go
  type PublisherConfig (line 8) | type PublisherConfig struct
    method setDefaults (line 14) | func (c *PublisherConfig) setDefaults() {
    method Validate (line 20) | func (c *PublisherConfig) Validate() error {
  type Publisher (line 30) | type Publisher struct
    method Publish (line 44) | func (p *Publisher) Publish(topic string, messages ...*message.Message...
    method Close (line 62) | func (p *Publisher) Close() error {
  function NewPublisher (line 35) | func NewPublisher(publisher message.Publisher, config PublisherConfig) *...

FILE: components/metrics/builder.go
  type PrometheusMetricsBuilderConfig (line 10) | type PrometheusMetricsBuilderConfig struct
  function NewPrometheusMetricsBuilderWithConfig (line 16) | func NewPrometheusMetricsBuilderWithConfig(prometheusRegistry prometheus...
  function NewPrometheusMetricsBuilder (line 26) | func NewPrometheusMetricsBuilder(prometheusRegistry prometheus.Registere...
  type PrometheusMetricsBuilder (line 34) | type PrometheusMetricsBuilder struct
    method AddPrometheusRouterMetrics (line 52) | func (b PrometheusMetricsBuilder) AddPrometheusRouterMetrics(r *messag...
    method DecoratePublisher (line 59) | func (b PrometheusMetricsBuilder) DecoratePublisher(pub message.Publis...
    method DecorateSubscriber (line 84) | func (b PrometheusMetricsBuilder) DecorateSubscriber(sub message.Subsc...
    method register (line 113) | func (b PrometheusMetricsBuilder) register(c prometheus.Collector) (pr...
    method registerCounterVec (line 126) | func (b PrometheusMetricsBuilder) registerCounterVec(c *prometheus.Cou...
    method registerHistogramVec (line 134) | func (b PrometheusMetricsBuilder) registerHistogramVec(h *prometheus.H...

FILE: components/metrics/ctx.go
  type contextValue (line 5) | type contextValue
  constant publishObserved (line 8) | publishObserved contextValue = iota
  constant subscribeObserved (line 9) | subscribeObserved
  function setPublishObservedToCtx (line 13) | func setPublishObservedToCtx(ctx context.Context) context.Context {
  function publishAlreadyObserved (line 17) | func publishAlreadyObserved(ctx context.Context) bool {
  function setSubscribeObservedToCtx (line 22) | func setSubscribeObservedToCtx(ctx context.Context) context.Context {
  function subscribeAlreadyObserved (line 26) | func subscribeAlreadyObserved(ctx context.Context) bool {

FILE: components/metrics/handler.go
  type HandlerPrometheusMetricsMiddleware (line 36) | type HandlerPrometheusMetricsMiddleware struct
    method Middleware (line 42) | func (m HandlerPrometheusMetricsMiddleware) Middleware(h message.Handl...
  method NewRouterMiddleware (line 67) | func (b PrometheusMetricsBuilder) NewRouterMiddleware() HandlerPrometheu...

FILE: components/metrics/http.go
  function CreateRegistryAndServeHTTP (line 14) | func CreateRegistryAndServeHTTP(addr string) (registry *prometheus.Regis...
  function ServeHTTP (line 21) | func ServeHTTP(addr string, registry *prometheus.Registry) (cancel func(...

FILE: components/metrics/http_test.go
  function TestCreateRegistryAndServeHTTP_metrics_endpoint (line 15) | func TestCreateRegistryAndServeHTTP_metrics_endpoint(t *testing.T) {
  function TestCreateRegistryAndServeHTTP_unknown_endpoint (line 35) | func TestCreateRegistryAndServeHTTP_unknown_endpoint(t *testing.T) {
  function waitServerReady (line 56) | func waitServerReady(t *testing.T, addr string) {

FILE: components/metrics/labels.go
  constant labelKeyHandlerName (line 12) | labelKeyHandlerName    = "handler_name"
  constant labelKeyPublisherName (line 13) | labelKeyPublisherName  = "publisher_name"
  constant labelKeySubscriberName (line 14) | labelKeySubscriberName = "subscriber_name"
  constant labelSuccess (line 15) | labelSuccess           = "success"
  constant labelAcked (line 16) | labelAcked             = "acked"
  constant labelValueNoHandler (line 18) | labelValueNoHandler = "<no handler>"
  function labelsFromCtx (line 29) | func labelsFromCtx(ctx context.Context, labels ...string) prometheus.Lab...
  type LabelComputeValueFn (line 50) | type LabelComputeValueFn
  type MetricLabel (line 52) | type MetricLabel struct
  function toLabelsSlice (line 57) | func toLabelsSlice(baseLabels []string, customs []MetricLabel) []string {

FILE: components/metrics/publisher.go
  type PublisherPrometheusMetricsDecorator (line 19) | type PublisherPrometheusMetricsDecorator struct
    method Publish (line 27) | func (m PublisherPrometheusMetricsDecorator) Publish(topic string, mes...
    method Close (line 68) | func (m PublisherPrometheusMetricsDecorator) Close() error {

FILE: components/metrics/subscriber.go
  type SubscriberPrometheusMetricsDecorator (line 16) | type SubscriberPrometheusMetricsDecorator struct
    method recordMetrics (line 24) | func (s SubscriberPrometheusMetricsDecorator) recordMetrics(msg *messa...

FILE: components/requestreply/backend_pubsub.go
  type PubSubBackend (line 15) | type PubSubBackend struct
  function NewPubSubBackend (line 23) | func NewPubSubBackend[Result any](
  type PubSubBackendSubscribeParams (line 42) | type PubSubBackendSubscribeParams struct
  type PubSubBackendSubscriberConstructorFn (line 48) | type PubSubBackendSubscriberConstructorFn
  type PubSubBackendGenerateSubscribeTopicFn (line 50) | type PubSubBackendGenerateSubscribeTopicFn
  type PubSubBackendPublishParams (line 52) | type PubSubBackendPublishParams struct
  type PubSubBackendGeneratePublishTopicFn (line 60) | type PubSubBackendGeneratePublishTopicFn
  type PubSubBackendOnCommandProcessedParams (line 62) | type PubSubBackendOnCommandProcessedParams struct
  type PubSubBackendModifyNotificationMessageFn (line 68) | type PubSubBackendModifyNotificationMessageFn
  type PubSubBackendOnListenForReplyFinishedFn (line 70) | type PubSubBackendOnListenForReplyFinishedFn
  type ReplyPublishErrorHandler (line 72) | type ReplyPublishErrorHandler
  type PubSubBackendConfig (line 74) | type PubSubBackendConfig struct
    method setDefaults (line 101) | func (p *PubSubBackendConfig) setDefaults() {
    method Validate (line 107) | func (p *PubSubBackendConfig) Validate() error {
  method ListenForNotifications (line 126) | func (p PubSubBackend[Result]) ListenForNotifications(
  constant OperationIDMetadataKey (line 215) | OperationIDMetadataKey = "_watermill_requestreply_op_id"
  method OnCommandProcessed (line 217) | func (p PubSubBackend[Result]) OnCommandProcessed(ctx context.Context, p...
  function operationIDFromMetadata (line 275) | func operationIDFromMetadata(msg *message.Message) (OperationID, error) {
  method handleNotifyMsg (line 284) | func (p PubSubBackend[Result]) handleNotifyMsg(

FILE: components/requestreply/backend_pubsub_marshaler.go
  type BackendPubsubMarshaler (line 11) | type BackendPubsubMarshaler interface
  constant ErrorMetadataKey (line 17) | ErrorMetadataKey    = "_watermill_requestreply_error"
  constant HasErrorMetadataKey (line 18) | HasErrorMetadataKey = "_watermill_requestreply_has_error"
  type BackendPubsubJSONMarshaler (line 21) | type BackendPubsubJSONMarshaler struct
  method MarshalReply (line 23) | func (m BackendPubsubJSONMarshaler[Result]) MarshalReply(
  method UnmarshalReply (line 44) | func (m BackendPubsubJSONMarshaler[Result]) UnmarshalReply(msg *message....

FILE: components/requestreply/command_bus.go
  type CommandBus (line 11) | type CommandBus interface
  function SendWithReply (line 43) | func SendWithReply[Result any](
  function SendWithReplies (line 96) | func SendWithReplies[Result any](

FILE: components/requestreply/handler.go
  function NewCommandHandler (line 16) | func NewCommandHandler[Command any](
  function NewCommandHandlerWithResult (line 47) | func NewCommandHandlerWithResult[Command any, Result any](
  function originalCommandMsgFromCtx (line 69) | func originalCommandMsgFromCtx(ctx context.Context) (*message.Message, e...

FILE: components/requestreply/requestreply.go
  type Reply (line 14) | type Reply struct
  type Backend (line 36) | type Backend interface
  type BackendListenForNotificationsParams (line 41) | type BackendListenForNotificationsParams struct
  type BackendOnCommandProcessedParams (line 46) | type BackendOnCommandProcessedParams struct
  type OperationID (line 56) | type OperationID
  type ReplyTimeoutError (line 59) | type ReplyTimeoutError struct
    method Error (line 64) | func (e ReplyTimeoutError) Error() string {
  type ReplyUnmarshalError (line 68) | type ReplyUnmarshalError struct
    method Error (line 72) | func (r ReplyUnmarshalError) Error() string {
    method Unwrap (line 76) | func (r ReplyUnmarshalError) Unwrap() error {
  type CommandHandlerError (line 81) | type CommandHandlerError struct
    method Error (line 85) | func (e CommandHandlerError) Error() string {
    method Unwrap (line 89) | func (e CommandHandlerError) Unwrap() error {

FILE: components/requestreply/requestreply_test.go
  type TestServices (line 21) | type TestServices struct
  type TestServicesConfig (line 34) | type TestServicesConfig struct
  function NewTestServices (line 43) | func NewTestServices[Result any](t *testing.T, c TestServicesConfig) Tes...
  method RunRouter (line 139) | func (ts TestServices[Result]) RunRouter() {
  type TestCommand (line 150) | type TestCommand struct
  type TestCommand2 (line 154) | type TestCommand2 struct
  type TestCommandResult (line 158) | type TestCommandResult struct
  function TestRequestReply_without_result_no_error (line 162) | func TestRequestReply_without_result_no_error(t *testing.T) {
  function TestRequestReply_without_result_with_error (line 203) | func TestRequestReply_without_result_with_error(t *testing.T) {
  function TestRequestReply_with_result_no_error (line 249) | func TestRequestReply_with_result_no_error(t *testing.T) {
  function TestRequestReply_with_result_with_error (line 291) | func TestRequestReply_with_result_with_error(t *testing.T) {
  function TestSendWithReply (line 333) | func TestSendWithReply(t *testing.T) {
  function TestRequestReply_without_result_multiple_replies (line 368) | func TestRequestReply_without_result_multiple_replies(t *testing.T) {
  function TestRequestReply_timeout (line 446) | func TestRequestReply_timeout(t *testing.T) {
  function TestRequestReply_context_cancellation (line 491) | func TestRequestReply_context_cancellation(t *testing.T) {
  function TestRequestReply_fn_cancellation (line 540) | func TestRequestReply_fn_cancellation(t *testing.T) {
  function TestRequestReply_parallel_different_handlers (line 587) | func TestRequestReply_parallel_different_handlers(t *testing.T) {
  function TestRequestReply_parallel_same_handler (line 690) | func TestRequestReply_parallel_same_handler(t *testing.T) {
  function TestNewPubSubBackend_missing_values (line 746) | func TestNewPubSubBackend_missing_values(t *testing.T) {

FILE: components/requeuer/requeuer.go
  constant RetriesKey (line 14) | RetriesKey = "_watermill_requeuer_retries"
  type Requeuer (line 18) | type Requeuer struct
    method handler (line 123) | func (r *Requeuer) handler(msg *message.Message) error {
    method Run (line 156) | func (r *Requeuer) Run(ctx context.Context) error {
  type GeneratePublishTopicParams (line 23) | type GeneratePublishTopicParams struct
  type Config (line 28) | type Config struct
    method setDefaults (line 56) | func (c *Config) setDefaults(logger watermill.LoggerAdapter) error {
    method validate (line 69) | func (c *Config) validate() error {
  function NewRequeuer (line 91) | func NewRequeuer(

FILE: components/requeuer/requeuer_test.go
  function TestRequeue (line 21) | func TestRequeue(t *testing.T) {

FILE: dev/consolidate-gomods/main.go
  function main (line 13) | func main() {
  function getGomods (line 63) | func getGomods() []string {

FILE: dev/update-examples-deps/main.go
  function main (line 17) | func main() {
  function getGomods (line 66) | func getGomods() []string {
  function getLatestGoVersionFromWebsite (line 83) | func getLatestGoVersionFromWebsite() string {
  function goModTidy (line 104) | func goModTidy(dir string, file string) error {
  function replaceGoInDockerCompose (line 119) | func replaceGoInDockerCompose(dir string) error {
  function updateWatermill (line 148) | func updateWatermill(dir string, file string) error {
  function updateDeps (line 162) | func updateDeps(dir string, file string) error {

FILE: dev/validate-examples/main.go
  type Config (line 18) | type Config struct
    method LoadFrom (line 26) | func (c *Config) LoadFrom(path string) error {
  function main (line 38) | func main() {
  function validate (line 74) | func validate(path string) error {
  function readLines (line 163) | func readLines(reader io.Reader, output chan<- string) {

FILE: docs/content/docs/message/receiving-ack.go
  function main (line 10) | func main() {

FILE: docs/content/docs/snippets/amqp-consumer-groups/main.go
  function createSubscriber (line 14) | func createSubscriber(queueSuffix string) *amqp.Subscriber {
  function main (line 35) | func main() {
  function publishMessages (line 65) | func publishMessages(publisher message.Publisher) {
  function process (line 75) | func process(subscriber string, messages <-chan *message.Message) {

FILE: docs/content/docs/snippets/tail-log-file/main.go
  function main (line 15) | func main() {
  function criterion (line 50) | func criterion(line string) bool {

FILE: docs/extract_middleware_godocs.py
  class MiddlewareSourceFile (line 6) | class MiddlewareSourceFile:
    method __init__ (line 7) | def __init__(self, filepath: str):
    method add_func_with_godoc (line 21) | def add_func_with_godoc(self, line_no: int):
    method format (line 42) | def format(self) -> str:
  function capitalize (line 56) | def capitalize(s: str) -> str:

FILE: internal/channel.go
  function IsChannelClosed (line 5) | func IsChannelClosed(channel chan struct{}) bool {

FILE: internal/channel_test.go
  function TestIsChannelClosed (line 10) | func TestIsChannelClosed(t *testing.T) {

FILE: internal/name.go
  function StructName (line 9) | func StructName(v interface{}) string {

FILE: internal/name_test.go
  type testStruct (line 10) | type testStruct struct
  type stringerStruct (line 12) | type stringerStruct struct
    method String (line 14) | func (stringerStruct) String() string {
  function TestStructName (line 18) | func TestStructName(t *testing.T) {

FILE: internal/norace.go
  constant RaceEnabled (line 6) | RaceEnabled = false

FILE: internal/publisher/errors.go
  type ErrCouldNotPublish (line 9) | type ErrCouldNotPublish struct
    method addMsg (line 13) | func (e *ErrCouldNotPublish) addMsg(msg *message.Message, reason error) {
    method Len (line 21) | func (e ErrCouldNotPublish) Len() int {
    method Error (line 25) | func (e ErrCouldNotPublish) Error() string {
    method Reasons (line 37) | func (e ErrCouldNotPublish) Reasons() map[string]error {
  function NewErrCouldNotPublish (line 17) | func NewErrCouldNotPublish() *ErrCouldNotPublish {

FILE: internal/publisher/retry.go
  type RetryPublisherConfig (line 17) | type RetryPublisherConfig struct
    method setDefaults (line 24) | func (c *RetryPublisherConfig) setDefaults() {
    method validate (line 38) | func (c RetryPublisherConfig) validate() error {
  type RetryPublisher (line 50) | type RetryPublisher struct
    method Publish (line 68) | func (p RetryPublisher) Publish(topic string, messages ...*message.Mes...
    method Close (line 86) | func (p RetryPublisher) Close() error {
    method send (line 91) | func (p RetryPublisher) send(topic string, msg *message.Message) error {
  function NewRetryPublisher (line 55) | func NewRetryPublisher(pub message.Publisher, config RetryPublisherConfi...

FILE: internal/publisher/retry_test.go
  type FailingPublisher (line 20) | type FailingPublisher struct
    method Publish (line 25) | func (p *FailingPublisher) Publish(topic string, messages ...*message....
    method publish (line 39) | func (p *FailingPublisher) publish(msg *message.Message) {
    method Close (line 47) | func (p *FailingPublisher) Close() error {
  function TestRetryPublisher_Publish_after_retries (line 51) | func TestRetryPublisher_Publish_after_retries(t *testing.T) {
  function TestRetryPublisher_Publish_too_many_retries (line 79) | func TestRetryPublisher_Publish_too_many_retries(t *testing.T) {
  function TestPublishEachMessageOnlyOnce (line 113) | func TestPublishEachMessageOnlyOnce(t *testing.T) {
  type ClosingPublisher (line 150) | type ClosingPublisher struct
    method Publish (line 155) | func (ClosingPublisher) Publish(topic string, messages ...*message.Mes...
    method Close (line 159) | func (p *ClosingPublisher) Close() error {
  function TestRetryPublisher_Close (line 167) | func TestRetryPublisher_Close(t *testing.T) {
  function TestRetryPublisher_Close_failed (line 183) | func TestRetryPublisher_Close_failed(t *testing.T) {

FILE: internal/race.go
  constant RaceEnabled (line 6) | RaceEnabled = true

FILE: internal/subscriber/multiplier.go
  type Constructor (line 13) | type Constructor
  type multiplier (line 15) | type multiplier struct
    method Subscribe (line 30) | func (s *multiplier) Subscribe(ctx context.Context, topic string) (msg...
    method Close (line 73) | func (s *multiplier) Close() error {
  function NewMultiplier (line 23) | func NewMultiplier(constructor Constructor, subscribersCount int) messag...

FILE: log.go
  type LogFields (line 19) | type LogFields
    method Add (line 22) | func (l LogFields) Add(newFields LogFields) LogFields {
    method Copy (line 32) | func (l LogFields) Copy() LogFields {
  type LoggerAdapter (line 41) | type LoggerAdapter interface
  type NopLogger (line 50) | type NopLogger struct
    method Error (line 52) | func (NopLogger) Error(msg string, err error, fields LogFields) {}
    method Info (line 53) | func (NopLogger) Info(msg string, fields LogFields)             {}
    method Debug (line 54) | func (NopLogger) Debug(msg string, fields LogFields)            {}
    method Trace (line 55) | func (NopLogger) Trace(msg string, fields LogFields)            {}
    method With (line 56) | func (l NopLogger) With(fields LogFields) LoggerAdapter         { retu...
  type StdLoggerAdapter (line 59) | type StdLoggerAdapter struct
    method Error (line 88) | func (l *StdLoggerAdapter) Error(msg string, err error, fields LogFiel...
    method Info (line 92) | func (l *StdLoggerAdapter) Info(msg string, fields LogFields) {
    method Debug (line 96) | func (l *StdLoggerAdapter) Debug(msg string, fields LogFields) {
    method Trace (line 100) | func (l *StdLoggerAdapter) Trace(msg string, fields LogFields) {
    method With (line 104) | func (l *StdLoggerAdapter) With(fields LogFields) LoggerAdapter {
    method log (line 114) | func (l *StdLoggerAdapter) log(logger *log.Logger, level string, msg s...
  function NewStdLogger (line 69) | func NewStdLogger(debug, trace bool) LoggerAdapter {
  function NewStdLoggerWithOut (line 74) | func NewStdLoggerWithOut(out io.Writer, debug bool, trace bool) LoggerAd...
  type LogLevel (line 152) | type LogLevel
  constant TraceLogLevel (line 155) | TraceLogLevel LogLevel = iota + 1
  constant DebugLogLevel (line 156) | DebugLogLevel
  constant InfoLogLevel (line 157) | InfoLogLevel
  constant ErrorLogLevel (line 158) | ErrorLogLevel
  type CapturedMessage (line 161) | type CapturedMessage struct
    method ContentEquals (line 169) | func (c CapturedMessage) ContentEquals(other CapturedMessage) bool {
  type CaptureLoggerAdapter (line 178) | type CaptureLoggerAdapter struct
    method With (line 191) | func (c *CaptureLoggerAdapter) With(fields LogFields) LoggerAdapter {
    method capture (line 202) | func (c *CaptureLoggerAdapter) capture(level LogLevel, msg string, err...
    method Captured (line 217) | func (c *CaptureLoggerAdapter) Captured() map[LogLevel][]CapturedMessa...
    method PrintCaptured (line 228) | func (c *CaptureLoggerAdapter) PrintCaptured(t Logfer) {
    method Has (line 239) | func (c *CaptureLoggerAdapter) Has(msg CapturedMessage) bool {
    method HasError (line 246) | func (c *CaptureLoggerAdapter) HasError(err error) bool {
    method Error (line 258) | func (c *CaptureLoggerAdapter) Error(msg string, err error, fields Log...
    method Info (line 262) | func (c *CaptureLoggerAdapter) Info(msg string, fields LogFields) {
    method Debug (line 266) | func (c *CaptureLoggerAdapter) Debug(msg string, fields LogFields) {
    method Trace (line 270) | func (c *CaptureLoggerAdapter) Trace(msg string, fields LogFields) {
  function NewCaptureLogger (line 184) | func NewCaptureLogger() *CaptureLoggerAdapter {
  type Logfer (line 224) | type Logfer interface

FILE: log_test.go
  function TestLogFields_Copy (line 13) | func TestLogFields_Copy(t *testing.T) {
  function TestStdLogger_with (line 23) | func TestStdLogger_with(t *testing.T) {
  type stringer (line 46) | type stringer struct
    method String (line 48) | func (s stringer) String() string {
  function TestStdLoggerAdapter_stringer_field (line 52) | func TestStdLoggerAdapter_stringer_field(t *testing.T) {
  function TestStdLoggerAdapter_field_with_space (line 62) | func TestStdLoggerAdapter_field_with_space(t *testing.T) {
  function TestCaptureLoggerAdapter (line 72) | func TestCaptureLoggerAdapter(t *testing.T) {

FILE: message/decorator.go
  function MessageTransformSubscriberDecorator (line 10) | func MessageTransformSubscriberDecorator(transform func(*Message)) Subsc...
  function MessageTransformPublisherDecorator (line 24) | func MessageTransformPublisherDecorator(transform func(*Message)) Publis...
  type messageTransformSubscriberDecorator (line 36) | type messageTransformSubscriberDecorator struct
    method Subscribe (line 43) | func (t *messageTransformSubscriberDecorator) Subscribe(ctx context.Co...
    method Close (line 63) | func (t *messageTransformSubscriberDecorator) Close() error {
  type messageTransformPublisherDecorator (line 70) | type messageTransformPublisherDecorator struct
    method Publish (line 76) | func (d messageTransformPublisherDecorator) Publish(topic string, mess...

FILE: message/decorator_bench_test.go
  type benchSubscriber (line 13) | type benchSubscriber struct
    method Subscribe (line 19) | func (b *benchSubscriber) Subscribe(ctx context.Context, topic string)...
    method Close (line 23) | func (b *benchSubscriber) Close() error {
  function newBenchSubscriber (line 31) | func newBenchSubscriber() *benchSubscriber {
  function BenchmarkMessageTransformSubscriberDecorator (line 55) | func BenchmarkMessageTransformSubscriberDecorator(b *testing.B) {
  function benchmarkNoDecorator (line 61) | func benchmarkNoDecorator(b *testing.B) {
  function benchmarkMessageTransformSubscriberDecorator (line 74) | func benchmarkMessageTransformSubscriberDecorator(b *testing.B) {

FILE: message/decorator_test.go
  type mockSubscriber (line 24) | type mockSubscriber struct
    method Subscribe (line 28) | func (m mockSubscriber) Subscribe(context.Context, string) (<-chan *me...
    method Close (line 32) | func (m mockSubscriber) Close() error {
  function TestMessageTransformSubscriberDecorator_transparent (line 37) | func TestMessageTransformSubscriberDecorator_transparent(t *testing.T) {
  type closingSubscriber (line 59) | type closingSubscriber struct
    method Subscribe (line 63) | func (closingSubscriber) Subscribe(context.Context, string) (<-chan *m...
    method Close (line 67) | func (c *closingSubscriber) Close() error {
  function TestMessageTransformSubscriberDecorator_Close (line 72) | func TestMessageTransformSubscriberDecorator_Close(t *testing.T) {
  function TestMessageTransformSubscriberDecorator_Subscribe (line 98) | func TestMessageTransformSubscriberDecorator_Subscribe(t *testing.T) {
  type mockPublisher (line 139) | type mockPublisher struct
    method Publish (line 143) | func (m *mockPublisher) Publish(topic string, messages ...*message.Mes...
    method Close (line 148) | func (m mockPublisher) Close() error {
  function TestMessageTransformPublisherDecorator_transparent (line 152) | func TestMessageTransformPublisherDecorator_transparent(t *testing.T) {
  type closingPublisher (line 167) | type closingPublisher struct
    method Publish (line 171) | func (c *closingPublisher) Publish(topic string, messages ...*message....
    method Close (line 175) | func (c *closingPublisher) Close() error {
  function TestMessageTransformPublisherDecorator_Close (line 180) | func TestMessageTransformPublisherDecorator_Close(t *testing.T) {
  function TestMessageTransformPublisherDecorator_Subscribe (line 206) | func TestMessageTransformPublisherDecorator_Subscribe(t *testing.T) {
  function TestMessageTransformer_nil_panics (line 233) | func TestMessageTransformer_nil_panics(t *testing.T) {

FILE: message/message.go
  function init (line 11) | func init() {
  type Payload (line 16) | type Payload
  type Message (line 25) | type Message struct
    method Equals (line 81) | func (m *Message) Equals(toCompare *Message) bool {
    method Ack (line 101) | func (m *Message) Ack() bool {
    method Nack (line 127) | func (m *Message) Nack() bool {
    method Acked (line 159) | func (m *Message) Acked() <-chan struct{} {
    method Nacked (line 173) | func (m *Message) Nacked() <-chan struct{} {
    method Context (line 182) | func (m *Message) Context() context.Context {
    method SetContext (line 190) | func (m *Message) SetContext(ctx context.Context) {
    method Copy (line 196) | func (m *Message) Copy() *Message {
    method CopyWithContext (line 206) | func (m *Message) CopyWithContext() *Message {
  function NewMessage (line 55) | func NewMessage(uuid string, payload Payload) *Message {
  function NewMessageWithContext (line 66) | func NewMessageWithContext(ctx context.Context, uuid string, payload Pay...
  type ackType (line 72) | type ackType
  constant noAckSent (line 75) | noAckSent ackType = iota
  constant ack (line 76) | ack
  constant nack (line 77) | nack

FILE: message/message_test.go
  function TestMessage_Equals (line 13) | func TestMessage_Equals(t *testing.T) {
  function TestMessage_Ack (line 59) | func TestMessage_Ack(t *testing.T) {
  function TestMessage_Ack_idempotent (line 67) | func TestMessage_Ack_idempotent(t *testing.T) {
  function TestMessage_Ack_already_Nack (line 75) | func TestMessage_Ack_already_Nack(t *testing.T) {
  function TestMessage_Nack (line 82) | func TestMessage_Nack(t *testing.T) {
  function TestMessage_Nack_idempotent (line 90) | func TestMessage_Nack_idempotent(t *testing.T) {
  function TestMessage_Nack_already_Ack (line 98) | func TestMessage_Nack_already_Ack(t *testing.T) {
  function TestMessage_Copy (line 105) | func TestMessage_Copy(t *testing.T) {
  type ctxKey (line 116) | type ctxKey
  function TestMessage_CopyWithContext (line 118) | func TestMessage_CopyWithContext(t *testing.T) {
  function TestMessage_CopyWithContextAndMetadata (line 131) | func TestMessage_CopyWithContextAndMetadata(t *testing.T) {
  function TestMessage_CopyMetadata (line 146) | func TestMessage_CopyMetadata(t *testing.T) {
  function assertAcked (line 156) | func assertAcked(t *testing.T, msg *message.Message) {
  function assertNacked (line 165) | func assertNacked(t *testing.T, msg *message.Message) {
  function assertNoAck (line 174) | func assertNoAck(t *testing.T, msg *message.Message) {
  function assertNoNack (line 183) | func assertNoNack(t *testing.T, msg *message.Message) {

FILE: message/messages.go
  type Messages (line 4) | type Messages
    method IDs (line 7) | func (m Messages) IDs() []string {

FILE: message/messages_test.go
  function TestMessages_IDs (line 10) | func TestMessages_IDs(t *testing.T) {

FILE: message/metadata.go
  type Metadata (line 4) | type Metadata
    method Get (line 7) | func (m Metadata) Get(key string) string {
    method Set (line 16) | func (m Metadata) Set(key, value string) {

FILE: message/pubsub.go
  type Publisher (line 8) | type Publisher interface
  type Subscriber (line 26) | type Subscriber interface
  type SubscribeInitializer (line 42) | type SubscribeInitializer interface

FILE: message/router.go
  type HandlerFunc (line 32) | type HandlerFunc
  type NoPublishHandlerFunc (line 35) | type NoPublishHandlerFunc
  type HandlerMiddleware (line 59) | type HandlerMiddleware
  type RouterPlugin (line 62) | type RouterPlugin
  type PublisherDecorator (line 65) | type PublisherDecorator
  type SubscriberDecorator (line 68) | type SubscriberDecorator
  type RouterConfig (line 71) | type RouterConfig struct
    method setDefaults (line 76) | func (c *RouterConfig) setDefaults() {
    method Validate (line 83) | func (c RouterConfig) Validate() error {
  function NewRouter (line 88) | func NewRouter(config RouterConfig, logger watermill.LoggerAdapter) (*Ro...
  function NewDefaultRouter (line 98) | func NewDefaultRouter(logger watermill.LoggerAdapter) *Router {
  function newRouter (line 105) | func newRouter(config RouterConfig, logger watermill.LoggerAdapter) *Rou...
  type middleware (line 134) | type middleware struct
  type Router (line 144) | type Router struct
    method Logger (line 177) | func (r *Router) Logger() watermill.LoggerAdapter {
    method AddMiddleware (line 184) | func (r *Router) AddMiddleware(m ...HandlerMiddleware) {
    method addRouterLevelMiddleware (line 190) | func (r *Router) addRouterLevelMiddleware(m ...HandlerMiddleware) {
    method addHandlerLevelMiddleware (line 201) | func (r *Router) addHandlerLevelMiddleware(handlerName string, m ...Ha...
    method AddPlugin (line 218) | func (r *Router) AddPlugin(p ...RouterPlugin) {
    method AddPublisherDecorators (line 226) | func (r *Router) AddPublisherDecorators(dec ...PublisherDecorator) {
    method AddSubscriberDecorators (line 234) | func (r *Router) AddSubscriberDecorators(dec ...SubscriberDecorator) {
    method Handlers (line 241) | func (r *Router) Handlers() map[string]HandlerFunc {
    method AddHandler (line 272) | func (r *Router) AddHandler(
    method AddConsumerHandler (line 342) | func (r *Router) AddConsumerHandler(
    method AddNoPublisherHandler (line 367) | func (r *Router) AddNoPublisherHandler(
    method Run (line 386) | func (r *Router) Run(ctx context.Context) (err error) {
    method RunHandlers (line 426) | func (r *Router) RunHandlers(ctx context.Context) error {
    method watchAllHandlersStopped (line 497) | func (r *Router) watchAllHandlersStopped(ctx context.Context) {
    method Running (line 545) | func (r *Router) Running() chan struct{} {
    method IsRunning (line 553) | func (r *Router) IsRunning() bool {
    method Close (line 563) | func (r *Router) Close() error {
    method waitForHandlers (line 592) | func (r *Router) waitForHandlers() bool {
    method IsClosed (line 611) | func (r *Router) IsClosed() bool {
    method decorateHandlerPublisher (line 724) | func (r *Router) decorateHandlerPublisher(h *handler) error {
    method decorateHandlerSubscriber (line 740) | func (r *Router) decorateHandlerSubscriber(h *handler) error {
  type DuplicateHandlerNameError (line 252) | type DuplicateHandlerNameError struct
    method Error (line 256) | func (d DuplicateHandlerNameError) Error() string {
  type handler (line 618) | type handler struct
    method run (line 645) | func (h *handler) run(ctx context.Context, middlewares []middleware) {
    method addHandlerContext (line 767) | func (h *handler) addHandlerContext(messages ...*Message) {
    method handleClose (line 790) | func (h *handler) handleClose(ctx context.Context) {
    method handleMessage (line 805) | func (h *handler) handleMessage(msg *Message, handler HandlerFunc) {
    method publishProducedMessages (line 843) | func (h *handler) publishProducedMessages(producedMessages Messages, m...
  type Handler (line 683) | type Handler struct
    method AddMiddleware (line 691) | func (h *Handler) AddMiddleware(m ...HandlerMiddleware) {
    method Started (line 702) | func (h *Handler) Started() chan struct{} {
    method Stop (line 709) | func (h *Handler) Stop() {
    method Stopped (line 718) | func (h *Handler) Stopped() chan struct{} {
  type disabledPublisher (line 868) | type disabledPublisher struct
    method Publish (line 870) | func (disabledPublisher) Publish(topic string, messages ...*Message) e...
    method Close (line 874) | func (disabledPublisher) Close() error {

FILE: message/router/middleware/circuit_breaker.go
  type CircuitBreaker (line 11) | type CircuitBreaker struct
    method Middleware (line 24) | func (c CircuitBreaker) Middleware(h message.HandlerFunc) message.Hand...
  function NewCircuitBreaker (line 17) | func NewCircuitBreaker(settings gobreaker.Settings) CircuitBreaker {

FILE: message/router/middleware/circuit_breaker_test.go
  function TestCircuitBreaker (line 14) | func TestCircuitBreaker(t *testing.T) {

FILE: message/router/middleware/correlation.go
  constant CorrelationIDMetadataKey (line 8) | CorrelationIDMetadataKey = "correlation_id"
  function SetCorrelationID (line 15) | func SetCorrelationID(id string, msg *message.Message) {
  function MessageCorrelationID (line 24) | func MessageCorrelationID(message *message.Message) string {
  function CorrelationID (line 32) | func CorrelationID(h message.HandlerFunc) message.HandlerFunc {

FILE: message/router/middleware/correlation_test.go
  function TestCorrelationID (line 15) | func TestCorrelationID(t *testing.T) {
  function TestSetCorrelationID_already_set (line 32) | func TestSetCorrelationID_already_set(t *testing.T) {

FILE: message/router/middleware/deduplicator.go
  constant MessageHasherReadLimitMinimum (line 21) | MessageHasherReadLimitMinimum = 64
  type ExpiringKeyRepository (line 26) | type ExpiringKeyRepository interface
  type MessageHasher (line 37) | type MessageHasher
  type Deduplicator (line 64) | type Deduplicator struct
    method IsDuplicate (line 72) | func (d *Deduplicator) IsDuplicate(m *message.Message) (bool, error) {
    method Middleware (line 112) | func (d *Deduplicator) Middleware(h message.HandlerFunc) message.Handl...
    method PublisherDecorator (line 303) | func (d *Deduplicator) PublisherDecorator() message.PublisherDecorator {
  function applyDefaultsToDeduplicator (line 82) | func applyDefaultsToDeduplicator(d *Deduplicator) *Deduplicator {
  type mapExpiringKeyRepository (line 126) | type mapExpiringKeyRepository struct
    method IsDuplicate (line 161) | func (kr *mapExpiringKeyRepository) IsDuplicate(
    method cleanOutLoop (line 180) | func (kr *mapExpiringKeyRepository) cleanOutLoop(ctx context.Context, ...
    method cleanOut (line 191) | func (kr *mapExpiringKeyRepository) cleanOut(tagsBefore time.Time) {
    method Len (line 204) | func (kr *mapExpiringKeyRepository) Len() (count int) {
  function NewMapExpiringKeyRepository (line 145) | func NewMapExpiringKeyRepository(window time.Duration) (ExpiringKeyRepos...
  function NewMessageHasherAdler32 (line 219) | func NewMessageHasherAdler32(readLimit int64) MessageHasher {
  function NewMessageHasherSHA256 (line 241) | func NewMessageHasherSHA256(readLimit int64) MessageHasher {
  function NewMessageHasherFromMetadataField (line 260) | func NewMessageHasherFromMetadataField(field string) MessageHasher {
  type deduplicatingPublisherDecorator (line 270) | type deduplicatingPublisherDecorator struct
    method Publish (line 275) | func (d *deduplicatingPublisherDecorator) Publish(

FILE: message/router/middleware/deduplicator_test.go
  function TestDeduplicatorMiddleware (line 15) | func TestDeduplicatorMiddleware(t *testing.T) {
  function TestDeduplicatorPublisherDecorator (line 50) | func TestDeduplicatorPublisherDecorator(t *testing.T) {
  function TestMessageHasherAdler32 (line 100) | func TestMessageHasherAdler32(t *testing.T) {
  function TestMessageHasherSHA256 (line 117) | func TestMessageHasherSHA256(t *testing.T) {
  function TestMessageHasherFromMetadataField (line 134) | func TestMessageHasherFromMetadataField(t *testing.T) {
  function TestMapExpiringKeyRepositoryCleanup (line 152) | func TestMapExpiringKeyRepositoryCleanup(t *testing.T) {

FILE: message/router/middleware/delay_on_error.go
  type DelayOnError (line 14) | type DelayOnError struct
    method Middleware (line 23) | func (d *DelayOnError) Middleware(h message.HandlerFunc) message.Handl...
    method applyDelay (line 34) | func (d *DelayOnError) applyDelay(msg *message.Message) {

FILE: message/router/middleware/delay_on_error_test.go
  function TestDelayOnError (line 15) | func TestDelayOnError(t *testing.T) {

FILE: message/router/middleware/duplicator.go
  function Duplicator (line 8) | func Duplicator(h message.HandlerFunc) message.HandlerFunc {

FILE: message/router/middleware/duplicator_test.go
  function TestDuplicator (line 17) | func TestDuplicator(t *testing.T) {
  function TestDuplicator_errors (line 31) | func TestDuplicator_errors(t *testing.T) {

FILE: message/router/middleware/ignore_errors.go
  type IgnoreErrors (line 9) | type IgnoreErrors struct
    method Middleware (line 25) | func (i IgnoreErrors) Middleware(h message.HandlerFunc) message.Handle...
  function NewIgnoreErrors (line 14) | func NewIgnoreErrors(errs []error) IgnoreErrors {

FILE: message/router/middleware/ignore_errors_test.go
  function TestIgnoreErrors_Middleware (line 12) | func TestIgnoreErrors_Middleware(t *testing.T) {

FILE: message/router/middleware/instant_ack.go
  function InstantAck (line 9) | func InstantAck(h message.HandlerFunc) message.HandlerFunc {

FILE: message/router/middleware/instant_ack_test.go
  function TestInstantAck (line 11) | func TestInstantAck(t *testing.T) {

FILE: message/router/middleware/message_test.go
  type mockPublisherBehaviour (line 9) | type mockPublisherBehaviour
  constant BehaviourAlwaysOK (line 12) | BehaviourAlwaysOK mockPublisherBehaviour = iota + 1
  constant BehaviourAlwaysFail (line 13) | BehaviourAlwaysFail
  constant BehaviourAlwaysPanic (line 14) | BehaviourAlwaysPanic
  type mockPublisher (line 23) | type mockPublisher struct
    method Publish (line 30) | func (mp *mockPublisher) Publish(topic string, messages ...*message.Me...
    method Close (line 47) | func (mp *mockPublisher) Close() error {
    method PopMessages (line 52) | func (mp *mockPublisher) PopMessages() []*message.Message {
  function handlerFuncAlwaysOK (line 62) | func handlerFuncAlwaysOK(*message.Message) ([]*message.Message, error) {
  function handlerFuncAlwaysFailing (line 66) | func handlerFuncAlwaysFailing(*message.Message) ([]*message.Message, err...

FILE: message/router/middleware/poison.go
  constant ReasonForPoisonedKey (line 15) | ReasonForPoisonedKey  = "reason_poisoned"
  constant PoisonedTopicKey (line 16) | PoisonedTopicKey      = "topic_poisoned"
  constant PoisonedHandlerKey (line 17) | PoisonedHandlerKey    = "handler_poisoned"
  constant PoisonedSubscriberKey (line 18) | PoisonedSubscriberKey = "subscriber_poisoned"
  type poisonQueue (line 21) | type poisonQueue struct
    method publishPoisonMessage (line 62) | func (pq poisonQueue) publishPoisonMessage(msg *message.Message, err e...
    method Middleware (line 78) | func (pq poisonQueue) Middleware(h message.HandlerFunc) message.Handle...
  function PoisonQueue (line 30) | func PoisonQueue(pub message.Publisher, topic string) (message.HandlerMi...
  function PoisonQueueWithFilter (line 47) | func PoisonQueueWithFilter(pub message.Publisher, topic string, shouldGo...

FILE: message/router/middleware/poison_test.go
  constant topic (line 20) | topic = "testing_poison_queue_topic"
  function TestPoisonQueue_handler_ok (line 24) | func TestPoisonQueue_handler_ok(t *testing.T) {
  function TestPoisonQueue_handler_failing (line 62) | func TestPoisonQueue_handler_failing(t *testing.T) {
  function TestPoisonQueue_context_values (line 113) | func TestPoisonQueue_context_values(t *testing.T) {
  function TestPoisonQueue_handler_failing_publisher_failing (line 160) | func TestPoisonQueue_handler_failing_publisher_failing(t *testing.T) {
  function TestPoisonQueueWithFilter_poison_queue (line 205) | func TestPoisonQueueWithFilter_poison_queue(t *testing.T) {
  function TestPoisonQueueWithFilter_non_poison_queue (line 224) | func TestPoisonQueueWithFilter_non_poison_queue(t *testing.T) {

FILE: message/router/middleware/randomfail.go
  function shouldFail (line 10) | func shouldFail(probability float32) bool {
  function RandomFail (line 16) | func RandomFail(errorProbability float32) message.HandlerMiddleware {
  function RandomPanic (line 29) | func RandomPanic(panicProbability float32) message.HandlerMiddleware {

FILE: message/router/middleware/randomfail_test.go
  function TestRandomFail (line 11) | func TestRandomFail(t *testing.T) {
  function TestRandomPanic (line 20) | func TestRandomPanic(t *testing.T) {

FILE: message/router/middleware/recoverer.go
  type RecoveredPanicError (line 12) | type RecoveredPanicError struct
    method Error (line 17) | func (p RecoveredPanicError) Error() string {
  function Recoverer (line 23) | func Recoverer(h message.HandlerFunc) message.HandlerFunc {

FILE: message/router/middleware/recoverer_test.go
  function TestRecoverer_Panic (line 13) | func TestRecoverer_Panic(t *testing.T) {
  function TestRecoverer_PanicNil (line 23) | func TestRecoverer_PanicNil(t *testing.T) {
  function TestRecoverer_NoPanic (line 32) | func TestRecoverer_NoPanic(t *testing.T) {

FILE: message/router/middleware/retry.go
  type RetryParams (line 14) | type RetryParams struct
  type Retry (line 25) | type Retry struct
    method Middleware (line 60) | func (r Retry) Middleware(h message.HandlerFunc) message.HandlerFunc {

FILE: message/router/middleware/retry_test.go
  function TestRetry_retry (line 17) | func TestRetry_retry(t *testing.T) {
  function TestRetry_max_retries (line 41) | func TestRetry_max_retries(t *testing.T) {
  function TestRetry_retry_hook (line 60) | func TestRetry_retry_hook(t *testing.T) {
  function TestRetry_logger (line 78) | func TestRetry_logger(t *testing.T) {
  function TestRetry_ctx_cancel (line 96) | func TestRetry_ctx_cancel(t *testing.T) {
  function TestRetry_max_elapsed (line 149) | func TestRetry_max_elapsed(t *testing.T) {
  function TestRetry_max_interval (line 178) | func TestRetry_max_interval(t *testing.T) {
  function TestRetry_first_run_no_delay (line 206) | func TestRetry_first_run_no_delay(t *testing.T) {
  function TestRetry_should_retry (line 227) | func TestRetry_should_retry(t *testing.T) {
  function TestRetry_should_retry_second_attempt (line 258) | func TestRetry_should_retry_second_attempt(t *testing.T) {
  function TestRetry_backoff_backoff_permanent (line 293) | func TestRetry_backoff_backoff_permanent(t *testing.T) {
  function TestRetry_uncancel_context (line 324) | func TestRetry_uncancel_context(t *testing.T) {

FILE: message/router/middleware/throttle.go
  type Throttle (line 11) | type Throttle struct
    method Middleware (line 24) | func (t Throttle) Middleware(h message.HandlerFunc) message.HandlerFunc {
  function NewThrottle (line 17) | func NewThrottle(count int64, duration time.Duration) *Throttle {

FILE: message/router/middleware/throttle_test.go
  constant perSecond (line 16) | perSecond          = 10
  constant testTimeout (line 17) | testTimeout        = time.Second
  constant concurrentHandlers (line 18) | concurrentHandlers = 10
  function TestThrottle_Middleware (line 21) | func TestThrottle_Middleware(t *testing.T) {

FILE: message/router/middleware/timeout.go
  function Timeout (line 12) | func Timeout(timeout time.Duration) func(message.HandlerFunc) message.Ha...

FILE: message/router/middleware/timeout_test.go
  function TestTimeout (line 14) | func TestTimeout(t *testing.T) {

FILE: message/router/plugin/signals.go
  function SignalsHandler (line 13) | func SignalsHandler(r *message.Router) error {

FILE: message/router_context.go
  type ctxKey (line 7) | type ctxKey
  constant handlerNameKey (line 10) | handlerNameKey    ctxKey = "handler_name"
  constant publisherNameKey (line 11) | publisherNameKey  ctxKey = "publisher_name"
  constant subscriberNameKey (line 12) | subscriberNameKey ctxKey = "subscriber_name"
  constant subscribeTopicKey (line 13) | subscribeTopicKey ctxKey = "subscribe_topic"
  constant publishTopicKey (line 14) | publishTopicKey   ctxKey = "publish_topic"
  function valFromCtx (line 17) | func valFromCtx(ctx context.Context, key ctxKey) string {
  function HandlerNameFromCtx (line 26) | func HandlerNameFromCtx(ctx context.Context) string {
  function PublisherNameFromCtx (line 32) | func PublisherNameFromCtx(ctx context.Context) string {
  function SubscriberNameFromCtx (line 38) | func SubscriberNameFromCtx(ctx context.Context) string {
  function SubscribeTopicFromCtx (line 43) | func SubscribeTopicFromCtx(ctx context.Context) string {
  function PublishTopicFromCtx (line 48) | func PublishTopicFromCtx(ctx context.Context) string {

FILE: message/router_context_test.go
  type namedMockPublisher (line 13) | type namedMockPublisher struct
    method Publish (line 15) | func (namedMockPublisher) Publish(topic string, messages ...*message.M...
    method Close (line 16) | func (namedMockPublisher) Close() error                               ...
    method String (line 17) | func (namedMockPublisher) String() string {
  type namedMockSubscriber (line 21) | type namedMockSubscriber struct
    method Subscribe (line 23) | func (s namedMockSubscriber) Subscribe(context.Context, string) (<-cha...
    method Close (line 26) | func (s *namedMockSubscriber) Close() error { close(s.ch); return nil }
    method String (line 27) | func (namedMockSubscriber) String() string {
  function TestRouter_Context_Stringer (line 31) | func TestRouter_Context_Stringer(t *testing.T) {
  type unnamedMockPublisher (line 78) | type unnamedMockPublisher struct
    method Publish (line 80) | func (unnamedMockPublisher) Publish(topic string, messages ...*message...
    method Close (line 81) | func (unnamedMockPublisher) Close() error                             ...
  type unnamedMockSubscriber (line 83) | type unnamedMockSubscriber struct
    method Subscribe (line 85) | func (s unnamedMockSubscriber) Subscribe(context.Context, string) (<-c...
    method Close (line 88) | func (s *unnamedMockSubscriber) Close() error { close(s.ch); return nil }
  function TestRouter_Context_TypeName (line 90) | func TestRouter_Context_TypeName(t *testing.T) {
  function setupPubsubNameTests (line 135) | func setupPubsubNameTests(t *testing.T, capturedMessages chan (*message....

FILE: message/router_test.go
  function TestRouter_functional (line 22) | func TestRouter_functional(t *testing.T) {
  function TestRouter_functional_nack (line 122) | func TestRouter_functional_nack(t *testing.T) {
  function TestRouter_ack_on_publishing_success (line 172) | func TestRouter_ack_on_publishing_success(t *testing.T) {
  function TestRouter_nack_on_publishing_failure (line 220) | func TestRouter_nack_on_publishing_failure(t *testing.T) {
  function TestRouter_nack_on_panic (line 268) | func TestRouter_nack_on_panic(t *testing.T) {
  function TestRouter_nack_on_handler_failure (line 316) | func TestRouter_nack_on_handler_failure(t *testing.T) {
  function TestRouter_AddMiddleware_to_router (line 361) | func TestRouter_AddMiddleware_to_router(t *testing.T) {
  function TestRouter_AddMiddleware_to_handler (line 441) | func TestRouter_AddMiddleware_to_handler(t *testing.T) {
  function TestRouter_AddMiddleware_to_handler_many (line 520) | func TestRouter_AddMiddleware_to_handler_many(t *testing.T) {
  function TestRouter_RunHandlers (line 614) | func TestRouter_RunHandlers(t *testing.T) {
  function TestRouter_close_handler (line 678) | func TestRouter_close_handler(t *testing.T) {
  type subscriberMock (line 762) | type subscriberMock struct
    method Subscribe (line 766) | func (s *subscriberMock) Subscribe(ctx context.Context, topic string) ...
    method Close (line 770) | func (s *subscriberMock) Close() error {
  type failingPublisherMock (line 775) | type failingPublisherMock struct
    method Publish (line 780) | func (p *failingPublisherMock) Publish(topic string, messages ...*mess...
    method Close (line 790) | func (p *failingPublisherMock) Close() error { return nil }
  function TestRouter_stop_when_all_handlers_stopped (line 792) | func TestRouter_stop_when_all_handlers_stopped(t *testing.T) {
  type benchMockSubscriber (line 853) | type benchMockSubscriber struct
    method Subscribe (line 857) | func (m benchMockSubscriber) Subscribe(_ context.Context, topic string...
    method Close (line 872) | func (benchMockSubscriber) Close() error {
  type nopPublisher (line 876) | type nopPublisher struct
    method Publish (line 878) | func (nopPublisher) Publish(topic string, messages ...*message.Message...
    method Close (line 879) | func (nopPublisher) Close() error                                     ...
  function BenchmarkRouterHandler (line 881) | func BenchmarkRouterHandler(b *testing.B) {
  function TestRouterNoPublisherHandler (line 917) | func TestRouterNoPublisherHandler(t *testing.T) {
  function BenchmarkRouterNoPublisherHandler (line 966) | func BenchmarkRouterNoPublisherHandler(b *testing.B) {
  function TestRouterDecoratorsOrder (line 1001) | func TestRouterDecoratorsOrder(t *testing.T) {
  function TestRouter_concurrent_close (line 1071) | func TestRouter_concurrent_close(t *testing.T) {
  function TestRouter_concurrent_close_on_handlers_closed (line 1086) | func TestRouter_concurrent_close_on_handlers_closed(t *testing.T) {
  function createBenchSubscriber (line 1119) | func createBenchSubscriber(b *testing.B) benchMockSubscriber {
  function publishMessagesForHandler (line 1131) | func publishMessagesForHandler(t *testing.T, messagesCount int, pub mess...
  function createPubSub (line 1148) | func createPubSub() (message.Publisher, message.Subscriber) {
  function readMessages (line 1156) | func readMessages(messagesCh <-chan *message.Message, limit int, timeout...
  function TestRouter_Handlers (line 1180) | func TestRouter_Handlers(t *testing.T) {
  function TestRouter_wait_for_handlers_before_shutdown (line 1224) | func TestRouter_wait_for_handlers_before_shutdown(t *testing.T) {
  function TestRouter_wait_for_handlers_before_shutdown_timeout (line 1278) | func TestRouter_wait_for_handlers_before_shutdown_timeout(t *testing.T) {
  function TestRouter_context_cancel_does_not_log_error (line 1323) | func TestRouter_context_cancel_does_not_log_error(t *testing.T) {
  function TestRouter_nack_on_context_canceled (line 1366) | func TestRouter_nack_on_context_canceled(t *testing.T) {
  function TestRouter_stopping_all_handlers_logs_error (line 1433) | func TestRouter_stopping_all_handlers_logs_error(t *testing.T) {

FILE: message/subscriber/read.go
  function BulkRead (line 10) | func BulkRead(messagesCh <-chan *message.Message, limit int, timeout tim...
  function BulkReadWithDeduplication (line 31) | func BulkReadWithDeduplication(messagesCh <-chan *message.Message, limit...

FILE: message/subscriber/read_test.go
  type bulkReadFunc (line 15) | type bulkReadFunc
  function TestBulkRead (line 17) | func TestBulkRead(t *testing.T) {
  function TestBulkRead_timeout (line 54) | func TestBulkRead_timeout(t *testing.T) {
  function TestBulkRead_with_limit (line 94) | func TestBulkRead_with_limit(t *testing.T) {
  function TestBulkRead_return_on_channel_close (line 129) | func TestBulkRead_return_on_channel_close(t *testing.T) {
  function TestBulkReadWithDeduplication (line 172) | func TestBulkReadWithDeduplication(t *testing.T) {

FILE: pubsub/gochannel/fanout.go
  type FanOut (line 23) | type FanOut struct
    method AddSubscription (line 67) | func (f *FanOut) AddSubscription(topic string) {
    method Run (line 94) | func (f *FanOut) Run(ctx context.Context) error {
    method Running (line 99) | func (f *FanOut) Running() chan struct{} {
    method IsClosed (line 103) | func (f *FanOut) IsClosed() bool {
    method Subscribe (line 108) | func (f *FanOut) Subscribe(ctx context.Context, topic string) (<-chan ...
    method Close (line 113) | func (f *FanOut) Close() error {
  function NewFanOut (line 36) | func NewFanOut(

FILE: pubsub/gochannel/fanout_test.go
  function TestFanOut (line 17) | func TestFanOut(t *testing.T) {
  function TestFanOut_RouterClosed (line 97) | func TestFanOut_RouterClosed(t *testing.T) {

FILE: pubsub/gochannel/pubsub.go
  type Config (line 15) | type Config struct
  type GoChannel (line 43) | type GoChannel struct
    method Publish (line 90) | func (g *GoChannel) Publish(topic string, messages ...*message.Message...
    method waitForAckFromSubscribers (line 140) | func (g *GoChannel) waitForAckFromSubscribers(msg *message.Message, ac...
    method sendMessage (line 152) | func (g *GoChannel) sendMessage(topic string, message *message.Message...
    method Subscribe (line 188) | func (g *GoChannel) Subscribe(ctx context.Context, topic string) (<-ch...
    method addSubscriber (line 266) | func (g *GoChannel) addSubscriber(topic string, s *subscriber) {
    method removeSubscriber (line 273) | func (g *GoChannel) removeSubscriber(topic string, toRemove *subscribe...
    method topicSubscribers (line 298) | func (g *GoChannel) topicSubscribers(topic string) []*subscriber {
    method isClosed (line 311) | func (g *GoChannel) isClosed() bool {
    method Close (line 319) | func (g *GoChannel) Close() error {
  function NewGoChannel (line 64) | func NewGoChannel(config Config, logger watermill.LoggerAdapter) *GoChan...
  type subscriber (line 339) | type subscriber struct
    method Close (line 354) | func (s *subscriber) Close() {
    method sendMessageToSubscriber (line 372) | func (s *subscriber) sendMessageToSubscriber(msg *message.Message, log...

FILE: pubsub/gochannel/pubsub_bench_test.go
  function BenchmarkSubscriber (line 14) | func BenchmarkSubscriber(b *testing.B) {
  function BenchmarkSubscriberPersistent (line 23) | func BenchmarkSubscriberPersistent(b *testing.B) {

FILE: pubsub/gochannel/pubsub_internal_test.go
  function TestSubscribe_clean_subscriber_data (line 15) | func TestSubscribe_clean_subscriber_data(t *testing.T) {
  function TestPublish_clean_lock_data (line 44) | func TestPublish_clean_lock_data(t *testing.T) {

FILE: pubsub/gochannel/pubsub_stress_test.go
  function TestPublishSubscribe_stress (line 12) | func TestPublishSubscribe_stress(t *testing.T) {

FILE: pubsub/gochannel/pubsub_test.go
  function createPersistentPubSub (line 21) | func createPersistentPubSub(t *testing.T) (message.Publisher, message.Su...
  function createPersistentPubSubWithContextPreserved (line 32) | func createPersistentPubSubWithContextPreserved(t *testing.T) (message.P...
  function TestPublishSubscribe_persistent (line 44) | func TestPublishSubscribe_persistent(t *testing.T) {
  function TestPublishSubscribe_context_preserved (line 59) | func TestPublishSubscribe_context_preserved(t *testing.T) {
  function TestPublishSubscribe_not_persistent (line 75) | func TestPublishSubscribe_not_persistent(t *testing.T) {
  function TestPublishSubscribe_not_persistent_with_context (line 94) | func TestPublishSubscribe_not_persistent_with_context(t *testing.T) {
  function TestPublishSubscribe_block_until_ack (line 119) | func TestPublishSubscribe_block_until_ack(t *testing.T) {
  function TestPublishSubscribe_race_condition_on_subscribe (line 163) | func TestPublishSubscribe_race_condition_on_subscribe(t *testing.T) {
  function TestSubscribe_race_condition_when_closing (line 177) | func TestSubscribe_race_condition_when_closing(t *testing.T) {
  function TestPublish_race_condition_when_closing (line 200) | func TestPublish_race_condition_when_closing(t *testing.T) {
  function TestPublishSubscribe_do_not_block_other_subscribers (line 223) | func TestPublishSubscribe_do_not_block_other_subscribers(t *testing.T) {
  function TestPublishSubscribe_flush_output_channel (line 261) | func TestPublishSubscribe_flush_output_channel(t *testing.T) {
  function testPublishSubscribeSubRace (line 336) | func testPublishSubscribeSubRace(t *testing.T) {

FILE: pubsub/sync/waitgroup.go
  function WaitGroupTimeout (line 10) | func WaitGroupTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {

FILE: pubsub/sync/waitgroup_test.go
  function TestWaitGroupTimeout_no_timeout (line 11) | func TestWaitGroupTimeout_no_timeout(t *testing.T) {
  function TestWaitGroupTimeout_timeout (line 18) | func TestWaitGroupTimeout_timeout(t *testing.T) {

FILE: pubsub/tests/bench_pubsub.go
  type BenchmarkPubSubConstructor (line 13) | type BenchmarkPubSubConstructor
  function BenchSubscriber (line 16) | func BenchSubscriber(b *testing.B, pubSubConstructor BenchmarkPubSubCons...

FILE: pubsub/tests/test_asserts.go
  function difference (line 12) | func difference(a, b []string) []string {
  function MissingMessages (line 27) | func MissingMessages(expected message.Messages, received message.Message...
  function AssertAllMessagesReceived (line 39) | func AssertAllMessagesReceived(t *testing.T, sent message.Messages, rece...
  function AssertMessagesPayloads (line 62) | func AssertMessagesPayloads(
  function AssertMessagesMetadata (line 85) | func AssertMessagesMetadata(t *testing.T, key string, expectedValues map...
  function AssertAllMessagesHaveSameContext (line 99) | func AssertAllMessagesHaveSameContext(t *testing.T, contextKeyString str...

FILE: pubsub/tests/test_pubsub.go
  function TestPubSub (line 34) | func TestPubSub(
  type Features (line 95) | type Features struct
  function RunOnlyFastTests (line 141) | func RunOnlyFastTests() bool {
  type PubSubConstructor (line 146) | type PubSubConstructor
  type ConsumerGroupPubSubConstructor (line 149) | type ConsumerGroupPubSubConstructor
  type SimpleMessage (line 152) | type SimpleMessage struct
  function getTestName (line 156) | func getTestName(testFunc interface{}) string {
  type TestID (line 164) | type TestID
    method String (line 166) | func (t TestID) String() string {
  function NewTestID (line 171) | func NewTestID() TestID {
  function NewTestULID (line 176) | func NewTestULID() TestID {
  type TestContext (line 181) | type TestContext struct
  function runTest (line 189) | func runTest(
  constant defaultStressTestTestsCount (line 213) | defaultStressTestTestsCount = 10
  function TestPubSubStressTest (line 216) | func TestPubSubStressTest(
  function TestPublishSubscribe (line 236) | func TestPublishSubscribe(
  function TestConcurrentSubscribe (line 284) | func TestConcurrentSubscribe(
  function TestConcurrentSubscribeMultipleTopics (line 327) | func TestConcurrentSubscribeMultipleTopics(
  function TestPublishSubscribeInOrder (line 405) | func TestPublishSubscribeInOrder(
  function TestResendOnError (line 484) | func TestResendOnError(
  function TestNoAck (line 533) | func TestNoAck(
  function TestContinueAfterSubscribeClose (line 607) | func TestContinueAfterSubscribeClose(
  function TestConcurrentClose (line 704) | func TestConcurrentClose(
  function TestContinueAfterErrors (line 755) | func TestContinueAfterErrors(
  function TestConsumerGroups (line 817) | func TestConsumerGroups(
  function TestPublisherClose (line 845) | func TestPublisherClose(
  function TestTopic (line 873) | func TestTopic(
  function TestMessageCtx (line 914) | func TestMessageCtx(
  type contextKey (line 970) | type contextKey
  function TestSubscribeCtx (line 973) | func TestSubscribeCtx(
  function TestReconnect (line 1042) | func TestReconnect(
  function TestNewSubscriberReceivesOldMessages (line 1126) | func TestNewSubscriberReceivesOldMessages(
  function restartServer (line 1202) | func restartServer(t *testing.T, features Features) {
  function assertConsumerGroupReceivedMessages (line 1215) | func assertConsumerGroupReceivedMessages(
  function testTopicName (line 1235) | func testTopicName(tCtx TestContext) string {
  function newTestID (line 1243) | func newTestID(tCtx TestContext) TestID {
  function closePubSub (line 1251) | func closePubSub(t *testing.T, pub message.Publisher, sub message.Subscr...
  function generateConsumerGroup (line 1259) | func generateConsumerGroup(t *testing.T, pubSubConstructor ConsumerGroup...
  function PublishSimpleMessages (line 1276) | func PublishSimpleMessages(t *testing.T, messagesCount int, publisher me...
  function PublishSimpleMessagesWithContext (line 1293) | func PublishSimpleMessagesWithContext(t *testing.T, messagesCount int, c...
  function AddSimpleMessagesParallel (line 1312) | func AddSimpleMessagesParallel(t *testing.T, messagesCount int, publishe...
  function assertMessagesChannelClosed (line 1344) | func assertMessagesChannelClosed(t *testing.T, messages <-chan *message....
  function publishWithRetry (line 1354) | func publishWithRetry(publisher message.Publisher, topic string, message...
  function bulkRead (line 1372) | func bulkRead(testCtx TestContext, messagesCh <-chan *message.Message, l...
  function createMultipliedSubscriber (line 1393) | func createMultipliedSubscriber(t *testing.T, pubSubConstructor PubSubCo...

FILE: pubsub/tests/test_pubsub_stress.go
  function init (line 10) | func init() {

FILE: slog.go
  constant LevelTrace (line 9) | LevelTrace = slog.LevelDebug - 4
  function slogAttrsFromFields (line 11) | func slogAttrsFromFields(fields LogFields) []any {
  type SlogLoggerAdapter (line 22) | type SlogLoggerAdapter struct
    method Error (line 29) | func (s *SlogLoggerAdapter) Error(msg string, err error, fields LogFie...
    method Info (line 34) | func (s *SlogLoggerAdapter) Info(msg string, fields LogFields) {
    method Debug (line 39) | func (s *SlogLoggerAdapter) Debug(msg string, fields LogFields) {
    method Trace (line 44) | func (s *SlogLoggerAdapter) Trace(msg string, fields LogFields) {
    method log (line 52) | func (s *SlogLoggerAdapter) log(level slog.Level, msg string, args ......
    method With (line 73) | func (s *SlogLoggerAdapter) With(fields LogFields) LoggerAdapter {
  function NewSlogLogger (line 78) | func NewSlogLogger(logger *slog.Logger) LoggerAdapter {
  function NewSlogLoggerWithLevelMapping (line 90) | func NewSlogLoggerWithLevelMapping(logger *slog.Logger, watermillLevelTo...

FILE: slog_test.go
  function TestSlogLoggerAdapter (line 14) | func TestSlogLoggerAdapter(t *testing.T) {
  function TestSlogLoggerAdapter_level_mapping (line 55) | func TestSlogLoggerAdapter_level_mapping(t *testing.T) {

FILE: tools/mill/cmd/amqp.go
  function amqpConsumerConfig (line 42) | func amqpConsumerConfig() amqp.Config {
  function amqpProducerConfig (line 76) | func amqpProducerConfig() amqp.Config {
  function init (line 103) | func init() {
  function configureAmqpCmd (line 112) | func configureAmqpCmd() {
  function configureConsumeCmd (line 137) | func configureConsumeCmd(consumeCmd *cobra.Command) {
  function configureProduceCmd (line 156) | func configureProduceCmd(produceCmd *cobra.Command) {

FILE: tools/mill/cmd/consume.go
  function addConsumeCmd (line 20) | func addConsumeCmd(parent *cobra.Command, topicKey string) *cobra.Command {

FILE: tools/mill/cmd/googlecloud.go
  function generateTempSubscription (line 185) | func generateTempSubscription() (id string, err error) {
  function addSubscription (line 206) | func addSubscription(
  function removeTempSubscription (line 248) | func removeTempSubscription() (err error) {
  function removeSubscription (line 259) | func removeSubscription(id string) error {
  function listSubscriptions (line 281) | func listSubscriptions(topic string, adapter watermill.LoggerAdapter, ve...
  function listSubscriptionsForTopic (line 319) | func listSubscriptionsForTopic(ctx context.Context, client *pubsub.Clien...
  function printSubscriptionInfo (line 360) | func printSubscriptionInfo(name string, config pubsub.SubscriptionConfig...
  type subscriptionConfig (line 381) | type subscriptionConfig struct
  type subscriptionConfigPushConfig (line 390) | type subscriptionConfigPushConfig struct
  function projectID (line 395) | func projectID() string {
  function init (line 404) | func init() {

FILE: tools/mill/cmd/internal/indent.go
  function Indent (line 6) | func Indent(s, prefix string) string {

FILE: tools/mill/cmd/kafka.go
  function init (line 66) | func init() {

FILE: tools/mill/cmd/produce.go
  function addProduceCmd (line 20) | func addProduceCmd(parent *cobra.Command, topicKey string) *cobra.Command {

FILE: tools/mill/cmd/root.go
  function Execute (line 65) | func Execute() {
  function init (line 72) | func init() {
  function initConfig (line 95) | func initConfig() {
  function ensure (line 120) | func ensure(err error) {
  function checkRequiredFlags (line 126) | func checkRequiredFlags(flags *pflag.FlagSet) error {

FILE: tools/mill/main.go
  function main (line 5) | func main() {

FILE: tools/pq/backend/postgres.go
  type PostgresMessage (line 17) | type PostgresMessage struct
  type PostgresBackend (line 24) | type PostgresBackend struct
    method AllMessages (line 46) | func (r *PostgresBackend) AllMessages(ctx context.Context) ([]cli.Mess...
    method Requeue (line 73) | func (r *PostgresBackend) Requeue(ctx context.Context, msg cli.Message...
    method Ack (line 84) | func (r *PostgresBackend) Ack(ctx context.Context, msg cli.Message) er...
    method topic (line 93) | func (r *PostgresBackend) topic() string {
  function NewPostgresBackend (line 29) | func NewPostgresBackend(ctx context.Context, config cli.BackendConfig) (...

FILE: tools/pq/cli/backend.go
  type BackendConfig (line 9) | type BackendConfig struct
    method Validate (line 14) | func (c BackendConfig) Validate() error {
  type BackendConstructor (line 26) | type BackendConstructor
  type Backend (line 28) | type Backend interface

FILE: tools/pq/cli/message.go
  type Message (line 10) | type Message struct
  function NewMessage (line 23) | func NewMessage(id string, uuid string, payload string, metadata map[str...

FILE: tools/pq/cli/model.go
  type MessagesUpdated (line 37) | type MessagesUpdated struct
  type DialogResult (line 41) | type DialogResult struct
  type Model (line 72) | type Model struct
    method FetchMessages (line 45) | func (m Model) FetchMessages() tea.Cmd {
    method WaitForMessages (line 62) | func (m Model) WaitForMessages() tea.Cmd {
    method Init (line 89) | func (m Model) Init() tea.Cmd {
    method Update (line 96) | func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    method View (line 288) | func (m Model) View() string {
  function NewModel (line 364) | func NewModel(backend Backend) Model {
  type Dialog (line 398) | type Dialog struct

FILE: tools/pq/main.go
  function main (line 20) | func main() {

FILE: uuid.go
  function NewUUID (line 12) | func NewUUID() string {
  function NewShortUUID (line 17) | func NewShortUUID() string {
  function NewULID (line 22) | func NewULID() string {

FILE: uuid_test.go
  function testUniqueness (line 10) | func testUniqueness(t *testing.T, genFunc func() string) {
  function TestUUID (line 47) | func TestUUID(t *testing.T) {
  function TestShortUUID (line 51) | func TestShortUUID(t *testing.T) {
  function TestULID (line 55) | func TestULID(t *testing.T) {
Condensed preview — 495 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,365K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 877,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n<!--\nThe resour"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 27,
    "preview": "blank_issues_enabled: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 651,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n## F"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 1535,
    "preview": "<!--\nThanks for contributing to Watermill!\n\nThe following template aims to help contributors write a good description fo"
  },
  {
    "path": ".github/workflows/master.yml",
    "chars": 253,
    "preview": "name: master\non:\n  push:\n    branches:\n      - master\njobs:\n  ci:\n    uses: ThreeDotsLabs/watermill/.github/workflows/te"
  },
  {
    "path": ".github/workflows/pr-examples.yml",
    "chars": 318,
    "preview": "name: pr-examples\non:\n  pull_request:\n    paths:\n      - '_examples/**/*'\n\njobs:\n  validate-examples:\n    runs-on: ubunt"
  },
  {
    "path": ".github/workflows/pr.yml",
    "chars": 203,
    "preview": "name: pr\non:\n  pull_request:\njobs:\n  ci:\n    uses: ThreeDotsLabs/watermill/.github/workflows/tests.yml@master\n    with:\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 2506,
    "preview": "name: tests\non:\n  workflow_call:\n    inputs:\n      stress-tests:\n        description: 'Run stress tests'\n        require"
  },
  {
    "path": ".gitignore",
    "chars": 148,
    "preview": ".idea\nvendor\ndocs/themes/\ndocs/node_modules/\ndocs/public/\ndocs/content/src-link\ndocs/content/middleware\ndocs/hugo_stats."
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2772,
    "preview": "# Contributors guide v0.1\n\n## How can I help?\n\nWe are always happy to help you in contributing to Watermill. If you have"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "MIT License\n\nCopyright (c) 2019 Three Dots Labs\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "Makefile",
    "chars": 660,
    "preview": "up:\n\ntest:\n\tgo test ./...\n\ntest_v:\n\tgo test -v ./...\n\ntest_short:\n\tgo test ./... -short\n\ntest_race:\n\tgo test ./... -shor"
  },
  {
    "path": "README.md",
    "chars": 9494,
    "preview": "# Watermill\n<img align=\"right\" width=\"300\" src=\"https://watermill.io/img/gopher.svg\">\n\n[![CI Status](https://github.com/"
  },
  {
    "path": "RELEASE-PROCEDURE.md",
    "chars": 488,
    "preview": "# Release procedure\n\n1. Generate clean go.mod: `make generate_gomod`\n2. Push to master\n3. Update missing documentation\n4"
  },
  {
    "path": "UPGRADE-0.3.md",
    "chars": 966,
    "preview": "# UPGRADE FROM 0.2.x to 0.3\n\n# `watermill/message`\n\n- `message.Message.Ack` and `message.Message.Nack` now return `bool`"
  },
  {
    "path": "UPGRADE-0.4.md",
    "chars": 2197,
    "preview": "# UPGRADE FROM 0.3.x to 0.4\n\n## `watermill/components/cqrs`\n\n### `CommandHandler.HandlerName` and `EventHandler.HandlerN"
  },
  {
    "path": "UPGRADE-1.0.md",
    "chars": 1635,
    "preview": "# Upgrade instructions from v0.4.X\n\nIn v1.0.0 we introduced a couple of breaking changes, to keep a stable API until ver"
  },
  {
    "path": "_examples/basic/1-your-first-app/.validate_example.yml",
    "chars": 131,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"received event {I"
  },
  {
    "path": "_examples/basic/1-your-first-app/README.md",
    "chars": 2367,
    "preview": "# Your first Watermill app\n\nThis example project shows a basic setup of Watermill. The application runs in a loop, consu"
  },
  {
    "path": "_examples/basic/1-your-first-app/docker-compose.yml",
    "chars": 560,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    volumes:\n    - .:/app\n    - $GOPATH/pkg/mod:/"
  },
  {
    "path": "_examples/basic/1-your-first-app/go.mod",
    "chars": 1763,
    "preview": "module main.go\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-kafka/"
  },
  {
    "path": "_examples/basic/1-your-first-app/go.sum",
    "chars": 11856,
    "preview": "github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=\ngithub.com/IBM/sarama v1.46.0/go.mod h1:0l"
  },
  {
    "path": "_examples/basic/1-your-first-app/main.go",
    "chars": 3587,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"github.com/Th"
  },
  {
    "path": "_examples/basic/2-realtime-feed/.validate_example_subscribing.yml",
    "chars": 119,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"Adding to feed\"\n"
  },
  {
    "path": "_examples/basic/2-realtime-feed/README.md",
    "chars": 1240,
    "preview": "# Realtime Feed\n\nThis example features a very busy blogging platform, with thousands of messages showing up on your feed"
  },
  {
    "path": "_examples/basic/2-realtime-feed/consumer/go.mod",
    "chars": 1763,
    "preview": "module main.go\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-kafka/"
  },
  {
    "path": "_examples/basic/2-realtime-feed/consumer/go.sum",
    "chars": 11856,
    "preview": "github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=\ngithub.com/IBM/sarama v1.46.0/go.mod h1:0l"
  },
  {
    "path": "_examples/basic/2-realtime-feed/consumer/main.go",
    "chars": 4916,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n"
  },
  {
    "path": "_examples/basic/2-realtime-feed/docker-compose.yml",
    "chars": 1062,
    "preview": "services:\n  producer:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n    - kafka\n    volumes:\n    - "
  },
  {
    "path": "_examples/basic/2-realtime-feed/producer/go.mod",
    "chars": 1804,
    "preview": "module main.go\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-kafka/"
  },
  {
    "path": "_examples/basic/2-realtime-feed/producer/go.sum",
    "chars": 12039,
    "preview": "github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=\ngithub.com/IBM/sarama v1.46.0/go.mod h1:0l"
  },
  {
    "path": "_examples/basic/2-realtime-feed/producer/main.go",
    "chars": 2307,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/brianvoe/go"
  },
  {
    "path": "_examples/basic/3-router/.validate_example.yml",
    "chars": 96,
    "preview": "validation_cmd: \"go run main.go\"\ntimeout: 120\nexpected_output: \"Received message: [0-9a-f\\\\-]+\"\n"
  },
  {
    "path": "_examples/basic/3-router/go.mod",
    "chars": 366,
    "preview": "module main.go\n\ngo 1.25\n\nrequire github.com/ThreeDotsLabs/watermill v1.5.1\n\nrequire (\n\tgithub.com/cenkalti/backoff/v5 v5"
  },
  {
    "path": "_examples/basic/3-router/go.sum",
    "chars": 2230,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/basic/3-router/main.go",
    "chars": 4160,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"gi"
  },
  {
    "path": "_examples/basic/4-metrics/.validate_example.yml",
    "chars": 126,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"msg=\\\"Message ack"
  },
  {
    "path": "_examples/basic/4-metrics/README.md",
    "chars": 3682,
    "preview": "# Prometheus metrics showcase\n\nThis is an example application that showcases how Watermill may be monitored with Prometh"
  },
  {
    "path": "_examples/basic/4-metrics/docker-compose.yml",
    "chars": 561,
    "preview": "services:\n  golang:\n    image: golang:1.25\n    restart: unless-stopped\n    ports:\n    - 8080:8080\n    - 8081:8081\n    de"
  },
  {
    "path": "_examples/basic/4-metrics/go.mod",
    "chars": 985,
    "preview": "module main.go\n\ngo 1.25\n\nrequire github.com/ThreeDotsLabs/watermill v1.5.1\n\nrequire (\n\tgithub.com/beorn7/perks v1.0.1 //"
  },
  {
    "path": "_examples/basic/4-metrics/go.sum",
    "chars": 4704,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/basic/4-metrics/main.go",
    "chars": 3745,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"math\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n"
  },
  {
    "path": "_examples/basic/4-metrics/prometheus.yml",
    "chars": 649,
    "preview": "# my global config\nglobal:\n  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 "
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/.validate_example.yml",
    "chars": 178,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 120\nexpected_outputs:\n  - \"beers ordere"
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/Makefile",
    "chars": 113,
    "preview": ".PHONY: proto\n\nproto:\n\tprotoc --proto_path=proto --go_out=. --go_opt=paths=source_relative  proto/messages.proto\n"
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/README.md",
    "chars": 1261,
    "preview": "# Example Golang CQRS application\n\nThis application is using [Watermill CQRS](http://watermill.io/docs/cqrs) component.\n"
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/docker-compose.yml",
    "chars": 346,
    "preview": "services:\n  golang:\n    image: golang:1.25\n    restart: unless-stopped\n    ports:\n    - 8080:8080\n    depends_on:\n      "
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/go.mod",
    "chars": 710,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2\n"
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/go.sum",
    "chars": 7137,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/main.go",
    "chars": 10577,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/watermill\""
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/messages.pb.go",
    "chars": 14363,
    "preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.31.0\n// \tprotoc        v4.24.4\n// sou"
  },
  {
    "path": "_examples/basic/5-cqrs-protobuf/proto/messages.proto",
    "chars": 627,
    "preview": "syntax = \"proto3\";\npackage main;\n\noption go_package = \"./main\";\n\nimport \"google/protobuf/timestamp.proto\";\n\nmessage Book"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/.validate_example.yml",
    "chars": 361,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 120\nexpected_outputs:\n  - \"Subscriber a"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/Makefile",
    "chars": 113,
    "preview": ".PHONY: proto\n\nproto:\n\tprotoc --proto_path=proto --go_out=. --go_opt=paths=source_relative  proto/messages.proto\n"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/README.md",
    "chars": 1478,
    "preview": "# Example Golang CQRS application - ordered events with Kafka\n\nThis application is using [Watermill CQRS](http://watermi"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/activity.go",
    "chars": 2176,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"sync\"\n\t\"time\"\n)\n\n// ActivityEntry represents a single event in th"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/docker-compose.yml",
    "chars": 1015,
    "preview": "services:\n  golang:\n    image: golang:1.25\n    restart: unless-stopped\n    ports:\n    - 8080:8080\n    depends_on:\n      "
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/go.mod",
    "chars": 1844,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/go.sum",
    "chars": 14400,
    "preview": "github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=\ngithub.com/IBM/sarama v1.46.0/go.mod h1:0l"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/main.go",
    "chars": 7815,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"github.com/ThreeDo"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/message.go",
    "chars": 1433,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\n\t\"github.com/ThreeDotsLabs/watermill/components/cqrs\"\n\t\"github.com/ThreeDotsL"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/messages.pb.go",
    "chars": 21838,
    "preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.31.0\n// \tprotoc        v4.24.4\n// sou"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/proto/messages.proto",
    "chars": 862,
    "preview": "syntax = \"proto3\";\npackage main;\n\noption go_package = \"./main\";\n\nimport \"google/protobuf/timestamp.proto\";\n\nmessage Mess"
  },
  {
    "path": "_examples/basic/6-cqrs-ordered-events/subscribers.go",
    "chars": 1338,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"sync\"\n)\n\ntype SubscriberReadModel struct {\n\tsubscribers map[string]strin"
  },
  {
    "path": "_examples/pubsubs/amqp/.validate_example.yml",
    "chars": 159,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 120\nexpected_output: \"received message:"
  },
  {
    "path": "_examples/pubsubs/amqp/docker-compose.yml",
    "chars": 283,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - rabbitmq\n    volumes:\n   "
  },
  {
    "path": "_examples/pubsubs/amqp/go.mod",
    "chars": 532,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2\n"
  },
  {
    "path": "_examples/pubsubs/amqp/go.sum",
    "chars": 2787,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/pubsubs/amqp/main.go",
    "chars": 1608,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "_examples/pubsubs/aws-sns/.validate_example.yml",
    "chars": 161,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 120\nexpected_output: \"A received messag"
  },
  {
    "path": "_examples/pubsubs/aws-sns/docker-compose.yml",
    "chars": 540,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    volumes:\n      - .:/app\n      - $GOPATH/pkg/m"
  },
  {
    "path": "_examples/pubsubs/aws-sns/go.mod",
    "chars": 702,
    "preview": "module main\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-aws v1.0.1\n\tgithub"
  },
  {
    "path": "_examples/pubsubs/aws-sns/go.sum",
    "chars": 5212,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/pubsubs/aws-sns/main.go",
    "chars": 3154,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/url\"\n\t\""
  },
  {
    "path": "_examples/pubsubs/aws-sqs/.validate_example.yml",
    "chars": 159,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 120\nexpected_output: \"received message:"
  },
  {
    "path": "_examples/pubsubs/aws-sqs/docker-compose.yml",
    "chars": 543,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    volumes:\n      - .:/app\n      - $GOPATH/pkg/m"
  },
  {
    "path": "_examples/pubsubs/aws-sqs/go.mod",
    "chars": 652,
    "preview": "module main\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-aws v1.0.1\n\tgithub"
  },
  {
    "path": "_examples/pubsubs/aws-sqs/go.sum",
    "chars": 5011,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/pubsubs/aws-sqs/main.go",
    "chars": 1881,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"net/url\"\n\t\"time\"\n\n"
  },
  {
    "path": "_examples/pubsubs/go-channel/.validate_example.yml",
    "chars": 87,
    "preview": "validation_cmd: \"go run main.go\"\ntimeout: 30\nexpected_output: \"payload: Hello, world!\"\n"
  },
  {
    "path": "_examples/pubsubs/go-channel/go.mod",
    "chars": 269,
    "preview": "module main.go\n\nrequire github.com/ThreeDotsLabs/watermill v1.5.1\n\nrequire (\n\tgithub.com/google/uuid v1.6.0 // indirect\n"
  },
  {
    "path": "_examples/pubsubs/go-channel/go.sum",
    "chars": 1616,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/pubsubs/go-channel/main.go",
    "chars": 1082,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "_examples/pubsubs/googlecloud/.validate_example.yml",
    "chars": 127,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"payload: Hello, w"
  },
  {
    "path": "_examples/pubsubs/googlecloud/docker-compose.yml",
    "chars": 536,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - googlecloud\n    volumes:\n"
  },
  {
    "path": "_examples/pubsubs/googlecloud/go.mod",
    "chars": 2146,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-googlecloud/v2 "
  },
  {
    "path": "_examples/pubsubs/googlecloud/go.sum",
    "chars": 17979,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.121.6 h1:waZiu"
  },
  {
    "path": "_examples/pubsubs/googlecloud/main.go",
    "chars": 1728,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "_examples/pubsubs/kafka/.validate_example.yml",
    "chars": 127,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"payload: Hello, w"
  },
  {
    "path": "_examples/pubsubs/kafka/docker-compose.yml",
    "chars": 740,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - kafka\n    volumes:\n      "
  },
  {
    "path": "_examples/pubsubs/kafka/go.mod",
    "chars": 1654,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/IBM/sarama v1.46.0\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDot"
  },
  {
    "path": "_examples/pubsubs/kafka/go.sum",
    "chars": 11418,
    "preview": "github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=\ngithub.com/IBM/sarama v1.46.0/go.mod h1:0l"
  },
  {
    "path": "_examples/pubsubs/kafka/main.go",
    "chars": 1771,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "_examples/pubsubs/nats-core/.validate_example.yml",
    "chars": 127,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"payload: Hello, w"
  },
  {
    "path": "_examples/pubsubs/nats-core/docker-compose.yml",
    "chars": 396,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - nats\n    volumes:\n      -"
  },
  {
    "path": "_examples/pubsubs/nats-core/go.mod",
    "chars": 578,
    "preview": "module main\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-nats/v2 v"
  },
  {
    "path": "_examples/pubsubs/nats-core/go.sum",
    "chars": 2817,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/pubsubs/nats-core/main.go",
    "chars": 2030,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/waterm"
  },
  {
    "path": "_examples/pubsubs/nats-jetstream/.validate_example.yml",
    "chars": 127,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"payload: Hello, w"
  },
  {
    "path": "_examples/pubsubs/nats-jetstream/docker-compose.yml",
    "chars": 396,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - nats\n    volumes:\n      -"
  },
  {
    "path": "_examples/pubsubs/nats-jetstream/go.mod",
    "chars": 578,
    "preview": "module main\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-nats/v2 v"
  },
  {
    "path": "_examples/pubsubs/nats-jetstream/go.sum",
    "chars": 2817,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/pubsubs/nats-jetstream/main.go",
    "chars": 2223,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/waterm"
  },
  {
    "path": "_examples/pubsubs/nats-streaming/.validate_example.yml",
    "chars": 127,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"payload: Hello, w"
  },
  {
    "path": "_examples/pubsubs/nats-streaming/docker-compose.yml",
    "chars": 304,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - nats-streaming\n    volume"
  },
  {
    "path": "_examples/pubsubs/nats-streaming/go.mod",
    "chars": 990,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-nats v1.0.7\n\tgi"
  },
  {
    "path": "_examples/pubsubs/nats-streaming/go.sum",
    "chars": 14564,
    "preview": "github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=\ngithub.com/Thre"
  },
  {
    "path": "_examples/pubsubs/nats-streaming/main.go",
    "chars": 1944,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\tstan \"gith"
  },
  {
    "path": "_examples/pubsubs/redisstream/.validate_example.yml",
    "chars": 127,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"payload: Hello, w"
  },
  {
    "path": "_examples/pubsubs/redisstream/docker-compose.yml",
    "chars": 301,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - redis\n    volumes:\n      "
  },
  {
    "path": "_examples/pubsubs/redisstream/go.mod",
    "chars": 814,
    "preview": "module main.go\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-rediss"
  },
  {
    "path": "_examples/pubsubs/redisstream/go.sum",
    "chars": 7508,
    "preview": "github.com/Rican7/retry v0.3.1 h1:scY4IbO8swckzoA/11HgBwaZRJEyY9vaNJshcdhp1Mc=\ngithub.com/Rican7/retry v0.3.1/go.mod h1:"
  },
  {
    "path": "_examples/pubsubs/redisstream/main.go",
    "chars": 1733,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "_examples/pubsubs/sql/.validate_example.yml",
    "chars": 118,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"Hello, world!\"\n"
  },
  {
    "path": "_examples/pubsubs/sql/docker-compose.yml",
    "chars": 392,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - mysql\n    volumes:\n      "
  },
  {
    "path": "_examples/pubsubs/sql/go.mod",
    "chars": 794,
    "preview": "module main.go\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-sql/v4"
  },
  {
    "path": "_examples/pubsubs/sql/go.sum",
    "chars": 4448,
    "preview": "filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:"
  },
  {
    "path": "_examples/pubsubs/sql/main.go",
    "chars": 1885,
    "preview": "// Sources for https://watermill.io/learn/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\tstdSQL \"database/sql\"\n\t\"lo"
  },
  {
    "path": "_examples/pubsubs/sqlite/.gitignore",
    "chars": 11,
    "preview": "db.sqlite3*"
  },
  {
    "path": "_examples/pubsubs/sqlite/.validate_example.yml",
    "chars": 80,
    "preview": "validation_cmd: \"go run .\"\ntimeout: 30\nexpected_stdout:\n  - \"received message:\"\n"
  },
  {
    "path": "_examples/pubsubs/sqlite/go.mod",
    "chars": 918,
    "preview": "module sqlite\n\ngo 1.24.0\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-sqlit"
  },
  {
    "path": "_examples/pubsubs/sqlite/go.sum",
    "chars": 4300,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/pubsubs/sqlite/main.go",
    "chars": 1911,
    "preview": "// Sources for https://watermill.io/docs/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"log\"\n\t\"tim"
  },
  {
    "path": "_examples/pubsubs/sqlite/transaction.go",
    "chars": 906,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"github.com/ThreeDotsLabs/wate"
  },
  {
    "path": "_examples/pubsubs/sqlite-zombiezen/.gitignore",
    "chars": 11,
    "preview": "db.sqlite3*"
  },
  {
    "path": "_examples/pubsubs/sqlite-zombiezen/.validate_example.yml",
    "chars": 85,
    "preview": "validation_cmd: \"go run main.go\"\ntimeout: 30\nexpected_stdout:\n  - \"received message:\""
  },
  {
    "path": "_examples/pubsubs/sqlite-zombiezen/go.mod",
    "chars": 952,
    "preview": "module sqlite\n\ngo 1.24.0\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-sqlit"
  },
  {
    "path": "_examples/pubsubs/sqlite-zombiezen/go.sum",
    "chars": 8866,
    "preview": "github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=\ngithub.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/pubsubs/sqlite-zombiezen/main.go",
    "chars": 1769,
    "preview": "// Sources for https://watermill.io/docs/getting-started/\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com"
  },
  {
    "path": "_examples/pubsubs/sqlite-zombiezen/transaction.go",
    "chars": 1039,
    "preview": "package main\n\nimport (\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"github.com/ThreeDotsLabs/watermill-sqlite/wmsqlitezombiez"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/README.md",
    "chars": 1133,
    "preview": "# Interactive Consumer Groups Example (Routing Events)\n\nThis example shows how Customer Groups work, i.e. how to decide "
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/api/http.go",
    "chars": 3251,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/api/main.go",
    "chars": 1476,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"github.com/ThreeDotsLabs/"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/api/public/index.html",
    "chars": 42372,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n    <title>Watermill Consumer Groups Example</title>\n    <meta name=\"viewport\" c"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/api/storage.go",
    "chars": 753,
    "preview": "package main\n\nimport (\n\t\"sync\"\n\n\t\"github.com/ThreeDotsLabs/watermill-routing-example/server/common\"\n)\n\ntype storage stru"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/common/events.go",
    "chars": 362,
    "preview": "package common\n\ntype UserSignedUp struct {\n\tUserID   string   `json:\"id\"`\n\tConsents Consents `json:\"consents\"`\n}\n\ntype C"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/common/messaging.go",
    "chars": 1024,
    "preview": "package common\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"github.com/ThreeDotsLabs/w"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/crm-service/main.go",
    "chars": 3994,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"gith"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/docker-compose.yml",
    "chars": 1157,
    "preview": "services:\n  api:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - redis\n    volumes:\n      - ."
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/go.mod",
    "chars": 1240,
    "preview": "module github.com/ThreeDotsLabs/watermill-routing-example/server\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/go.sum",
    "chars": 16048,
    "preview": "github.com/Rican7/retry v0.3.1 h1:scY4IbO8swckzoA/11HgBwaZRJEyY9vaNJshcdhp1Mc=\ngithub.com/Rican7/retry v0.3.1/go.mod h1:"
  },
  {
    "path": "_examples/real-world-examples/consumer-groups/newsletter-service/main.go",
    "chars": 8571,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"gith"
  },
  {
    "path": "_examples/real-world-examples/delayed-messages/docker-compose.yml",
    "chars": 477,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    volumes:\n      - .:/app\n      - $GOPATH/pkg/m"
  },
  {
    "path": "_examples/real-world-examples/delayed-messages/go.mod",
    "chars": 1256,
    "preview": "module delayed-messages\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermi"
  },
  {
    "path": "_examples/real-world-examples/delayed-messages/go.sum",
    "chars": 12469,
    "preview": "github.com/Rican7/retry v0.3.1 h1:scY4IbO8swckzoA/11HgBwaZRJEyY9vaNJshcdhp1Mc=\ngithub.com/Rican7/retry v0.3.1/go.mod h1:"
  },
  {
    "path": "_examples/real-world-examples/delayed-messages/main.go",
    "chars": 5357,
    "preview": "package main\n\nimport (\n\t\"context\"\n\tstdSQL \"database/sql\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/brianvoe/gofakeit/v6\"\n\t"
  },
  {
    "path": "_examples/real-world-examples/delayed-requeue/docker-compose.yml",
    "chars": 477,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    volumes:\n      - .:/app\n      - $GOPATH/pkg/m"
  },
  {
    "path": "_examples/real-world-examples/delayed-requeue/go.mod",
    "chars": 1318,
    "preview": "module delayed-requeue\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermil"
  },
  {
    "path": "_examples/real-world-examples/delayed-requeue/go.sum",
    "chars": 12634,
    "preview": "filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:"
  },
  {
    "path": "_examples/real-world-examples/delayed-requeue/main.go",
    "chars": 4290,
    "preview": "package main\n\nimport (\n\t\"context\"\n\tstdSQL \"database/sql\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/watermi"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/README.md",
    "chars": 2610,
    "preview": "# Exactly-once delivery counter\n\nIs exactly-once delivery impossible? Well, it depends a lot on the definition of exactl"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/docker-compose.yml",
    "chars": 644,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    ports:\n      - 8080:8080\n    volumes:\n      -"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/run.go",
    "chars": 3175,
    "preview": "package main\n\nimport (\n\tstdSQL \"database/sql\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os/exec\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cheggaaa/pb/v3"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/schema.sql",
    "chars": 85,
    "preview": "CREATE TABLE counter (\n    id VARCHAR(36) NOT NULL UNIQUE,\n    value int NOT NULL\n);\n"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/server/go.mod",
    "chars": 841,
    "preview": "module exactly-once-delivery\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/wa"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/server/go.sum",
    "chars": 4615,
    "preview": "filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/server/main.go",
    "chars": 1659,
    "preview": "package main\n\nimport (\n\tstdSQL \"database/sql\"\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/ThreeDotsLabs/watermill\""
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/worker/go.mod",
    "chars": 796,
    "preview": "module exactly-once-delivery\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/wa"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/worker/go.sum",
    "chars": 4448,
    "preview": "filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:"
  },
  {
    "path": "_examples/real-world-examples/exactly-once-delivery-counter/worker/main.go",
    "chars": 3060,
    "preview": "package main\n\nimport (\n\t\"context\"\n\tstdSQL \"database/sql\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/Th"
  },
  {
    "path": "_examples/real-world-examples/persistent-event-log/.validate_example.yml",
    "chars": 119,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"received event\"\n"
  },
  {
    "path": "_examples/real-world-examples/persistent-event-log/README.md",
    "chars": 3012,
    "preview": "# Persistent Event Log (Google Cloud Pub/Sub to MySQL)\n\nThis example shows how to use the SQL Publisher from [SQL Pub/Su"
  },
  {
    "path": "_examples/real-world-examples/persistent-event-log/docker-compose.yml",
    "chars": 822,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    depends_on:\n      - mysql\n      - googlecloud"
  },
  {
    "path": "_examples/real-world-examples/persistent-event-log/go.mod",
    "chars": 2586,
    "preview": "module main.go\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-google"
  },
  {
    "path": "_examples/real-world-examples/persistent-event-log/go.sum",
    "chars": 19951,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.121.6 h1:waZiu"
  },
  {
    "path": "_examples/real-world-examples/persistent-event-log/main.go",
    "chars": 2789,
    "preview": "package main\n\nimport (\n\t\"context\"\n\tstdSQL \"database/sql\"\n\t\"encoding/json\"\n\t\"log\"\n\t\"time\"\n\n\tdriver \"github.com/go-sql-dri"
  },
  {
    "path": "_examples/real-world-examples/receiving-webhooks/.validate_example.yml",
    "chars": 121,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"Starting handler\""
  },
  {
    "path": "_examples/real-world-examples/receiving-webhooks/README.md",
    "chars": 424,
    "preview": "# Receiving webhooks (HTTP to Kafka)\n\nThis example showcases the use of the **HTTP Subscriber** to receive webhooks with"
  },
  {
    "path": "_examples/real-world-examples/receiving-webhooks/docker-compose.yml",
    "chars": 790,
    "preview": "services:\n  golang:\n    image: golang:1.25\n    restart: unless-stopped\n    ports:\n    - 8080:8080\n    depends_on:\n    - "
  },
  {
    "path": "_examples/real-world-examples/receiving-webhooks/go.mod",
    "chars": 1951,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-http v1.1.4\n\tgi"
  },
  {
    "path": "_examples/real-world-examples/receiving-webhooks/go.sum",
    "chars": 17415,
    "preview": "github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=\ngithub.com/IBM/sarama v1.46.0/go.mod h1:0l"
  },
  {
    "path": "_examples/real-world-examples/receiving-webhooks/main.go",
    "chars": 2401,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\tstdHttp \"net/http\"\n\t_ \"net/http/pprof"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/.validate_example.yml",
    "chars": 130,
    "preview": "validation_cmd: \"docker compose up\"\nteardown_cmd: \"docker compose down\"\ntimeout: 180\nexpected_output: \"POST /foo_or_bar:"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/README.md",
    "chars": 1376,
    "preview": "# Sending webhooks (Kafka to HTTP)\n\nThis example showcases the use of the **HTTP Publisher** to call webhooks with HTTP "
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/docker-compose.yml",
    "chars": 1187,
    "preview": "services:\n  webhooks-server:\n    image: golang:1.25\n    restart: unless-stopped\n    volumes:\n    - .:/app\n    - $GOPATH/"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/producer/go.mod",
    "chars": 1666,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.0"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/producer/go.sum",
    "chars": 11418,
    "preview": "github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=\ngithub.com/IBM/sarama v1.46.0/go.mod h1:0l"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/producer/main.go",
    "chars": 975,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"github.com/ThreeDotsLabs/wat"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/router/go.mod",
    "chars": 1854,
    "preview": "module main.go\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-http v1.1.4\n\tgi"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/router/go.sum",
    "chars": 17067,
    "preview": "github.com/IBM/sarama v1.46.0 h1:+YTM1fNd6WKMchlnLKRUB5Z0qD4M8YbvwIIPLvJD53s=\ngithub.com/IBM/sarama v1.46.0/go.mod h1:0l"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/router/main.go",
    "chars": 1912,
    "preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\twatermill_http \"github.com/ThreeDotsLabs/water"
  },
  {
    "path": "_examples/real-world-examples/sending-webhooks/webhooks-server/main.go",
    "chars": 533,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"time\"\n)\n\n// handler receives the webhook requests and logs them"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/README.md",
    "chars": 5082,
    "preview": "# HTTP Server push using SSE (Server-Sent Events)\n\nThis example is a Twitter-like web application using [Server-Sent Eve"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/docker-compose.yml",
    "chars": 706,
    "preview": "services:\n  server:\n    image: golang:1.25\n    restart: unless-stopped\n    ports:\n      - 8080:8080\n    volumes:\n      -"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/schema.sql",
    "chars": 188,
    "preview": "CREATE TABLE example.posts (\n    id VARCHAR(36) NOT NULL PRIMARY KEY,\n    title VARCHAR(255) NOT NULL DEFAULT '',\n    co"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/event_handlers.go",
    "chars": 4057,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n\t\"github.com/ThreeDots"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/feeds_storage.go",
    "chars": 3611,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"go.mongodb.org/mongo-driver/bson\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\t\"go"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/go.mod",
    "chars": 1857,
    "preview": "module main.go\n\ngo 1.25\n\nrequire (\n\tgithub.com/ThreeDotsLabs/watermill v1.5.1\n\tgithub.com/ThreeDotsLabs/watermill-nats v"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/go.sum",
    "chars": 18126,
    "preview": "filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/http.go",
    "chars": 7978,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bria"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/main.go",
    "chars": 581,
    "preview": "package main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/ThreeDotsLabs/watermill\"\n)\n\nfunc main() {\n\tlogger := watermill.NewStdLo"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/models.go",
    "chars": 1600,
    "preview": "package main\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Note that in a real application using both \"json\" and \"bson\" t"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/posts_storage.go",
    "chars": 1421,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/go-sql-driver/mysql\"\n)\n\ntype PostsStorage"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events/server/public/index.html",
    "chars": 11484,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n    <title>Watermill Server-Sent Events Example</title>\n    <meta charset=\"utf-8"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events-htmx/Dockerfile",
    "chars": 219,
    "preview": "FROM golang:1.25 AS builder\n\nCOPY . /src\nWORKDIR /src/\n\nRUN CGO_ENABLED=0 go build -ldflags=\"-s -w\" -trimpath -o /main ."
  },
  {
    "path": "_examples/real-world-examples/server-sent-events-htmx/README.md",
    "chars": 179,
    "preview": "# Server Sent Events (htmx)\n\nThis is an example project described in [Live website updates with Go, SSE, and htmx](https"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events-htmx/docker/Dockerfile",
    "chars": 192,
    "preview": "FROM golang:1.25\n\nRUN go install github.com/cespare/reflex@latest\nRUN go install github.com/a-h/templ/cmd/templ@latest\n\n"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events-htmx/docker/reflex.conf",
    "chars": 63,
    "preview": "-r '(\\.go$|go\\.mod$)' -s go run .\n-r '\\.templ$' templ generate\n"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events-htmx/docker-compose.yml",
    "chars": 806,
    "preview": "services:\n  server:\n    build:\n      context: docker\n    volumes:\n      - ./:/src\n      - go_pkg:/go/pkg\n      - go_cach"
  },
  {
    "path": "_examples/real-world-examples/server-sent-events-htmx/events.go",
    "chars": 4718,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"cloud.google.com/go/pubsub/v2/apiv1/pubsubpb\"\n\t\"github.com/ThreeDots"
  }
]

// ... and 295 more files (download for full content)

About this extraction

This page contains the full source code of the ThreeDotsLabs/watermill GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 495 files (2.1 MB), approximately 578.4k tokens, and a symbol index with 1538 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!