{"id":47978710,"url":"https://github.com/zoobz-io/herald","last_synced_at":"2026-04-04T10:59:36.686Z","repository":{"id":328279115,"uuid":"1112776042","full_name":"zoobz-io/herald","owner":"zoobz-io","description":"Bidirectional bindings between capitan events and message brokers","archived":false,"fork":false,"pushed_at":"2026-03-29T04:07:51.000Z","size":324,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-04T10:59:32.079Z","etag":null,"topics":["events","go","golang","kafka","messaging","nats","pubsub","zoobzio"],"latest_commit_sha":null,"homepage":"https://herald.zoobz.io","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zoobz-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-09T04:48:57.000Z","updated_at":"2026-03-29T04:02:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zoobz-io/herald","commit_stats":null,"previous_names":["zoobzio/herald","zoobz-io/herald"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/zoobz-io/herald","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoobz-io%2Fherald","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoobz-io%2Fherald/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoobz-io%2Fherald/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoobz-io%2Fherald/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zoobz-io","download_url":"https://codeload.github.com/zoobz-io/herald/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoobz-io%2Fherald/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31397056,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["events","go","golang","kafka","messaging","nats","pubsub","zoobzio"],"created_at":"2026-04-04T10:59:33.906Z","updated_at":"2026-04-04T10:59:36.661Z","avatar_url":"https://github.com/zoobz-io.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# herald\n\n[![CI Status](https://github.com/zoobz-io/herald/workflows/CI/badge.svg)](https://github.com/zoobz-io/herald/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/zoobz-io/herald/graph/badge.svg?branch=main)](https://codecov.io/gh/zoobz-io/herald)\n[![Go Report Card](https://goreportcard.com/badge/github.com/zoobz-io/herald)](https://goreportcard.com/report/github.com/zoobz-io/herald)\n[![CodeQL](https://github.com/zoobz-io/herald/workflows/CodeQL/badge.svg)](https://github.com/zoobz-io/herald/security/code-scanning)\n[![Go Reference](https://pkg.go.dev/badge/github.com/zoobz-io/herald.svg)](https://pkg.go.dev/github.com/zoobz-io/herald)\n[![License](https://img.shields.io/github/license/zoobz-io/herald)](LICENSE)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/zoobz-io/herald)](go.mod)\n[![Release](https://img.shields.io/github/v/release/zoobz-io/herald)](https://github.com/zoobz-io/herald/releases)\n\nBidirectional bindings between [capitan](https://github.com/zoobz-io/capitan) events and message brokers.\n\nEmit a [capitan](https://github.com/zoobz-io/capitan) event, herald publishes it. Herald receives a message, capitan emits it. Same types, same signals, automatic serialization.\n\n## Bidirectional Event Distribution\n\n```go\n// capitan → broker: Publish events to external systems\npub := herald.NewPublisher(provider, signal, key, nil)\npub.Start()\n\n// broker → capitan: Subscribe to external messages as events\nsub := herald.NewSubscriber(provider, signal, key, nil)\nsub.Start(ctx)\n```\n\nOne provider, one signal, one key. Herald handles serialization, acknowledgment, and error routing.\n\n## Installation\n\n```bash\ngo get github.com/zoobz-io/herald\n```\n\nRequires Go 1.23+.\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n\n    kafkago \"github.com/segmentio/kafka-go\"\n    \"github.com/zoobz-io/capitan\"\n    \"github.com/zoobz-io/herald\"\n    \"github.com/zoobz-io/herald/kafka\"\n)\n\ntype Order struct {\n    ID    string  `json:\"id\"`\n    Total float64 `json:\"total\"`\n}\n\nfunc main() {\n    ctx := context.Background()\n\n    // Define signal and typed key\n    orderCreated := capitan.NewSignal(\"order.created\", \"New order\")\n    orderKey := capitan.NewKey[Order](\"order\", \"app.Order\")\n\n    // Create Kafka provider\n    writer := \u0026kafkago.Writer{Addr: kafkago.TCP(\"localhost:9092\"), Topic: \"orders\"}\n    reader := kafkago.NewReader(kafkago.ReaderConfig{\n        Brokers: []string{\"localhost:9092\"},\n        Topic:   \"orders\",\n        GroupID: \"order-processor\",\n    })\n    provider := kafka.New(\"orders\", kafka.WithWriter(writer), kafka.WithReader(reader))\n    defer provider.Close()\n\n    // Publish: capitan events → Kafka\n    pub := herald.NewPublisher(provider, orderCreated, orderKey, nil)\n    pub.Start()\n    defer pub.Close()\n\n    // Subscribe: Kafka → capitan events\n    sub := herald.NewSubscriber(provider, orderCreated, orderKey, nil)\n    sub.Start(ctx)\n    defer sub.Close()\n\n    // Handle incoming messages with standard capitan hooks\n    capitan.Hook(orderCreated, func(ctx context.Context, e *capitan.Event) {\n        order, _ := orderKey.From(e)\n        fmt.Printf(\"Received order: %s\\n\", order.ID)\n    })\n\n    // Emit an event — automatically published to Kafka\n    capitan.Emit(ctx, orderCreated, orderKey.Field(Order{ID: \"ORD-123\", Total: 99.99}))\n\n    capitan.Shutdown()\n}\n```\n\n## Capabilities\n\n| Feature              | Description                                                                                         | Docs                                                                                     |\n| -------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| Bidirectional Flow   | Publish capitan events to brokers or subscribe broker messages as events                            | [Publishing](docs/2.learn/1.publishing.md), [Subscribing](docs/2.learn/2.subscribing.md) |\n| Type-Safe Generics   | Compile-time checked publishers and subscribers                                                     | [Overview](docs/1.overview.md)                                                           |\n| 11 Providers         | Kafka, NATS, JetStream, Pub/Sub, Redis, SQS, AMQP, SNS, Bolt, Firestore, io                         | [Providers](docs/2.learn/3.providers.md)                                                 |\n| Pipeline Middleware  | Validation, transformation, and side effects via processors                                         | [Reliability](docs/3.guides/1.reliability.md)                                            |\n| Reliability Patterns | Retry, backoff, timeout, circuit breaker, rate limiting via [pipz](https://github.com/zoobz-io/pipz) | [Reliability](docs/3.guides/1.reliability.md)                                            |\n| Auto Acknowledgment  | Messages acked/nacked based on processing outcome                                                   | [Subscribing](docs/2.learn/2.subscribing.md)                                             |\n| Custom Codecs        | Pluggable serialization (JSON default, custom supported)                                            | [Codecs](docs/3.guides/2.codecs.md)                                                      |\n| Error Observability  | All errors emit as [capitan](https://github.com/zoobz-io/capitan) events                             | [Error Handling](docs/3.guides/3.errors.md)                                              |\n\n## Why herald?\n\n- **Type-safe** — Generic publishers and subscribers with compile-time checking\n- **Bidirectional** — Publish to brokers or subscribe from brokers\n- **11 providers** — Kafka, NATS, JetStream, Pub/Sub, Redis, SQS, RabbitMQ, SNS, BoltDB, Firestore, io\n- **Reliable** — Pipeline middleware for retry, backoff, timeout, circuit breaker, rate limiting\n- **Observable** — Errors flow through [capitan](https://github.com/zoobz-io/capitan)\n\n## Unified Event Flow\n\nHerald enables a pattern: **internal events become external messages, external messages become internal events**.\n\nYour application emits [capitan](https://github.com/zoobz-io/capitan) events as usual. Herald publishes them to any broker. Other services publish to brokers. Herald subscribes and emits them as capitan events. Same signals, same keys, same hooks — the boundary between internal and external disappears.\n\n```go\n// Service A: emit locally, publish externally\ncapitan.Emit(ctx, orderCreated, orderKey.Field(order))\n\n// Service B: subscribe externally, handle locally\ncapitan.Hook(orderCreated, processOrder)\n```\n\nTwo services, one event type, zero coupling. The broker is just a transport.\n\n## Providers\n\n| Provider       | Package                  | Use Case                          |\n| -------------- | ------------------------ | --------------------------------- |\n| Kafka          | [`kafka`](kafka)         | High-throughput streaming         |\n| NATS           | [`nats`](nats)           | Lightweight cloud messaging       |\n| JetStream      | [`jetstream`](jetstream) | NATS with persistence and headers |\n| Google Pub/Sub | [`pubsub`](pubsub)       | GCP managed messaging             |\n| Redis Streams  | [`redis`](redis)         | In-memory with persistence        |\n| AWS SQS        | [`sqs`](sqs)             | AWS managed queues                |\n| RabbitMQ/AMQP  | [`amqp`](amqp)           | Traditional message broker        |\n| AWS SNS        | [`sns`](sns)             | Pub/sub fanout                    |\n| BoltDB         | [`bolt`](bolt)           | Embedded local queues             |\n| Firestore      | [`firestore`](firestore) | Firebase/GCP document store       |\n| io             | [`io`](io)               | Testing with io.Reader/Writer     |\n\n## Processing Hooks\n\nAdd processing steps via middleware processors:\n\n```go\npub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithMiddleware(\n        herald.UseApply[Order](\"validate\", func(ctx context.Context, env *herald.Envelope[Order]) (*herald.Envelope[Order], error) {\n            if env.Value.Total \u003c 0 {\n                return env, errors.New(\"invalid total\")\n            }\n            return env, nil\n        }),\n        herald.UseEffect[Order](\"log\", func(ctx context.Context, env *herald.Envelope[Order]) error {\n            log.Printf(\"order %s\", env.Value.ID)\n            return nil\n        }),\n        herald.UseTransform[Order](\"enrich\", func(ctx context.Context, env *herald.Envelope[Order]) *herald.Envelope[Order] {\n            env.Value.ProcessedAt = time.Now()\n            return env\n        }),\n    ),\n})\n```\n\n- `UseApply` — Transform envelope with possible error\n- `UseEffect` — Side effect, envelope passes through unchanged\n- `UseTransform` — Pure transform, cannot fail\n\n## Pipeline Options\n\nAdd reliability features via [pipz](https://github.com/zoobz-io/pipz):\n\n```go\npub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRetry[Order](3),\n    herald.WithBackoff[Order](3, 100*time.Millisecond),\n    herald.WithTimeout[Order](5*time.Second),\n    herald.WithCircuitBreaker[Order](5, 30*time.Second),\n    herald.WithRateLimit[Order](100, 10),\n})\n```\n\nSee [Reliability Guide](docs/3.guides/1.reliability.md) for middleware and pipeline details.\n\n## Acknowledgment\n\nHerald handles message acknowledgment automatically:\n\n| Outcome                        | Action                                     |\n| ------------------------------ | ------------------------------------------ |\n| Message processed successfully | `Ack()` — Message acknowledged             |\n| Deserialization fails          | `Nack()` — Message returned for redelivery |\n| Provider doesn't support ack   | No-op (e.g., NATS core, SNS)               |\n\n## Error Handling\n\nAll errors flow through capitan:\n\n```go\ncapitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n    log.Printf(\"[herald] %s: %v\", err.Operation, err.Err)\n})\n```\n\nSee [Error Handling Guide](docs/3.guides/3.errors.md) for details.\n\n## Documentation\n\nFull documentation is available in the [docs/](docs/) directory:\n\n### Learn\n\n- [Overview](docs/1.overview.md) — Architecture and philosophy\n- [Publishing](docs/2.learn/1.publishing.md) — Forward capitan events to brokers\n- [Subscribing](docs/2.learn/2.subscribing.md) — Consume broker messages as capitan events\n- [Providers](docs/2.learn/3.providers.md) — Available broker implementations\n\n### Guides\n\n- [Reliability](docs/3.guides/1.reliability.md) — Retry, backoff, circuit breaker, rate limiting\n- [Codecs](docs/3.guides/2.codecs.md) — Custom serialization formats\n- [Error Handling](docs/3.guides/3.errors.md) — Centralized error management\n- [Testing](docs/3.guides/4.testing.md) — Testing herald-based applications\n\n### Reference\n\n- [API Reference](docs/4.reference/1.api.md) — Complete function and type documentation\n- [Providers Reference](docs/4.reference/2.providers.md) — Provider configuration details\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n## License\n\nMIT License — see [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoobz-io%2Fherald","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzoobz-io%2Fherald","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoobz-io%2Fherald/lists"}