[{"data":1,"prerenderedAt":6827},["ShallowReactive",2],{"search-sections-herald":3,"nav-herald":1193,"content-tree-herald":1239,"footer-resources":1260,"content-/v1.0.5/guides/errors":4665,"surround-/v1.0.5/guides/errors":6824},[4,10,14,20,25,30,35,41,46,51,56,59,64,69,74,79,84,89,94,98,103,108,113,118,123,128,133,138,143,148,153,158,163,168,173,177,181,185,189,194,199,204,209,214,219,224,229,234,239,244,249,254,259,263,268,273,278,283,287,291,295,299,304,309,314,319,324,327,332,337,342,347,352,357,362,367,372,377,382,386,391,396,401,406,411,416,420,424,429,434,438,443,448,452,457,462,467,472,476,481,485,489,494,499,503,508,513,518,523,527,532,537,542,547,552,556,561,566,571,576,580,585,590,594,599,604,608,613,618,622,627,632,637,642,647,652,657,662,667,671,676,680,684,689,694,699,703,708,711,716,720,725,730,734,738,742,746,750,754,758,763,768,773,778,782,786,790,793,798,803,808,813,817,821,826,831,835,840,845,849,854,859,863,867,872,877,881,885,888,893,897,902,907,912,917,922,927,932,936,941,946,950,954,958,963,968,973,978,983,988,993,998,1002,1007,1011,1016,1021,1026,1030,1035,1039,1044,1049,1054,1059,1064,1068,1072,1077,1080,1084,1089,1093,1097,1102,1106,1110,1115,1119,1123,1128,1132,1136,1141,1145,1149,1154,1158,1162,1167,1171,1175,1180,1184,1188],{"id":5,"title":6,"titles":7,"content":8,"level":9},"/v1.0.5/overview","Overview",[],"Bidirectional bindings between capitan events and message brokers",1,{"id":11,"title":6,"titles":12,"content":13,"level":9},"/v1.0.5/overview#overview",[],"Bridging in-process events to distributed systems typically means writing broker-specific code, managing serialization, and wiring up error handling—repeatedly, for each broker. Herald provides a unified abstraction: publish capitan events to any broker, subscribe from any broker to capitan events. // Publish capitan events to Kafka\npub := herald.NewPublisher(kafkaProvider, orderCreated, orderKey, nil)\npub.Start()\n\n// Subscribe from Redis to capitan events\nsub := herald.NewSubscriber(redisProvider, paymentReceived, paymentKey, nil)\nsub.Start(ctx) Type-safe, bidirectional, with 12 providers out of the box.",{"id":15,"title":16,"titles":17,"content":18,"level":19},"/v1.0.5/overview#architecture","Architecture",[6],"┌─────────────────────────────────────────────────────────────────┐\n│                         Your Application                        │\n│                                                                 │\n│  ┌─────────────┐              ┌─────────────┐                   │\n│  │   Service   │──Emit───────▶│   Capitan   │                   │\n│  │      A      │              │   (events)  │                   │\n│  └─────────────┘              └──────┬──────┘                   │\n│                                      │                          │\n│                              ┌───────▼───────┐                  │\n│                              │   Publisher   │                  │\n│                              │   (herald)    │                  │\n│                              └───────┬───────┘                  │\n└──────────────────────────────────────┼──────────────────────────┘\n                                       │\n                               ┌───────▼───────┐\n                               │    Provider   │\n                               │ (kafka/redis/ │\n                               │  nats/sqs...) │\n                               └───────┬───────┘\n                                       │\n                               ┌───────▼───────┐\n                               │    Broker     │\n                               │  (external)   │\n                               └───────────────┘ Publishers observe capitan signals and forward events to brokers. Subscribers consume from brokers and emit to capitan. The provider abstraction handles broker-specific details.",2,{"id":21,"title":22,"titles":23,"content":24,"level":19},"/v1.0.5/overview#philosophy","Philosophy",[6],"Herald extends capitan's unified event stream to distributed systems. Just as capitan enables cross-cutting concerns within a process, herald enables cross-cutting concerns across processes and services. // Service A: Emit order events locally\ncapitan.Emit(ctx, orderCreated, orderKey.Field(order))\n\n// Herald: Bridge to Kafka\npub := herald.NewPublisher(kafkaProvider, orderCreated, orderKey, nil)\n\n// Service B: Receive from Kafka via herald\nsub := herald.NewSubscriber(kafkaProvider, orderCreated, orderKey, nil)\n\n// Service B: Handle locally\ncapitan.Hook(orderCreated, processOrder) Three services, one event contract, zero direct dependencies between them.",{"id":26,"title":27,"titles":28,"content":29,"level":19},"/v1.0.5/overview#capabilities","Capabilities",[6],"Herald bridges capitan to external systems: Publishing — Forward capitan events to any supported broker with automatic serialization and metadata propagation. Subscribing — Consume broker messages and emit them as capitan events, with automatic deserialization and acknowledgment. Providers — 11 built-in providers for Kafka, NATS, JetStream, Pub/Sub, Redis, SQS, RabbitMQ, SNS, BoltDB, Firestore, and io. Reliability — Pipeline options for retry, backoff, timeout, circuit breaker, and rate limiting. Herald handles the bridging. What you build across your distributed system is up to you.",{"id":31,"title":32,"titles":33,"content":34,"level":19},"/v1.0.5/overview#priorities","Priorities",[6],"",{"id":36,"title":37,"titles":38,"content":39,"level":40},"/v1.0.5/overview#type-safety","Type Safety",[6,32],"Messages are typed at compile time through capitan keys: type Order struct {\n    ID    string  `json:\"id\"`\n    Total float64 `json:\"total\"`\n}\n\nvar orderKey = capitan.NewKey[Order](\"order\", \"app.Order\")\n\n// Type-safe publishing\npub := herald.NewPublisher[Order](provider, signal, orderKey, nil)\n\n// Type-safe subscribing\nsub := herald.NewSubscriber[Order](provider, signal, orderKey, nil) No runtime type assertions, no interface{} juggling.",3,{"id":42,"title":43,"titles":44,"content":45,"level":40},"/v1.0.5/overview#reliability","Reliability",[6,32],"Events flow through composable pipelines: pub := 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}) Failed publishes can retry with backoff. Circuit breakers prevent cascade failures. Rate limiters protect downstream systems.",{"id":47,"title":48,"titles":49,"content":50,"level":40},"/v1.0.5/overview#observability","Observability",[6,32],"All errors flow through capitan's event system: capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n    log.Printf(\"[herald] %s failed: %v\", err.Operation, err.Err)\n}) Monitor publishing failures, deserialization errors, and acknowledgment issues through the same event infrastructure you use for everything else. html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":52,"title":53,"titles":54,"content":55,"level":9},"/v1.0.5/learn/quickstart","Quickstart",[],"Get started with herald in minutes",{"id":57,"title":53,"titles":58,"content":34,"level":9},"/v1.0.5/learn/quickstart#quickstart",[],{"id":60,"title":61,"titles":62,"content":63,"level":19},"/v1.0.5/learn/quickstart#requirements","Requirements",[53],"Go 1.24 or later.",{"id":65,"title":66,"titles":67,"content":68,"level":19},"/v1.0.5/learn/quickstart#installation","Installation",[53],"go get github.com/zoobz-io/herald",{"id":70,"title":71,"titles":72,"content":73,"level":19},"/v1.0.5/learn/quickstart#basic-usage","Basic Usage",[53],"package 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\nvar (\n    orderCreated = capitan.NewSignal(\"order.created\", \"New order placed\")\n    orderKey     = capitan.NewKey[Order](\"order\", \"app.Order\")\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    // Create provider\n    writer := &kafkago.Writer{\n        Addr:  kafkago.TCP(\"localhost:9092\"),\n        Topic: \"orders\",\n    }\n    provider := kafka.New(\"orders\", kafka.WithWriter(writer))\n    defer provider.Close()\n\n    // Publish capitan events to Kafka\n    pub := herald.NewPublisher(provider, orderCreated, orderKey, nil)\n    pub.Start()\n    defer pub.Close()\n\n    // Emit an event - automatically published\n    capitan.Emit(ctx, orderCreated, orderKey.Field(Order{\n        ID:    \"ORD-123\",\n        Total: 99.99,\n    }))\n\n    capitan.Shutdown()\n}",{"id":75,"title":76,"titles":77,"content":78,"level":19},"/v1.0.5/learn/quickstart#whats-happening","What's Happening",[53],"NewSignal and NewKey define the event contract (from capitan)kafka.New creates a provider for Kafka communicationNewPublisher bridges capitan events to the brokerpub.Start() registers a capitan listener that forwards eventscapitan.Emit queues an event — herald publishes it to Kafkacapitan.Shutdown drains pending events before exit",{"id":80,"title":81,"titles":82,"content":83,"level":19},"/v1.0.5/learn/quickstart#subscribing","Subscribing",[53],"The reverse direction — broker to capitan: // Create provider with reader\nreader := kafkago.NewReader(kafkago.ReaderConfig{\n    Brokers: []string{\"localhost:9092\"},\n    Topic:   \"orders\",\n    GroupID: \"order-processor\",\n})\nprovider := kafka.New(\"orders\", kafka.WithReader(reader))\ndefer provider.Close()\n\n// Hook listener first\ncapitan.Hook(orderCreated, func(ctx context.Context, e *capitan.Event) {\n    order, _ := orderKey.From(e)\n    fmt.Printf(\"Received: %s\\n\", order.ID)\n})\n\n// Subscribe: broker messages become capitan events\nsub := herald.NewSubscriber(provider, orderCreated, orderKey, nil)\nsub.Start(ctx)\ndefer sub.Close() Messages are deserialized, emitted as capitan events, and acknowledged on success.",{"id":85,"title":86,"titles":87,"content":88,"level":19},"/v1.0.5/learn/quickstart#next-steps","Next Steps",[53],"Concepts — Publishers, subscribers, providers, and pipelinesArchitecture — Internal design and data flow html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}",{"id":90,"title":91,"titles":92,"content":93,"level":9},"/v1.0.5/learn/concepts","Core Concepts",[],"Publishers, subscribers, providers, and pipelines in herald",{"id":95,"title":91,"titles":96,"content":97,"level":9},"/v1.0.5/learn/concepts#core-concepts",[],"Herald has five primitives: publishers, subscribers, providers, envelopes, and pipelines. Understanding these unlocks the full API.",{"id":99,"title":100,"titles":101,"content":102,"level":19},"/v1.0.5/learn/concepts#publishers","Publishers",[91],"A publisher observes a capitan signal and forwards events to a message broker. pub := herald.NewPublisher(provider, orderCreated, orderKey, nil)\npub.Start() When Start() is called, the publisher registers as a capitan listener. Every event emitted to that signal is serialized and published to the broker. // This event automatically flows to Kafka/Redis/NATS/etc.\ncapitan.Emit(ctx, orderCreated, orderKey.Field(order))",{"id":104,"title":105,"titles":106,"content":107,"level":40},"/v1.0.5/learn/concepts#publisher-lifecycle","Publisher Lifecycle",[91,100],"pub := herald.NewPublisher(provider, signal, key, opts)\npub.Start()   // Register listener, begin forwarding\n// ... emit events ...\npub.Close()   // Unregister listener, cleanup Always call Close() to release resources.",{"id":109,"title":110,"titles":111,"content":112,"level":19},"/v1.0.5/learn/concepts#subscribers","Subscribers",[91],"A subscriber consumes messages from a broker and emits them as capitan events. sub := herald.NewSubscriber(provider, orderCreated, orderKey, nil)\nsub.Start(ctx) When Start(ctx) is called, the subscriber spawns a goroutine that reads from the broker. Each message is deserialized and emitted as a capitan event. // Messages from the broker arrive here\ncapitan.Hook(orderCreated, func(ctx context.Context, e *capitan.Event) {\n    order, _ := orderKey.From(e)\n    // process order\n})",{"id":114,"title":115,"titles":116,"content":117,"level":40},"/v1.0.5/learn/concepts#subscriber-lifecycle","Subscriber Lifecycle",[91,110],"ctx, cancel := context.WithCancel(context.Background())\nsub := herald.NewSubscriber(provider, signal, key, opts)\nsub.Start(ctx)  // Spawn consumer goroutine\n// ... process messages ...\ncancel()        // Signal stop\nsub.Close()     // Wait for cleanup Context cancellation stops message consumption gracefully.",{"id":119,"title":120,"titles":121,"content":122,"level":19},"/v1.0.5/learn/concepts#providers","Providers",[91],"A provider implements broker-specific communication. Herald includes 11 providers: ProviderPackageUse CaseKafkakafkaHigh-throughput streamingNATSnatsLightweight cloud messagingJetStreamjetstreamNATS with persistenceGoogle Pub/SubpubsubGCP managed messagingRedis StreamsredisIn-memory with persistenceAWS SQSsqsAWS managed queuesRabbitMQ/AMQPamqpTraditional message brokerAWS SNSsnsPub/sub fanoutBoltDBboltEmbedded local queuesFirestorefirestoreFirebase/GCP document storeioioTesting with io.Reader/Writer",{"id":124,"title":125,"titles":126,"content":127,"level":40},"/v1.0.5/learn/concepts#provider-interface","Provider Interface",[91,120],"All providers implement: type Provider interface {\n    Publish(ctx context.Context, data []byte, metadata Metadata) error\n    Subscribe(ctx context.Context) \u003C-chan Result[Message]\n    Ping(ctx context.Context) error\n    Close() error\n} Publishers call Publish. Subscribers read from Subscribe. The interface abstracts broker differences.",{"id":129,"title":130,"titles":131,"content":132,"level":40},"/v1.0.5/learn/concepts#message-structure","Message Structure",[91,120],"type Message struct {\n    Data     []byte\n    Metadata Metadata\n    Ack      func() error\n    Nack     func() error\n} Data: Raw message bytesMetadata: Headers/attributes (maps to broker-native headers)Ack: Acknowledge successful processingNack: Signal processing failure (triggers redelivery)",{"id":134,"title":135,"titles":136,"content":137,"level":19},"/v1.0.5/learn/concepts#envelopes","Envelopes",[91],"An envelope wraps a typed value with metadata for pipeline processing: type Envelope[T any] struct {\n    Value    T\n    Metadata Metadata\n} Middleware operates on envelopes, allowing transformation and header injection before the terminal operation (publish/emit). herald.UseTransform[Order](\"add-trace\", func(_ context.Context, env *herald.Envelope[Order]) *herald.Envelope[Order] {\n    env.Metadata[\"trace-id\"] = generateTraceID()\n    return env\n})",{"id":139,"title":140,"titles":141,"content":142,"level":19},"/v1.0.5/learn/concepts#pipelines","Pipelines",[91],"Pipelines compose middleware around a terminal operation. Herald uses pipz for pipeline construction.",{"id":144,"title":145,"titles":146,"content":147,"level":40},"/v1.0.5/learn/concepts#pipeline-options","Pipeline Options",[91,140],"Add reliability features: opts := []herald.Option[Order]{\n    herald.WithRetry[Order](3),                          // Retry up to 3 times\n    herald.WithBackoff[Order](3, 100*time.Millisecond),  // Exponential backoff\n    herald.WithTimeout[Order](5*time.Second),            // Per-operation timeout\n    herald.WithCircuitBreaker[Order](5, 30*time.Second), // Fail-fast on errors\n    herald.WithRateLimit[Order](100, 10),                // 100 ops/sec, burst 10\n}\n\npub := herald.NewPublisher(provider, signal, key, opts) Options wrap the terminal in order. The final pipeline structure: Rate Limit → Circuit Breaker → Timeout → Backoff → Retry → Terminal (publish/emit)",{"id":149,"title":150,"titles":151,"content":152,"level":40},"/v1.0.5/learn/concepts#middleware","Middleware",[91,140],"Add custom processing with middleware: 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 nil, 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(\"Processing 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.Metadata[\"processed-at\"] = time.Now().Format(time.RFC3339)\n        return env\n    }),\n) ProcessorPurposeUseApplyTransform with possible errorUseEffectSide effect, envelope unchangedUseTransformPure transform, cannot fail",{"id":154,"title":155,"titles":156,"content":157,"level":19},"/v1.0.5/learn/concepts#codecs","Codecs",[91],"Codecs handle serialization between typed values and bytes: type Codec interface {\n    Marshal(v any) ([]byte, error)\n    Unmarshal(data []byte, v any) error\n    ContentType() string\n} Herald defaults to JSON. Use custom codecs for other formats: pub := herald.NewPublisher(provider, signal, key, opts,\n    herald.WithPublisherCodec[Order](myProtobufCodec))\n\nsub := herald.NewSubscriber(provider, signal, key, opts,\n    herald.WithSubscriberCodec[Order](myProtobufCodec))",{"id":159,"title":160,"titles":161,"content":162,"level":19},"/v1.0.5/learn/concepts#acknowledgment","Acknowledgment",[91],"Herald handles acknowledgment automatically: OutcomeActionSuccessful processingAck() calledDeserialization failureNack() calledPipeline failureNack() called Each provider maps ack/nack to broker-appropriate behavior: ProviderAckNackKafkaCommit offsetDon't commitJetStreammsg.Ack()msg.Nak()Pub/Submsg.Ack()msg.Nack()SQSDelete messageVisibility timeoutAMQPAck(false)Nack(false, true)",{"id":164,"title":165,"titles":166,"content":167,"level":19},"/v1.0.5/learn/concepts#error-handling","Error Handling",[91],"All errors flow through capitan's event system: capitan.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}) The error struct includes context: type Error struct {\n    Operation string  // \"publish\", \"subscribe\", \"unmarshal\", \"ack\", \"nack\"\n    Signal    string  // Signal name\n    Err       error   // Underlying error\n    Raw       []byte  // Raw payload (for unmarshal errors)\n}",{"id":169,"title":170,"titles":171,"content":172,"level":19},"/v1.0.5/learn/concepts#metadata","Metadata",[91],"Metadata flows through the entire pipeline: Publishing: Middleware can add headers → Provider maps to broker headers herald.UseTransform[Order](\"headers\", func(_ context.Context, env *herald.Envelope[Order]) *herald.Envelope[Order] {\n    env.Metadata[\"correlation-id\"] = correlationID\n    return env\n}) Subscribing: Broker headers → Metadata → Available in capitan event capitan.Hook(signal, func(ctx context.Context, e *capitan.Event) {\n    meta, _ := herald.MetadataKey.From(e)\n    correlationID := meta[\"correlation-id\"]\n})",{"id":174,"title":86,"titles":175,"content":176,"level":19},"/v1.0.5/learn/concepts#next-steps",[91],"Architecture — Internal design and data flowPublishing Guide — Detailed publishing patternsSubscribing Guide — Detailed subscribing patterns html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":178,"title":16,"titles":179,"content":180,"level":9},"/v1.0.5/learn/architecture",[],"Internal design, pipelines, and data flow in herald",{"id":182,"title":16,"titles":183,"content":184,"level":9},"/v1.0.5/learn/architecture#architecture",[],"Understanding herald's internals helps you reason about data flow, error handling, and extension points.",{"id":186,"title":6,"titles":187,"content":188,"level":19},"/v1.0.5/learn/architecture#overview",[16],"┌─────────────────────────────────────────────────────────────────────────┐\n│                              Herald                                      │\n│                                                                          │\n│  ┌──────────────────────────────┐    ┌──────────────────────────────┐   │\n│  │         Publisher            │    │         Subscriber           │   │\n│  │                              │    │                              │   │\n│  │  ┌────────┐    ┌──────────┐  │    │  ┌──────────┐    ┌────────┐  │   │\n│  │  │Capitan │───▶│ Pipeline │  │    │  │ Pipeline │───▶│Capitan │  │   │\n│  │  │Listener│    │          │  │    │  │          │    │  Emit  │  │   │\n│  │  └────────┘    └────┬─────┘  │    │  └────┬─────┘    └────────┘  │   │\n│  │                     │        │    │       │                      │   │\n│  │              ┌──────▼─────┐  │    │  ┌────▼───────┐              │   │\n│  │              │  Terminal  │  │    │  │  Terminal  │              │   │\n│  │              │ (publish)  │  │    │  │  (emit)    │              │   │\n│  │              └──────┬─────┘  │    │  └────┬───────┘              │   │\n│  └─────────────────────┼────────┘    └───────┼──────────────────────┘   │\n│                        │                     │                          │\n│                        ▼                     ▲                          │\n│  ┌──────────────────────────────────────────────────────────────────┐   │\n│  │                          Provider                                 │   │\n│  │                                                                   │   │\n│  │   Publish(ctx, data, metadata)      Subscribe(ctx) \u003C-chan Result │   │\n│  │                                                                   │   │\n│  └──────────────────────────────────────────────────────────────────┘   │\n└─────────────────────────────────────────────────────────────────────────┘\n                                    │\n                                    ▼\n                        ┌───────────────────────┐\n                        │    Message Broker     │\n                        │  (Kafka/NATS/Redis/   │\n                        │   SQS/Pub/Sub/...)    │\n                        └───────────────────────┘",{"id":190,"title":191,"titles":192,"content":193,"level":19},"/v1.0.5/learn/architecture#publishing-flow","Publishing Flow",[16],"When a capitan event is emitted, the publisher processes it through a pipeline: capitan.Emit(ctx, signal, key.Field(value))\n        │\n        ▼\n┌───────────────────┐\n│  Capitan Worker   │  (async dispatch)\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│ Publisher Listener│  (registered via pub.Start())\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│  Extract Value    │  key.From(event) → typed value\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│  Wrap in Envelope │  Envelope{Value: T, Metadata: {}}\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────────────────────────────────────┐\n│                    Pipeline                        │\n│                                                    │\n│  ┌─────────────┐                                   │\n│  │ Rate Limit  │ ─── ops/sec + burst control       │\n│  └──────┬──────┘                                   │\n│         │                                          │\n│  ┌──────▼──────┐                                   │\n│  │   Circuit   │ ─── fail-fast on repeated errors  │\n│  │   Breaker   │                                   │\n│  └──────┬──────┘                                   │\n│         │                                          │\n│  ┌──────▼──────┐                                   │\n│  │   Timeout   │ ─── per-operation deadline        │\n│  └──────┬──────┘                                   │\n│         │                                          │\n│  ┌──────▼──────┐                                   │\n│  │   Backoff   │ ─── exponential retry delays      │\n│  └──────┬──────┘                                   │\n│         │                                          │\n│  ┌──────▼──────┐                                   │\n│  │    Retry    │ ─── immediate retries             │\n│  └──────┬──────┘                                   │\n│         │                                          │\n│  ┌──────▼──────┐                                   │\n│  │ Middleware  │ ─── transform/effect/apply        │\n│  └──────┬──────┘                                   │\n│         │                                          │\n│  ┌──────▼──────┐                                   │\n│  │  Terminal   │ ─── serialize + provider.Publish  │\n│  └─────────────┘                                   │\n└───────────────────────────────────────────────────┘\n         │\n         ▼\n┌───────────────────┐\n│ provider.Publish  │  (broker-specific)\n└───────────────────┘",{"id":195,"title":196,"titles":197,"content":198,"level":40},"/v1.0.5/learn/architecture#terminal-publish","Terminal (Publish)",[16,191],"The publish terminal: Marshals the envelope value using the codecCalls provider.Publish(ctx, data, envelope.Metadata)Returns error or nil func (p *Publisher[T]) terminal(ctx context.Context, env *Envelope[T]) error {\n    data, err := p.codec.Marshal(env.Value)\n    if err != nil {\n        return err\n    }\n    return p.provider.Publish(ctx, data, env.Metadata)\n}",{"id":200,"title":201,"titles":202,"content":203,"level":19},"/v1.0.5/learn/architecture#subscribing-flow","Subscribing Flow",[16],"The subscriber consumes from the provider and emits to capitan: ┌───────────────────┐\n│ provider.Subscribe│  (returns \u003C-chan Result[Message])\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│  Subscriber Loop  │  (goroutine from sub.Start)\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│  Result Check     │  ─── error? emit to ErrorSignal, continue\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│   Unmarshal       │  codec.Unmarshal(msg.Data, &value)\n└────────┬──────────┘\n         │  error? → Nack + emit to ErrorSignal\n         ▼\n┌───────────────────┐\n│  Wrap in Envelope │  Envelope{Value: T, Metadata: msg.Metadata}\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────────────────────────────────────┐\n│                    Pipeline                        │\n│         (same structure as publishing)             │\n│                                                    │\n│  Rate Limit → Circuit Breaker → Timeout →          │\n│  Backoff → Retry → Middleware → Terminal           │\n└────────┬──────────────────────────────────────────┘\n         │\n         ▼\n┌───────────────────┐\n│    Terminal       │  capitan.Emit(ctx, signal, fields...)\n└────────┬──────────┘\n         │  success? → Ack\n         │  error?   → Nack\n         ▼\n┌───────────────────┐\n│  Capitan Event    │  (handlers receive via Hook)\n└───────────────────┘",{"id":205,"title":206,"titles":207,"content":208,"level":40},"/v1.0.5/learn/architecture#terminal-subscribe","Terminal (Subscribe)",[16,201],"The subscribe terminal: Emits the typed value and metadata as capitan fieldsReturns error or nil (determines ack/nack) func (s *Subscriber[T]) terminal(ctx context.Context, env *Envelope[T]) error {\n    s.cap.Emit(ctx, s.signal,\n        s.key.Field(env.Value),\n        MetadataKey.Field(env.Metadata),\n    )\n    return nil\n}",{"id":210,"title":211,"titles":212,"content":213,"level":19},"/v1.0.5/learn/architecture#pipeline-construction","Pipeline Construction",[16],"Herald uses pipz for pipeline construction. Each option wraps the terminal: // Options applied in order\nopts := []herald.Option[Order]{\n    herald.WithRetry[Order](3),      // Wraps terminal\n    herald.WithTimeout[Order](5*s),  // Wraps retry\n    herald.WithRateLimit[Order](100, 10), // Wraps timeout\n} The final pipeline structure (innermost to outermost): Terminal\n  └── Retry\n       └── Timeout\n            └── RateLimit Execution flows outside-in: RateLimit → Timeout → Retry → Terminal.",{"id":215,"title":216,"titles":217,"content":218,"level":40},"/v1.0.5/learn/architecture#option-implementation","Option Implementation",[16,211],"Each option returns a function that wraps the pipeline: func WithRetry[T any](attempts int) Option[T] {\n    return func(p pipz.Pipeline[*Envelope[T]]) pipz.Pipeline[*Envelope[T]] {\n        return pipz.Retry(p, attempts)\n    }\n}",{"id":220,"title":221,"titles":222,"content":223,"level":19},"/v1.0.5/learn/architecture#provider-abstraction","Provider Abstraction",[16],"Providers abstract broker differences behind a minimal interface: type Provider interface {\n    Publish(ctx context.Context, data []byte, metadata Metadata) error\n    Subscribe(ctx context.Context) \u003C-chan Result[Message]\n    Ping(ctx context.Context) error\n    Close() error\n}",{"id":225,"title":226,"titles":227,"content":228,"level":40},"/v1.0.5/learn/architecture#metadata-mapping","Metadata Mapping",[16,221],"Each provider maps Metadata (a map[string]string) to broker-native headers: ProviderPublishSubscribeKafkakafka.Headerkafka.HeaderJetStreamnats.Headernats.HeaderPub/Subpubsub.Attributespubsub.AttributesSQSMessageAttributesMessageAttributesAMQPamqp.Table (Headers)amqp.Table",{"id":230,"title":231,"titles":232,"content":233,"level":40},"/v1.0.5/learn/architecture#acknowledgment-mapping","Acknowledgment Mapping",[16,221],"Each provider implements broker-appropriate ack/nack: type Message struct {\n    Data     []byte\n    Metadata Metadata\n    Ack      func() error  // Success acknowledgment\n    Nack     func() error  // Failure acknowledgment\n} Example (Kafka): Ack: func() error {\n    return reader.CommitMessages(ctx, kafkaMsg)\n}\nNack: func() error {\n    // Don't commit - message redelivered on next fetch\n    return nil\n}",{"id":235,"title":236,"titles":237,"content":238,"level":19},"/v1.0.5/learn/architecture#error-flow","Error Flow",[16],"All errors emit to herald.ErrorSignal: ┌───────────────────┐\n│   Error Occurs    │  (marshal, publish, unmarshal, ack, nack)\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│  Create Error     │  herald.Error{Operation, Signal, Err, Raw}\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│  Emit to Capitan  │  capitan.Emit(ctx, ErrorSignal, ErrorKey.Field(err))\n└────────┬──────────┘\n         │\n         ▼\n┌───────────────────┐\n│  Error Handlers   │  (registered via capitan.Hook)\n└───────────────────┘ Error operations: publish: Provider publish failedunmarshal: Codec unmarshal failedack: Message acknowledgment failednack: Message nack failedsubscribe: Provider subscribe returned error",{"id":240,"title":241,"titles":242,"content":243,"level":19},"/v1.0.5/learn/architecture#thread-safety","Thread Safety",[16],"Herald components are thread-safe: ComponentSafety ModelPublisherSingle capitan listener, no shared stateSubscriberSingle goroutine consumerProviderImplementation-dependent (typically thread-safe)PipelineStateless transformations Publishers can run concurrently for different signals. Multiple subscribers can consume from different providers. The underlying capitan instance handles listener registration thread-safely.",{"id":245,"title":246,"titles":247,"content":248,"level":19},"/v1.0.5/learn/architecture#graceful-shutdown","Graceful Shutdown",[16],"Proper shutdown order prevents message loss:",{"id":250,"title":251,"titles":252,"content":253,"level":40},"/v1.0.5/learn/architecture#publisher-shutdown","Publisher Shutdown",[16,246],"capitan.Shutdown()  // 1. Drain pending events (ensures all reach publisher)\npub.Close()         // 2. Unregister listener\nprovider.Close()    // 3. Close broker connection",{"id":255,"title":256,"titles":257,"content":258,"level":40},"/v1.0.5/learn/architecture#subscriber-shutdown","Subscriber Shutdown",[16,246],"cancel()            // 1. Signal consumer to stop\nsub.Close()         // 2. Wait for in-flight message to complete\ncapitan.Shutdown()  // 3. Drain any queued capitan events\nprovider.Close()    // 4. Close broker connection The subscriber tracks in-flight messages. Close() blocks until the current message completes its pipeline (including ack/nack).",{"id":260,"title":261,"titles":262,"content":34,"level":19},"/v1.0.5/learn/architecture#design-decisions","Design Decisions",[16],{"id":264,"title":265,"titles":266,"content":267,"level":40},"/v1.0.5/learn/architecture#why-pipelines","Why Pipelines?",[16,261],"Pipelines provide composable reliability without coupling concerns: Retry logic doesn't know about rate limitingCircuit breakers don't know about timeoutsEach concern is testable in isolation",{"id":269,"title":270,"titles":271,"content":272,"level":40},"/v1.0.5/learn/architecture#why-envelopes","Why Envelopes?",[16,261],"Envelopes carry metadata alongside typed values: Middleware can inject headers without knowing the value typeMetadata flows end-to-end without type assertionsClean separation between payload and transport concerns",{"id":274,"title":275,"titles":276,"content":277,"level":40},"/v1.0.5/learn/architecture#why-error-signals","Why Error Signals?",[16,261],"Emitting errors to capitan enables: Centralized error handling across all publishers/subscribersSame observability patterns as application eventsIntegration with existing capitan infrastructure (logging, metrics)",{"id":279,"title":280,"titles":281,"content":282,"level":40},"/v1.0.5/learn/architecture#why-separate-providers","Why Separate Providers?",[16,261],"Each broker has distinct semantics: Kafka has partitions and consumer groupsNATS has subjects and queue groupsSQS has visibility timeouts and dead letter queues A single abstraction would either lose features or become unwieldy. Providers encapsulate broker-specific configuration while exposing a uniform runtime interface.",{"id":284,"title":86,"titles":285,"content":286,"level":19},"/v1.0.5/learn/architecture#next-steps",[16],"Publishing Guide — Detailed publishing patternsSubscribing Guide — Detailed subscribing patternsProviders Reference — Provider configuration details html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}",{"id":288,"title":43,"titles":289,"content":290,"level":9},"/v1.0.5/guides/reliability",[],"Pipeline options for resilient message bridging",{"id":292,"title":43,"titles":293,"content":294,"level":9},"/v1.0.5/guides/reliability#reliability",[],"Herald provides composable pipeline options for building resilient message bridges. These options wrap the publish/subscribe operations with retry logic, timeouts, circuit breakers, and rate limiting.",{"id":296,"title":150,"titles":297,"content":298,"level":19},"/v1.0.5/guides/reliability#middleware",[43],"Herald exposes pipz primitives for adding processing steps via WithMiddleware. Middleware operates on *Envelope[T], providing access to both the value and metadata.",{"id":300,"title":301,"titles":302,"content":303,"level":40},"/v1.0.5/guides/reliability#useapply","UseApply",[43,150],"Transform data with possible failure: pub := 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    ),\n})",{"id":305,"title":306,"titles":307,"content":308,"level":40},"/v1.0.5/guides/reliability#useeffect","UseEffect",[43,150],"Side effects without modifying data: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithMiddleware(\n        herald.UseEffect[Order](\"log\", func(ctx context.Context, env *herald.Envelope[Order]) error {\n            log.Printf(\"processing order %s\", env.Value.ID)\n            return nil\n        }),\n    ),\n})",{"id":310,"title":311,"titles":312,"content":313,"level":40},"/v1.0.5/guides/reliability#usetransform","UseTransform",[43,150],"Pure transformation that cannot fail: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithMiddleware(\n        herald.UseTransform[Order](\"timestamp\", func(ctx context.Context, env *herald.Envelope[Order]) *herald.Envelope[Order] {\n            env.Value.ProcessedAt = time.Now()\n            return env\n        }),\n    ),\n})",{"id":315,"title":316,"titles":317,"content":318,"level":40},"/v1.0.5/guides/reliability#error-behavior","Error Behavior",[43,150],"Return an error to abort processing: Publisher: Error prevents publish, emits to ErrorSignalSubscriber: Error triggers Nack(), emits to ErrorSignal",{"id":320,"title":321,"titles":322,"content":323,"level":40},"/v1.0.5/guides/reliability#execution-order","Execution Order",[43,150],"Middleware executes in order, then the terminal: opts := []herald.Option[Order]{\n    herald.WithMiddleware(\n        herald.UseEffect[Order](\"first\", firstFn),   // runs first\n        herald.UseEffect[Order](\"second\", secondFn), // runs second\n    ),\n} Execution order: first → second → terminal",{"id":325,"title":145,"titles":326,"content":34,"level":19},"/v1.0.5/guides/reliability#pipeline-options",[43],{"id":328,"title":329,"titles":330,"content":331,"level":40},"/v1.0.5/guides/reliability#retry","Retry",[43,145],"Retry failed operations immediately: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRetry[Order](3), // Retry up to 3 times\n}) Use for transient failures like network blips.",{"id":333,"title":334,"titles":335,"content":336,"level":40},"/v1.0.5/guides/reliability#backoff","Backoff",[43,145],"Retry with exponential backoff: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithBackoff[Order](3, 100*time.Millisecond),\n    // Delays: 100ms, 200ms, 400ms\n}) Use when the downstream system needs recovery time.",{"id":338,"title":339,"titles":340,"content":341,"level":40},"/v1.0.5/guides/reliability#timeout","Timeout",[43,145],"Cancel operations that take too long: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithTimeout[Order](5 * time.Second),\n}) Prevents hanging on slow brokers.",{"id":343,"title":344,"titles":345,"content":346,"level":40},"/v1.0.5/guides/reliability#circuit-breaker","Circuit Breaker",[43,145],"Stop attempting operations after repeated failures: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithCircuitBreaker[Order](5, 30*time.Second),\n    // Opens after 5 failures, recovers after 30s\n}) Prevents cascade failures when a broker is down.",{"id":348,"title":349,"titles":350,"content":351,"level":40},"/v1.0.5/guides/reliability#rate-limit","Rate Limit",[43,145],"Limit operations per second: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRateLimit[Order](100, 10),\n    // 100 ops/sec with burst of 10\n}) Protects downstream systems from overload.",{"id":353,"title":354,"titles":355,"content":356,"level":40},"/v1.0.5/guides/reliability#error-handler","Error Handler",[43,145],"Custom error handling via pipz chainable: import \"github.com/zoobz-io/pipz\"\n\nerrorLogger := pipz.Effect(\"log-error\", func(ctx context.Context, err *pipz.Error[*herald.Envelope[Order]]) error {\n    log.Printf(\"publish failed: %v (order: %s)\", err.Err, err.InputData.Value.ID)\n    metrics.IncrementPublishError()\n    return nil\n})\n\npub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithErrorHandler[Order](errorLogger),\n}) The error handler observes errors without modifying the error flow — the original error still propagates after the handler processes it.",{"id":358,"title":359,"titles":360,"content":361,"level":40},"/v1.0.5/guides/reliability#custom-pipeline","Custom Pipeline",[43,145],"Full control over the pipeline: import \"github.com/zoobz-io/pipz\"\n\ncustomPipeline := pipz.NewSequence(\"custom\",\n    pipz.NewRetry[*herald.Envelope[Order]](\"retry\", nil, 3),\n    pipz.NewTimeout[*herald.Envelope[Order]](\"timeout\", nil, 5*time.Second),\n)\n\npub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithPipeline[Order](customPipeline),\n})",{"id":363,"title":364,"titles":365,"content":366,"level":19},"/v1.0.5/guides/reliability#combining-options","Combining Options",[43],"Options are applied in order, wrapping inside-out: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRateLimit[Order](100, 10),    // Outermost: rate limit first\n    herald.WithCircuitBreaker[Order](5, 30*time.Second),\n    herald.WithBackoff[Order](3, 100*time.Millisecond),\n    herald.WithTimeout[Order](5*time.Second), // Innermost: timeout per attempt\n}) Execution order: Rate limit (wait if needed)Check circuit breaker (fail fast if open)Attempt with backoff (retry on failure)Each attempt has 5s timeout",{"id":368,"title":369,"titles":370,"content":371,"level":40},"/v1.0.5/guides/reliability#timing-example","Timing Example",[43,364],"With the configuration above, a failing publish follows this timeline: t=0ms      Rate limit check (pass)\nt=0ms      Circuit breaker check (closed, pass)\nt=0ms      Attempt 1 starts\nt=5000ms   Attempt 1 times out\nt=5000ms   Backoff delay: 100ms\nt=5100ms   Attempt 2 starts\nt=10100ms  Attempt 2 times out\nt=10100ms  Backoff delay: 200ms (exponential)\nt=10300ms  Attempt 3 starts\nt=15300ms  Attempt 3 times out\nt=15300ms  All retries exhausted, error emitted Total worst-case duration: ~15.3 seconds (3 attempts × 5s timeout + backoff delays)",{"id":373,"title":374,"titles":375,"content":376,"level":40},"/v1.0.5/guides/reliability#backoff-vs-retry","Backoff vs Retry",[43,364],"WithRetry retries immediately; WithBackoff retries with exponential delays: // Immediate retry - good for transient network blips\nherald.WithRetry[Order](3)\n// Timeline: attempt1 → fail → attempt2 → fail → attempt3\n\n// Backoff retry - good when downstream needs recovery time\nherald.WithBackoff[Order](3, 100*time.Millisecond)\n// Timeline: attempt1 → fail → 100ms → attempt2 → fail → 200ms → attempt3 Use backoff when the failure is likely due to load or temporary unavailability. Use immediate retry for random packet loss or brief disconnections.",{"id":378,"title":379,"titles":380,"content":381,"level":40},"/v1.0.5/guides/reliability#rate-limit-circuit-breaker","Rate Limit + Circuit Breaker",[43,364],"When combined, rate limiting applies before the circuit breaker check: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRateLimit[Order](100, 10),\n    herald.WithCircuitBreaker[Order](5, 30*time.Second),\n}) Behavior: Rate limit controls throughput (100 ops/sec, burst of 10)Circuit breaker tracks failures from operations that pass the rate limitIf the circuit opens, requests fail fast without consuming rate limit tokensWhen the circuit recovers, rate limiting resumes normally This combination protects both directions: rate limiting prevents overwhelming a healthy downstream, while circuit breaker stops hammering a failing one.",{"id":383,"title":384,"titles":385,"content":34,"level":19},"/v1.0.5/guides/reliability#recommended-patterns","Recommended Patterns",[43],{"id":387,"title":388,"titles":389,"content":390,"level":40},"/v1.0.5/guides/reliability#high-availability-publishing","High-Availability Publishing",[43,384],"pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithBackoff[Order](5, 200*time.Millisecond),\n    herald.WithTimeout[Order](10*time.Second),\n    herald.WithCircuitBreaker[Order](10, time.Minute),\n})",{"id":392,"title":393,"titles":394,"content":395,"level":40},"/v1.0.5/guides/reliability#rate-limited-subscription","Rate-Limited Subscription",[43,384],"sub := herald.NewSubscriber(provider, signal, key, []herald.Option[Order]{\n    herald.WithRateLimit[Order](50, 5),\n    herald.WithTimeout[Order](30*time.Second),\n})",{"id":397,"title":398,"titles":399,"content":400,"level":40},"/v1.0.5/guides/reliability#fail-fast-for-critical-paths","Fail-Fast for Critical Paths",[43,384],"pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithTimeout[Order](time.Second),\n    // No retry - fail immediately for latency-sensitive operations\n})",{"id":402,"title":403,"titles":404,"content":405,"level":19},"/v1.0.5/guides/reliability#error-propagation","Error Propagation",[43],"When all retries are exhausted or the circuit is open, errors propagate to capitan: capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    switch {\n    case errors.Is(err.Err, pipz.ErrCircuitOpen):\n        alertOps(\"Circuit breaker open for \" + err.Signal)\n    case errors.Is(err.Err, context.DeadlineExceeded):\n        log.Printf(\"Timeout publishing to %s\", err.Signal)\n    default:\n        log.Printf(\"Publish failed: %v\", err.Err)\n    }\n})",{"id":407,"title":408,"titles":409,"content":410,"level":19},"/v1.0.5/guides/reliability#performance-considerations","Performance Considerations",[43],"Pipeline options add minimal overhead: Retry/Backoff: Only active on failuresTimeout: Single context wrapperCircuit Breaker: Atomic counter checkRate Limit: Token bucket algorithm For maximum throughput, use options sparingly: // High-throughput path: minimal options\npub := herald.NewPublisher(provider, signal, key, nil)\n\n// Critical path: full protection\npub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRetry[Order](3),\n    herald.WithTimeout[Order](5*time.Second),\n})",{"id":412,"title":413,"titles":414,"content":415,"level":19},"/v1.0.5/guides/reliability#integration-with-pipz","Integration with pipz",[43],"Herald's options are powered by pipz. For advanced use cases, use pipz directly: import \"github.com/zoobz-io/pipz\"\n\n// Build a custom pipeline with pipz primitives\nfallback := pipz.Effect[*herald.Envelope[Order]](\"fallback\", func(ctx context.Context, env *herald.Envelope[Order]) error {\n    return fallbackPublish(ctx, env)\n})\n\npipeline := pipz.NewSequence(\"custom\",\n    pipz.NewRetry[*herald.Envelope[Order]](\"retry\", nil, 3),\n    pipz.NewFallback[*herald.Envelope[Order]](\"fallback\", nil, fallback),\n)\n\npub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithPipeline[Order](pipeline),\n}) html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":417,"title":155,"titles":418,"content":419,"level":9},"/v1.0.5/guides/codecs",[],"Custom serialization formats",{"id":421,"title":155,"titles":422,"content":423,"level":9},"/v1.0.5/guides/codecs#codecs",[],"Codecs handle message serialization and deserialization. Herald uses JSON by default, but supports custom codecs for alternative formats.",{"id":425,"title":426,"titles":427,"content":428,"level":19},"/v1.0.5/guides/codecs#codec-interface","Codec Interface",[155],"type Codec interface {\n    Marshal(v any) ([]byte, error)\n    Unmarshal(data []byte, v any) error\n    ContentType() string\n}",{"id":430,"title":431,"titles":432,"content":433,"level":19},"/v1.0.5/guides/codecs#default-json-codec","Default JSON Codec",[155],"Herald provides a built-in JSON codec: type JSONCodec struct{}\n\nfunc (JSONCodec) Marshal(v any) ([]byte, error) {\n    return json.Marshal(v)\n}\n\nfunc (JSONCodec) Unmarshal(data []byte, v any) error {\n    return json.Unmarshal(data, v)\n}\n\nfunc (JSONCodec) ContentType() string {\n    return \"application/json\"\n}",{"id":435,"title":436,"titles":437,"content":34,"level":19},"/v1.0.5/guides/codecs#custom-codec-example","Custom Codec Example",[155],{"id":439,"title":440,"titles":441,"content":442,"level":40},"/v1.0.5/guides/codecs#protocol-buffers","Protocol Buffers",[155,436],"type ProtobufCodec struct{}\n\nfunc (ProtobufCodec) Marshal(v any) ([]byte, error) {\n    msg, ok := v.(proto.Message)\n    if !ok {\n        return nil, fmt.Errorf(\"value must implement proto.Message\")\n    }\n    return proto.Marshal(msg)\n}\n\nfunc (ProtobufCodec) Unmarshal(data []byte, v any) error {\n    msg, ok := v.(proto.Message)\n    if !ok {\n        return fmt.Errorf(\"value must implement proto.Message\")\n    }\n    return proto.Unmarshal(data, msg)\n}\n\nfunc (ProtobufCodec) ContentType() string {\n    return \"application/protobuf\"\n}",{"id":444,"title":445,"titles":446,"content":447,"level":40},"/v1.0.5/guides/codecs#messagepack","MessagePack",[155,436],"import \"github.com/vmihailenco/msgpack/v5\"\n\ntype MsgpackCodec struct{}\n\nfunc (MsgpackCodec) Marshal(v any) ([]byte, error) {\n    return msgpack.Marshal(v)\n}\n\nfunc (MsgpackCodec) Unmarshal(data []byte, v any) error {\n    return msgpack.Unmarshal(data, v)\n}\n\nfunc (MsgpackCodec) ContentType() string {\n    return \"application/msgpack\"\n}",{"id":449,"title":450,"titles":451,"content":34,"level":19},"/v1.0.5/guides/codecs#using-custom-codecs","Using Custom Codecs",[155],{"id":453,"title":454,"titles":455,"content":456,"level":40},"/v1.0.5/guides/codecs#with-publisher","With Publisher",[155,450],"pub := herald.NewPublisher(provider, signal, key, nil,\n    herald.WithPublisherCodec[Order](ProtobufCodec{}))",{"id":458,"title":459,"titles":460,"content":461,"level":40},"/v1.0.5/guides/codecs#with-subscriber","With Subscriber",[155,450],"sub := herald.NewSubscriber(provider, signal, key, nil,\n    herald.WithSubscriberCodec[Order](ProtobufCodec{}))",{"id":463,"title":464,"titles":465,"content":466,"level":19},"/v1.0.5/guides/codecs#content-type-header","Content-Type Header",[155],"The codec's ContentType() is automatically added to message metadata: // Publishing with JSON codec\n// Metadata includes: {\"Content-Type\": \"application/json\"}\n\n// Publishing with custom codec\n// Metadata includes: {\"Content-Type\": \"application/protobuf\"} Existing Content-Type in metadata is not overwritten: // Middleware that sets Content-Type before the terminal\nopts := []herald.Option[Order]{\n    herald.WithMiddleware(\n        herald.UseTransform[Order](\"set-ct\", func(_ context.Context, env *herald.Envelope[Order]) *herald.Envelope[Order] {\n            env.Metadata[\"Content-Type\"] = \"application/x-custom\"\n            return env\n        }),\n    ),\n}\n// This Content-Type is preserved by the publish terminal",{"id":468,"title":469,"titles":470,"content":471,"level":19},"/v1.0.5/guides/codecs#codec-matching","Codec Matching",[155],"Publishers and subscribers must use compatible codecs: // Service A: Publishes with Protobuf\npub := herald.NewPublisher(provider, signal, key, nil,\n    herald.WithPublisherCodec[Order](ProtobufCodec{}))\n\n// Service B: Must subscribe with Protobuf\nsub := herald.NewSubscriber(provider, signal, key, nil,\n    herald.WithSubscriberCodec[Order](ProtobufCodec{})) Mismatched codecs result in deserialization errors: capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n    if err.Operation == \"unmarshal\" {\n        // Likely codec mismatch\n        log.Printf(\"Deserialization failed: %v\", err.Err)\n        log.Printf(\"Raw payload: %s\", err.Raw)\n    }\n})",{"id":473,"title":408,"titles":474,"content":475,"level":19},"/v1.0.5/guides/codecs#performance-considerations",[155],"CodecSizeSpeedSchemaJSONLargerModerateNoProtobufSmallestFastRequiredMessagePackSmallFastNo Choose based on your requirements: JSON: Human-readable, universal compatibilityProtobuf: Maximum performance, schema evolutionMessagePack: Good balance, no schema needed",{"id":477,"title":478,"titles":479,"content":480,"level":19},"/v1.0.5/guides/codecs#testing-with-codecs","Testing with Codecs",[155],"func TestCustomCodec(t *testing.T) {\n    codec := MsgpackCodec{}\n\n    original := Order{ID: \"123\", Total: 99.99}\n\n    // Marshal\n    data, err := codec.Marshal(original)\n    if err != nil {\n        t.Fatalf(\"marshal failed: %v\", err)\n    }\n\n    // Unmarshal\n    var decoded Order\n    err = codec.Unmarshal(data, &decoded)\n    if err != nil {\n        t.Fatalf(\"unmarshal failed: %v\", err)\n    }\n\n    if decoded != original {\n        t.Errorf(\"round-trip failed: got %+v, want %+v\", decoded, original)\n    }\n} html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":482,"title":165,"titles":483,"content":484,"level":9},"/v1.0.5/guides/errors",[],"Handling errors in herald",{"id":486,"title":165,"titles":487,"content":488,"level":9},"/v1.0.5/guides/errors#error-handling",[],"Herald routes all operational errors through capitan's event system, enabling centralized error handling and observability.",{"id":490,"title":491,"titles":492,"content":493,"level":19},"/v1.0.5/guides/errors#error-signal","Error Signal",[165],"All herald errors are emitted on herald.ErrorSignal: capitan.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})",{"id":495,"title":496,"titles":497,"content":498,"level":19},"/v1.0.5/guides/errors#error-structure","Error Structure",[165],"type Error struct {\n    Operation string // \"publish\", \"subscribe\", \"unmarshal\", \"ack\", \"nack\"\n    Signal    string // The signal name involved\n    Err       string // The error message (string for JSON serialization)\n    Nack      bool   // Whether the message was nack'd\n    Raw       []byte // Raw message data (for unmarshal errors)\n} Note: Err is a string rather than error to support JSON serialization of error events.",{"id":500,"title":501,"titles":502,"content":34,"level":19},"/v1.0.5/guides/errors#error-types","Error Types",[165],{"id":504,"title":505,"titles":506,"content":507,"level":40},"/v1.0.5/guides/errors#publish-errors","Publish Errors",[165,501],"Occur when publishing to a broker fails: capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n    if err.Operation == \"publish\" {\n        log.Printf(\"Failed to publish to %s: %v\", err.Signal, err.Err)\n        // Retry logic, alerting, etc.\n    }\n}) Common causes: Broker unavailableNetwork timeoutAuthentication failureTopic/queue doesn't exist",{"id":509,"title":510,"titles":511,"content":512,"level":40},"/v1.0.5/guides/errors#subscribe-errors","Subscribe Errors",[165,501],"Occur when consuming from a broker fails: if err.Operation == \"subscribe\" {\n    log.Printf(\"Subscription error on %s: %v\", err.Signal, err.Err)\n} Common causes: Connection lostConsumer group rebalancePermission denied",{"id":514,"title":515,"titles":516,"content":517,"level":40},"/v1.0.5/guides/errors#unmarshal-errors","Unmarshal Errors",[165,501],"Occur when deserializing a message fails: if err.Operation == \"unmarshal\" {\n    log.Printf(\"Deserialization failed: %v\", err.Err)\n    log.Printf(\"Raw payload: %s\", err.Raw)\n    log.Printf(\"Message was nack'd: %t\", err.Nack)\n} The Raw field contains the original message bytes for debugging. Common causes: Codec mismatch (JSON vs Protobuf)Schema incompatibilityCorrupted messageEmpty payload",{"id":519,"title":520,"titles":521,"content":522,"level":40},"/v1.0.5/guides/errors#acknack-errors","Ack/Nack Errors",[165,501],"Occur when acknowledging or rejecting a message fails: if err.Operation == \"ack\" {\n    log.Printf(\"Failed to acknowledge message: %v\", err.Err)\n}\n\nif err.Operation == \"nack\" {\n    log.Printf(\"Failed to nack message: %v\", err.Err)\n} Common causes: Connection lost after processingMessage already ack'd/expiredBroker timeout",{"id":524,"title":525,"titles":526,"content":34,"level":19},"/v1.0.5/guides/errors#error-handling-patterns","Error Handling Patterns",[165],{"id":528,"title":529,"titles":530,"content":531,"level":40},"/v1.0.5/guides/errors#centralized-logging","Centralized Logging",[165,525],"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    slog.Error(\"herald error\",\n        \"operation\", err.Operation,\n        \"signal\", err.Signal,\n        \"error\", err.Err,\n    )\n})",{"id":533,"title":534,"titles":535,"content":536,"level":40},"/v1.0.5/guides/errors#metrics-collection","Metrics Collection",[165,525],"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    metrics.Counter(\"herald_errors_total\",\n        \"operation\", err.Operation,\n        \"signal\", err.Signal,\n    ).Inc()\n})",{"id":538,"title":539,"titles":540,"content":541,"level":40},"/v1.0.5/guides/errors#alerting","Alerting",[165,525],"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    if err.Operation == \"publish\" {\n        // Critical: messages not being delivered\n        alertOps(fmt.Sprintf(\"Publishing failed: %s - %v\", err.Signal, err.Err))\n    }\n})",{"id":543,"title":544,"titles":545,"content":546,"level":40},"/v1.0.5/guides/errors#dead-letter-handling","Dead Letter Handling",[165,525],"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    if err.Operation == \"unmarshal\" && err.Raw != nil {\n        // Store failed messages for later analysis\n        deadLetterQueue.Send(ctx, err.Raw, map[string]string{\n            \"signal\":    err.Signal,\n            \"error\":     err.Err,\n            \"timestamp\": time.Now().Format(time.RFC3339),\n        })\n    }\n})",{"id":548,"title":549,"titles":550,"content":551,"level":19},"/v1.0.5/guides/errors#sentinel-errors","Sentinel Errors",[165],"Herald provides sentinel errors for common cases: var (\n    ErrNoWriter = errors.New(\"herald: no writer configured\")\n    ErrNoReader = errors.New(\"herald: no reader configured\")\n) Check for specific errors: if errors.Is(err.Err, herald.ErrNoWriter) {\n    log.Printf(\"Provider not configured for publishing\")\n}",{"id":553,"title":554,"titles":555,"content":34,"level":19},"/v1.0.5/guides/errors#error-recovery","Error Recovery",[165],{"id":557,"title":558,"titles":559,"content":560,"level":40},"/v1.0.5/guides/errors#automatic-recovery","Automatic Recovery",[165,554],"Pipeline options handle transient errors: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRetry[Order](3),\n    herald.WithBackoff[Order](3, 100*time.Millisecond),\n})\n// Retries automatically before emitting error signal",{"id":562,"title":563,"titles":564,"content":565,"level":40},"/v1.0.5/guides/errors#manual-recovery","Manual Recovery",[165,554],"For persistent errors, implement custom recovery: capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    if err.Operation == \"publish\" {\n        // Fallback to backup broker\n        backupProvider.Publish(ctx, originalData, metadata)\n    }\n})",{"id":567,"title":568,"titles":569,"content":570,"level":19},"/v1.0.5/guides/errors#testing-error-handling","Testing Error Handling",[165],"func TestErrorHandling(t *testing.T) {\n    var capturedError herald.Error\n\n    capitan.Hook(herald.ErrorSignal, func(_ context.Context, e *capitan.Event) {\n        capturedError, _ = herald.ErrorKey.From(e)\n    })\n\n    // Trigger error condition\n    provider := &failingProvider{err: errors.New(\"connection refused\")}\n    pub := herald.NewPublisher(provider, signal, key, nil)\n    pub.Start()\n\n    capitan.Emit(ctx, signal, key.Field(order))\n    capitan.Shutdown()\n\n    if capturedError.Operation != \"publish\" {\n        t.Errorf(\"expected publish error, got %s\", capturedError.Operation)\n    }\n} html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":572,"title":573,"titles":574,"content":575,"level":9},"/v1.0.5/guides/testing","Testing",[],"Testing herald-based applications",{"id":577,"title":573,"titles":578,"content":579,"level":9},"/v1.0.5/guides/testing#testing",[],"Herald provides utilities for testing applications that use message bridging.",{"id":581,"title":582,"titles":583,"content":584,"level":19},"/v1.0.5/guides/testing#mockprovider","MockProvider",[573],"The testing package provides a mock provider for unit tests: import heraldtest \"github.com/zoobz-io/herald/testing\"\n\nfunc TestOrderPublishing(t *testing.T) {\n    // Create mock provider\n    mock := heraldtest.NewMockProvider()\n\n    // Create publisher with mock\n    pub := herald.NewPublisher(mock, orderSignal, orderKey, nil)\n    pub.Start()\n\n    // Emit event\n    capitan.Emit(ctx, orderSignal, orderKey.Field(Order{ID: \"123\"}))\n    capitan.Shutdown()\n    pub.Close()\n\n    // Verify\n    if mock.PublishCount() != 1 {\n        t.Errorf(\"expected 1 publish, got %d\", mock.PublishCount())\n    }\n\n    published := mock.Published()\n    // Assert on published[0].Data and published[0].Metadata\n}",{"id":586,"title":587,"titles":588,"content":589,"level":19},"/v1.0.5/guides/testing#mockprovider-api","MockProvider API",[573],"MockProvider uses a fluent builder pattern: // Create basic mock\nmock := heraldtest.NewMockProvider()\n\n// Configure with fluent methods (returns *MockProvider for chaining)\nsubCh := make(chan herald.Result[herald.Message], 10)\nmock := heraldtest.NewMockProvider().\n    WithSubscribeChannel(subCh).\n    WithPublishCallback(func(data []byte, meta herald.Metadata) {\n        // Custom logic on each publish\n    })\n\n// Query state\nmock.Published()     // []PublishedMessage - all published messages\nmock.PublishCount()  // int - number of published messages\nmock.IsClosed()      // bool - whether Close() was called\nmock.Reset()         // Clear published messages",{"id":591,"title":592,"titles":593,"content":34,"level":19},"/v1.0.5/guides/testing#testing-publishers","Testing Publishers",[573],{"id":595,"title":596,"titles":597,"content":598,"level":40},"/v1.0.5/guides/testing#basic-publisher-test","Basic Publisher Test",[573,592],"func TestPublisher_EmitsToProvider(t *testing.T) {\n    mock := heraldtest.NewMockProvider()\n\n    signal := capitan.NewSignal(\"test.pub\", \"Test\")\n    key := capitan.NewKey[Order](\"order\", \"test.Order\")\n\n    pub := herald.NewPublisher(mock, signal, key, nil)\n    pub.Start(context.Background())\n\n    capitan.Emit(context.Background(), signal, key.Field(Order{\n        ID:    \"ORD-123\",\n        Total: 99.99,\n    }))\n\n    capitan.Shutdown()\n    pub.Close()\n\n    if mock.PublishCount() != 1 {\n        t.Fatalf(\"expected 1 message, got %d\", mock.PublishCount())\n    }\n\n    var order Order\n    json.Unmarshal(mock.Published()[0].Data, &order)\n\n    if order.ID != \"ORD-123\" {\n        t.Errorf(\"unexpected order ID: %s\", order.ID)\n    }\n}",{"id":600,"title":601,"titles":602,"content":603,"level":40},"/v1.0.5/guides/testing#testing-metadata-propagation","Testing Metadata Propagation",[573,592],"func TestPublisher_PropagatesMetadata(t *testing.T) {\n    mock := heraldtest.NewMockProvider()\n\n    // Middleware to add metadata\n    opts := []herald.Option[Order]{\n        herald.WithMiddleware(\n            herald.UseTransform[Order](\"add-correlation\", func(_ context.Context, env *herald.Envelope[Order]) *herald.Envelope[Order] {\n                env.Metadata[\"correlation-id\"] = \"abc-123\"\n                return env\n            }),\n        ),\n    }\n\n    pub := herald.NewPublisher(mock, signal, key, opts)\n    pub.Start()\n\n    capitan.Emit(context.Background(), signal, key.Field(order))\n    capitan.Shutdown()\n    pub.Close()\n\n    meta := mock.Published()[0].Metadata\n    if meta[\"correlation-id\"] != \"abc-123\" {\n        t.Errorf(\"metadata not propagated\")\n    }\n}",{"id":605,"title":606,"titles":607,"content":34,"level":19},"/v1.0.5/guides/testing#testing-subscribers","Testing Subscribers",[573],{"id":609,"title":610,"titles":611,"content":612,"level":40},"/v1.0.5/guides/testing#basic-subscriber-test","Basic Subscriber Test",[573,606],"func TestSubscriber_EmitsToCapitan(t *testing.T) {\n    subCh := make(chan herald.Result[herald.Message], 1)\n    mock := heraldtest.NewMockProvider(\n        heraldtest.WithSubscribeChannel(subCh),\n    )\n\n    signal := capitan.NewSignal(\"test.sub\", \"Test\")\n    key := capitan.NewKey[Order](\"order\", \"test.Order\")\n\n    var received Order\n    var wg sync.WaitGroup\n    wg.Add(1)\n\n    capitan.Hook(signal, func(_ context.Context, e *capitan.Event) {\n        received, _ = key.From(e)\n        wg.Done()\n    })\n\n    sub := herald.NewSubscriber(mock, signal, key, nil)\n    ctx, cancel := context.WithCancel(context.Background())\n    sub.Start(ctx)\n\n    // Send message\n    data, _ := json.Marshal(Order{ID: \"SUB-123\", Total: 50.0})\n    subCh \u003C- herald.NewSuccess(herald.Message{\n        Data: data,\n        Ack:  func() error { return nil },\n        Nack: func() error { return nil },\n    })\n\n    wg.Wait()\n    cancel()\n    sub.Close()\n\n    if received.ID != \"SUB-123\" {\n        t.Errorf(\"unexpected order ID: %s\", received.ID)\n    }\n}",{"id":614,"title":615,"titles":616,"content":617,"level":40},"/v1.0.5/guides/testing#testing-acknack","Testing Ack/Nack",[573,606],"func TestSubscriber_AcksOnSuccess(t *testing.T) {\n    subCh := make(chan herald.Result[herald.Message], 1)\n    mock := heraldtest.NewMockProvider(\n        heraldtest.WithSubscribeChannel(subCh),\n    )\n\n    var acked bool\n    data, _ := json.Marshal(Order{ID: \"123\"})\n    subCh \u003C- herald.NewSuccess(herald.Message{\n        Data: data,\n        Ack:  func() error { acked = true; return nil },\n        Nack: func() error { return nil },\n    })\n\n    sub := herald.NewSubscriber(mock, signal, key, nil)\n    ctx, cancel := context.WithCancel(context.Background())\n    sub.Start(ctx)\n\n    // Wait for processing\n    time.Sleep(50 * time.Millisecond)\n\n    cancel()\n    sub.Close()\n\n    if !acked {\n        t.Error(\"message was not acknowledged\")\n    }\n}",{"id":619,"title":568,"titles":620,"content":621,"level":19},"/v1.0.5/guides/testing#testing-error-handling",[573],"func TestPublisher_EmitsErrorSignal(t *testing.T) {\n    failingProvider := &mockProvider{\n        publishErr: errors.New(\"connection refused\"),\n    }\n\n    var capturedErr herald.Error\n\n    capitan.Hook(herald.ErrorSignal, func(_ context.Context, e *capitan.Event) {\n        capturedErr, _ = herald.ErrorKey.From(e)\n    })\n\n    pub := herald.NewPublisher(failingProvider, signal, key, nil)\n    pub.Start()\n\n    capitan.Emit(ctx, signal, key.Field(order))\n    capitan.Shutdown()\n    pub.Close()\n\n    if capturedErr.Operation != \"publish\" {\n        t.Errorf(\"expected publish error, got %s\", capturedErr.Operation)\n    }\n}",{"id":623,"title":624,"titles":625,"content":626,"level":19},"/v1.0.5/guides/testing#integration-testing","Integration Testing",[573],"For integration tests with real brokers, use the provider packages directly: func TestKafkaIntegration(t *testing.T) {\n    if testing.Short() {\n        t.Skip(\"skipping integration test\")\n    }\n\n    // Setup real Kafka (e.g., with testcontainers)\n    writer := &kafkago.Writer{...}\n    reader := kafkago.NewReader(...)\n\n    provider := kafka.New(\"test-topic\",\n        kafka.WithWriter(writer),\n        kafka.WithReader(reader),\n    )\n    defer provider.Close()\n\n    // Test actual publish/subscribe flow\n    // ...\n}",{"id":628,"title":629,"titles":630,"content":631,"level":19},"/v1.0.5/guides/testing#isolated-capitan-instances","Isolated Capitan Instances",[573],"Use isolated capitan instances to prevent test interference: func TestWithIsolatedCapitan(t *testing.T) {\n    c := capitan.New()\n    defer c.Shutdown()\n\n    pub := herald.NewPublisher(mock, signal, key, nil,\n        herald.WithPublisherCapitan[Order](c))\n\n    sub := herald.NewSubscriber(mock, signal, key, nil,\n        herald.WithSubscriberCapitan[Order](c))\n\n    // Tests use isolated instance\n}",{"id":633,"title":634,"titles":635,"content":636,"level":19},"/v1.0.5/guides/testing#additional-test-helpers","Additional Test Helpers",[573],"The testing package provides additional utilities:",{"id":638,"title":639,"titles":640,"content":641,"level":40},"/v1.0.5/guides/testing#message-helpers","Message Helpers",[573,634],"// Create a message with no-op ack/nack\nmsg := heraldtest.NewTestMessage(data, metadata)\n\n// Create a message with tracking callbacks\nvar acked, nacked bool\nmsg := heraldtest.NewTestMessageWithAck(data, metadata,\n    func() { acked = true },   // onAck\n    func() { nacked = true },  // onNack\n)",{"id":643,"title":644,"titles":645,"content":646,"level":40},"/v1.0.5/guides/testing#messagecapture","MessageCapture",[573,634],"Capture messages for verification: capture := heraldtest.NewMessageCapture()\n\n// In your test setup, capture messages\ncapture.Capture(msg)\n\n// Assert\nif capture.Count() != 1 {\n    t.Errorf(\"expected 1 message, got %d\", capture.Count())\n}\n\nmessages := capture.Messages() // []herald.Message\n\n// Wait for async messages\nif !capture.WaitForCount(3, 5*time.Second) {\n    t.Fatal(\"timeout waiting for messages\")\n}\n\n// Reset between tests\ncapture.Reset()",{"id":648,"title":649,"titles":650,"content":651,"level":40},"/v1.0.5/guides/testing#errorcapture","ErrorCapture",[573,634],"Capture herald errors for verification: capture := heraldtest.NewErrorCapture()\n\n// Hook into error signal\ncapitan.Hook(herald.ErrorSignal, func(_ context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n    capture.Capture(err)\n})\n\n// After test actions...\nif capture.Count() != 1 {\n    t.Errorf(\"expected 1 error, got %d\", capture.Count())\n}\n\nerrors := capture.Errors() // []herald.Error\n\n// Wait for async errors\nif !capture.WaitForCount(1, time.Second) {\n    t.Fatal(\"expected error not received\")\n}",{"id":653,"title":654,"titles":655,"content":656,"level":19},"/v1.0.5/guides/testing#best-practices","Best Practices",[573],"Use isolated capitan instances — Prevents test pollutionAlways call Shutdown() — Ensures events are processedUse buffered channels — Prevents blocking in testsTest both paths — Publish and subscribe separatelyVerify metadata — Ensure propagation works correctlyTest error cases — Verify error signal emissionUse timeouts — Prevent hanging tests func TestWithTimeout(t *testing.T) {\n    done := make(chan struct{})\n\n    go func() {\n        // Test logic\n        close(done)\n    }()\n\n    select {\n    case \u003C-done:\n        // Success\n    case \u003C-time.After(5 * time.Second):\n        t.Fatal(\"test timed out\")\n    }\n} html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}",{"id":658,"title":659,"titles":660,"content":661,"level":9},"/v1.0.5/guides/publishing","Publishing Guide",[],"Forward capitan events to message brokers",{"id":663,"title":664,"titles":665,"content":666,"level":9},"/v1.0.5/guides/publishing#publishing","Publishing",[],"Publishers observe capitan signals and forward events to external message brokers.",{"id":668,"title":71,"titles":669,"content":670,"level":19},"/v1.0.5/guides/publishing#basic-usage",[664],"package main\n\nimport (\n    \"context\"\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 key\n    orderCreated := capitan.NewSignal(\"order.created\", \"New order placed\")\n    orderKey := capitan.NewKey[Order](\"order\", \"app.Order\")\n\n    // Create provider\n    writer := &kafkago.Writer{\n        Addr:  kafkago.TCP(\"localhost:9092\"),\n        Topic: \"orders\",\n    }\n    provider := kafka.New(\"orders\", kafka.WithWriter(writer))\n    defer provider.Close()\n\n    // Create and start publisher\n    pub := herald.NewPublisher(provider, orderCreated, orderKey, nil)\n    pub.Start()\n    defer pub.Close()\n\n    // Emit events - automatically published to Kafka\n    capitan.Emit(ctx, orderCreated, orderKey.Field(Order{\n        ID:    \"ORD-123\",\n        Total: 99.99,\n    }))\n\n    capitan.Shutdown()\n}",{"id":672,"title":673,"titles":674,"content":675,"level":19},"/v1.0.5/guides/publishing#how-it-works","How It Works",[664],"Publisher observes signal — When pub.Start() is called, the publisher registers as a capitan listener for the specified signal.Event emitted — When capitan.Emit() is called with the signal, the publisher's listener receives the event.Extract and wrap — The publisher extracts the typed value using the key and wraps it in an Envelope with empty metadata.Process through pipeline — The Envelope passes through any configured middleware, which can add metadata headers.Serialize and publish — The terminal marshals the value to bytes and sends it to the broker with the Envelope's metadata.",{"id":677,"title":678,"titles":679,"content":34,"level":19},"/v1.0.5/guides/publishing#publisher-options","Publisher Options",[664],{"id":681,"title":145,"titles":682,"content":683,"level":40},"/v1.0.5/guides/publishing#pipeline-options",[664,678],"Add reliability features via pipeline options: pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRetry[Order](3),                          // Retry up to 3 times\n    herald.WithBackoff[Order](3, 100*time.Millisecond),  // Exponential backoff\n    herald.WithTimeout[Order](5*time.Second),            // Timeout per operation\n    herald.WithCircuitBreaker[Order](5, 30*time.Second), // Circuit breaker\n    herald.WithRateLimit[Order](100, 10),                // Rate limiting\n})",{"id":685,"title":686,"titles":687,"content":688,"level":40},"/v1.0.5/guides/publishing#custom-codec","Custom Codec",[664,678],"Use a different serialization format: pub := herald.NewPublisher(provider, signal, key, nil,\n    herald.WithPublisherCodec[Order](myProtobufCodec))",{"id":690,"title":691,"titles":692,"content":693,"level":40},"/v1.0.5/guides/publishing#custom-capitan-instance","Custom Capitan Instance",[664,678],"Use an isolated capitan instance instead of the global singleton: c := capitan.New()\npub := herald.NewPublisher(provider, signal, key, nil,\n    herald.WithPublisherCapitan[Order](c))",{"id":695,"title":696,"titles":697,"content":698,"level":19},"/v1.0.5/guides/publishing#metadata-propagation","Metadata Propagation",[664],"Use middleware to attach metadata that flows through to the broker: // Middleware to add headers\nopts := []herald.Option[Order]{\n    herald.WithMiddleware(\n        herald.UseTransform[Order](\"add-headers\", func(_ context.Context, env *herald.Envelope[Order]) *herald.Envelope[Order] {\n            env.Metadata[\"correlation-id\"] = \"abc-123\"\n            env.Metadata[\"trace-id\"] = requestID\n            return env\n        }),\n    ),\n}\n\npub := herald.NewPublisher(provider, orderCreated, orderKey, opts) The metadata maps to broker-native headers (Kafka headers, AMQP properties, SQS attributes, etc.).",{"id":700,"title":165,"titles":701,"content":702,"level":19},"/v1.0.5/guides/publishing#error-handling",[664],"Publishing errors are emitted as capitan events: capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n    if err.Operation == \"publish\" {\n        log.Printf(\"Failed to publish to %s: %v\", err.Signal, err.Err)\n    }\n}) The error event includes: Operation: \"publish\"Signal: The signal name that failedErr: The underlying error",{"id":704,"title":705,"titles":706,"content":707,"level":19},"/v1.0.5/guides/publishing#lifecycle","Lifecycle",[664],"// Create publisher\npub := herald.NewPublisher(provider, signal, key, nil)\n\n// Start observing (registers capitan listener)\npub.Start()\n\n// ... emit events ...\n\n// Stop observing and cleanup\npub.Close() Always call Close() to unregister the listener and release resources.",{"id":709,"title":654,"titles":710,"content":34,"level":19},"/v1.0.5/guides/publishing#best-practices",[664],{"id":712,"title":713,"titles":714,"content":715,"level":40},"/v1.0.5/guides/publishing#one-direction-per-node","One Direction Per Node",[664,654],"A node should be either a Publisher OR Subscriber for a given signal, never both. This prevents event loops in distributed topologies. // Service A: Publishes order events\npub := herald.NewPublisher(provider, orderCreated, orderKey, nil)\n\n// Service B: Subscribes to order events (different process)\nsub := herald.NewSubscriber(provider, orderCreated, orderKey, nil)",{"id":717,"title":246,"titles":718,"content":719,"level":40},"/v1.0.5/guides/publishing#graceful-shutdown",[664,654],"Drain capitan before closing the publisher: capitan.Shutdown() // Wait for pending events to process\npub.Close()        // Then close publisher\nprovider.Close()   // Finally close provider",{"id":721,"title":722,"titles":723,"content":724,"level":40},"/v1.0.5/guides/publishing#handle-serialization-errors","Handle Serialization Errors",[664,654],"If marshaling fails, an error event is emitted. Ensure your types are JSON-serializable (or match your codec): type Order struct {\n    ID    string  `json:\"id\"`\n    Total float64 `json:\"total\"`\n    // Unexported fields are not serialized\n} html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":726,"title":727,"titles":728,"content":729,"level":9},"/v1.0.5/guides/subscribing","Subscribing Guide",[],"Consume broker messages as capitan events",{"id":731,"title":81,"titles":732,"content":733,"level":9},"/v1.0.5/guides/subscribing#subscribing",[],"Subscribers consume messages from external brokers and emit them as capitan events.",{"id":735,"title":71,"titles":736,"content":737,"level":19},"/v1.0.5/guides/subscribing#basic-usage",[81],"package 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 key\n    orderCreated := capitan.NewSignal(\"order.created\", \"New order placed\")\n    orderKey := capitan.NewKey[Order](\"order\", \"app.Order\")\n\n    // Create provider\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.WithReader(reader))\n    defer provider.Close()\n\n    // Hook listener before starting subscriber\n    capitan.Hook(orderCreated, func(ctx context.Context, e *capitan.Event) {\n        order, ok := orderKey.From(e)\n        if ok {\n            fmt.Printf(\"Received order: %s ($%.2f)\\n\", order.ID, order.Total)\n        }\n    })\n\n    // Create and start subscriber\n    sub := herald.NewSubscriber(provider, orderCreated, orderKey, nil)\n    sub.Start(ctx)\n    defer sub.Close()\n\n    // Block until shutdown\n    select {}\n}",{"id":739,"title":673,"titles":740,"content":741,"level":19},"/v1.0.5/guides/subscribing#how-it-works",[81],"Subscriber starts consuming — When sub.Start(ctx) is called, the subscriber spawns a goroutine that calls provider.Subscribe(ctx).Message received — The provider returns messages through a channel.Deserialize and wrap — The subscriber unmarshals the message bytes into the typed value and wraps it in an Envelope with the broker's metadata.Process through pipeline — The Envelope passes through any configured middleware, which can read/modify metadata.Emit to capitan — The typed value and metadata are emitted as fields on the capitan event.Acknowledge — On successful emission, the message is acknowledged. On failure, it's nack'd for redelivery.",{"id":743,"title":744,"titles":745,"content":34,"level":19},"/v1.0.5/guides/subscribing#subscriber-options","Subscriber Options",[81],{"id":747,"title":145,"titles":748,"content":749,"level":40},"/v1.0.5/guides/subscribing#pipeline-options",[81,744],"Add reliability features: sub := herald.NewSubscriber(provider, signal, key, []herald.Option[Order]{\n    herald.WithTimeout[Order](5*time.Second),\n    herald.WithRateLimit[Order](100, 10),\n})",{"id":751,"title":686,"titles":752,"content":753,"level":40},"/v1.0.5/guides/subscribing#custom-codec",[81,744],"Use a different serialization format: sub := herald.NewSubscriber(provider, signal, key, nil,\n    herald.WithSubscriberCodec[Order](myProtobufCodec))",{"id":755,"title":691,"titles":756,"content":757,"level":40},"/v1.0.5/guides/subscribing#custom-capitan-instance",[81,744],"Use an isolated capitan instance: c := capitan.New()\nsub := herald.NewSubscriber(provider, signal, key, nil,\n    herald.WithSubscriberCapitan[Order](c))",{"id":759,"title":760,"titles":761,"content":762,"level":19},"/v1.0.5/guides/subscribing#metadata-access","Metadata Access",[81],"Message metadata is available in two ways:",{"id":764,"title":765,"titles":766,"content":767,"level":40},"/v1.0.5/guides/subscribing#in-capitan-hooks","In Capitan Hooks",[81,760],"Metadata is emitted as a field on the Capitan event: capitan.Hook(orderCreated, func(ctx context.Context, e *capitan.Event) {\n    order, _ := orderKey.From(e)\n    meta, _ := herald.MetadataKey.From(e)\n\n    correlationID := meta[\"correlation-id\"]\n    traceID := meta[\"trace-id\"]\n    processOrder(ctx, order, correlationID)\n})",{"id":769,"title":770,"titles":771,"content":772,"level":40},"/v1.0.5/guides/subscribing#in-middleware","In Middleware",[81,760],"Access metadata via the Envelope for processing before emission: opts := []herald.Option[Order]{\n    herald.WithMiddleware(\n        herald.UseEffect[Order](\"log-headers\", func(_ context.Context, env *herald.Envelope[Order]) error {\n            log.Printf(\"Processing order with trace %s\", env.Metadata[\"trace-id\"])\n            return nil\n        }),\n    ),\n}\n\nsub := herald.NewSubscriber(provider, orderCreated, orderKey, opts)",{"id":774,"title":775,"titles":776,"content":777,"level":19},"/v1.0.5/guides/subscribing#acknowledgment-semantics","Acknowledgment Semantics",[81],"Herald handles acknowledgment automatically: OutcomeActionSuccessful deserialization + emitAck() calledDeserialization failureNack() calledPipeline failureNack() called Each provider implements broker-appropriate ack/nack behavior: ProviderAckNackKafkaCommit offsetDon't commit (redelivered)JetStreammsg.Ack()msg.Nak() (redelivered)Pub/Submsg.Ack()msg.Nack()RedisXACKRemains pendingSQSDelete messageReturns after visibility timeoutAMQPAck(false)Nack(false, true) with requeue",{"id":779,"title":165,"titles":780,"content":781,"level":19},"/v1.0.5/guides/subscribing#error-handling",[81],"Deserialization and processing errors are emitted as capitan events: capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    switch err.Operation {\n    case \"unmarshal\":\n        log.Printf(\"Failed to deserialize message: %v\", err.Err)\n        log.Printf(\"Raw payload: %s\", err.Raw)\n    case \"subscribe\":\n        log.Printf(\"Subscription error: %v\", err.Err)\n    case \"ack\":\n        log.Printf(\"Failed to acknowledge: %v\", err.Err)\n    case \"nack\":\n        log.Printf(\"Failed to nack: %v\", err.Err)\n    }\n})",{"id":783,"title":705,"titles":784,"content":785,"level":19},"/v1.0.5/guides/subscribing#lifecycle",[81],"// Create subscriber\nsub := herald.NewSubscriber(provider, signal, key, nil)\n\n// Start consuming (spawns goroutine)\nctx, cancel := context.WithCancel(context.Background())\nsub.Start(ctx)\n\n// ... process messages ...\n\n// Stop consuming\ncancel()      // Signal goroutine to stop\nsub.Close()   // Wait for cleanup",{"id":787,"title":246,"titles":788,"content":789,"level":19},"/v1.0.5/guides/subscribing#graceful-shutdown",[81],"The subscriber responds to context cancellation. The shutdown order matters: ctx, cancel := context.WithCancel(context.Background())\nsub.Start(ctx)\n\n// On shutdown signal (in this exact order):\ncancel()                  // 1. Stop consuming new messages\nsub.Close()              // 2. Wait for in-flight messages to complete\ncapitan.Shutdown()       // 3. Drain any queued capitan events\nprovider.Close()         // 4. Close broker connection Why this order: cancel() signals the subscribe goroutine to stop fetching new messagessub.Close() waits for any message currently being processed (deserialization, pipeline, ack/nack)capitan.Shutdown() ensures all emitted events reach their hooksprovider.Close() releases broker resources after all operations complete Common mistake: Closing the provider before the subscriber can cause ack/nack operations to fail on in-flight messages.",{"id":791,"title":654,"titles":792,"content":34,"level":19},"/v1.0.5/guides/subscribing#best-practices",[81],{"id":794,"title":795,"titles":796,"content":797,"level":40},"/v1.0.5/guides/subscribing#hook-listeners-before-starting","Hook Listeners Before Starting",[81,654],"Register capitan listeners before starting the subscriber to avoid missing events: // First: set up listeners\ncapitan.Hook(signal, handler)\n\n// Then: start subscriber\nsub.Start(ctx)",{"id":799,"title":800,"titles":801,"content":802,"level":40},"/v1.0.5/guides/subscribing#use-consumer-groups","Use Consumer Groups",[81,654],"For scalable consumption, use broker consumer groups: // Kafka\nreader := kafkago.NewReader(kafkago.ReaderConfig{\n    GroupID: \"order-processor\",  // Consumer group\n    // ...\n})\n\n// Redis\nprovider := redis.New(\"stream\", redis.WithGroup(\"processor-group\"))",{"id":804,"title":805,"titles":806,"content":807,"level":40},"/v1.0.5/guides/subscribing#handle-poison-messages","Handle Poison Messages",[81,654],"Messages that consistently fail deserialization will be nack'd repeatedly. Consider: Dead letter queues at the broker levelError signal handlers that log problematic payloadsCircuit breakers to pause consumption on repeated failures html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":809,"title":810,"titles":811,"content":812,"level":9},"/v1.0.5/reference/api","API Reference",[],"Complete herald API documentation",{"id":814,"title":810,"titles":815,"content":816,"level":9},"/v1.0.5/reference/api#api-reference",[],"Complete reference for herald's public API.",{"id":818,"title":819,"titles":820,"content":34,"level":19},"/v1.0.5/reference/api#core-types","Core Types",[810],{"id":822,"title":823,"titles":824,"content":825,"level":40},"/v1.0.5/reference/api#provider","Provider",[810,819],"type Provider interface {\n    Publish(ctx context.Context, data []byte, metadata Metadata) error\n    Subscribe(ctx context.Context) \u003C-chan Result[Message]\n    Ping(ctx context.Context) error\n    Close() error\n} The interface all broker providers implement. MethodDescriptionPublishSend raw bytes with metadata to the brokerSubscribeReturn a stream of messages from the brokerPingVerify broker connectivity for health checksCloseRelease broker resources",{"id":827,"title":828,"titles":829,"content":830,"level":40},"/v1.0.5/reference/api#message","Message",[810,819],"type Message struct {\n    Data     []byte\n    Metadata Metadata\n    Ack      func() error\n    Nack     func() error\n} Represents a message received from a broker. FieldDescriptionDataRaw message payloadMetadataMessage headers/attributesAckAcknowledge successful processingNackReject for redelivery",{"id":832,"title":170,"titles":833,"content":834,"level":40},"/v1.0.5/reference/api#metadata",[810,819],"type Metadata map[string]string Key-value pairs for message headers.",{"id":836,"title":837,"titles":838,"content":839,"level":40},"/v1.0.5/reference/api#resultt","ResultT",[810,819],"type Result[T any] struct {\n    // unexported fields\n}\n\nfunc NewSuccess[T any](value T) Result[T]\nfunc NewError[T any](err error) Result[T]\n\nfunc (r Result[T]) IsSuccess() bool\nfunc (r Result[T]) IsError() bool\nfunc (r Result[T]) Value() T\nfunc (r Result[T]) Error() error Discriminated union for either a value or error.",{"id":841,"title":842,"titles":843,"content":844,"level":40},"/v1.0.5/reference/api#error","Error",[810,819],"type Error struct {\n    Operation string // \"publish\", \"subscribe\", \"unmarshal\", \"ack\", \"nack\"\n    Signal    string // Signal name\n    Err       string // Error message (string for JSON serialization)\n    Nack      bool   // Whether message was nack'd\n    Raw       []byte // Raw payload (for unmarshal errors)\n} Error information emitted on ErrorSignal. Note that Err is a string (not error) to support JSON serialization.",{"id":846,"title":847,"titles":848,"content":34,"level":19},"/v1.0.5/reference/api#publisher","Publisher",[810],{"id":850,"title":851,"titles":852,"content":853,"level":40},"/v1.0.5/reference/api#newpublisher","NewPublisher",[810,847],"func NewPublisher[T any](\n    provider Provider,\n    signal capitan.Signal,\n    key capitan.Key[T],\n    opts []Option[T],\n    pubOpts ...PublisherOption[T],\n) *Publisher[T] Creates a new publisher. ParameterDescriptionproviderBroker providersignalCapitan signal to observekeyTyped key for extracting valuesoptsPipeline options (retry, timeout, etc.)pubOptsPublisher-specific options",{"id":855,"title":856,"titles":857,"content":858,"level":40},"/v1.0.5/reference/api#publisher-methods","Publisher Methods",[810,847],"func (p *Publisher[T]) Start()\nfunc (p *Publisher[T]) Close() error MethodDescriptionStartBegin observing the signal. Must be called exactly once.CloseStop observing and wait for in-flight publishes",{"id":860,"title":678,"titles":861,"content":862,"level":40},"/v1.0.5/reference/api#publisher-options",[810,847],"func WithPublisherCodec[T any](codec Codec) PublisherOption[T]\nfunc WithPublisherCapitan[T any](c *capitan.Capitan) PublisherOption[T] OptionDescriptionWithPublisherCodecUse custom serialization codecWithPublisherCapitanUse custom capitan instance",{"id":864,"title":865,"titles":866,"content":34,"level":19},"/v1.0.5/reference/api#subscriber","Subscriber",[810],{"id":868,"title":869,"titles":870,"content":871,"level":40},"/v1.0.5/reference/api#newsubscriber","NewSubscriber",[810,865],"func NewSubscriber[T any](\n    provider Provider,\n    signal capitan.Signal,\n    key capitan.Key[T],\n    opts []Option[T],\n    subOpts ...SubscriberOption[T],\n) *Subscriber[T] Creates a new subscriber. ParameterDescriptionproviderBroker providersignalCapitan signal to emit tokeyTyped key for creating fieldsoptsPipeline optionssubOptsSubscriber-specific options",{"id":873,"title":874,"titles":875,"content":876,"level":40},"/v1.0.5/reference/api#subscriber-methods","Subscriber Methods",[810,865],"func (s *Subscriber[T]) Start(ctx context.Context)\nfunc (s *Subscriber[T]) Close() error MethodDescriptionStartBegin consuming messages. Must be called exactly once. The context controls subscriber lifetime.CloseStop consuming and wait for goroutine to exit",{"id":878,"title":744,"titles":879,"content":880,"level":40},"/v1.0.5/reference/api#subscriber-options",[810,865],"func WithSubscriberCodec[T any](codec Codec) SubscriberOption[T]\nfunc WithSubscriberCapitan[T any](c *capitan.Capitan) SubscriberOption[T] OptionDescriptionWithSubscriberCodecUse custom deserialization codecWithSubscriberCapitanUse custom capitan instance",{"id":882,"title":883,"titles":884,"content":34,"level":19},"/v1.0.5/reference/api#codec","Codec",[810],{"id":886,"title":426,"titles":887,"content":428,"level":40},"/v1.0.5/reference/api#codec-interface",[810,883],{"id":889,"title":890,"titles":891,"content":892,"level":40},"/v1.0.5/reference/api#jsoncodec","JSONCodec",[810,883],"type JSONCodec struct{} Default codec using encoding/json.",{"id":894,"title":145,"titles":895,"content":896,"level":19},"/v1.0.5/reference/api#pipeline-options",[810],"type Option[T any] func(pipz.Chainable[*Envelope[T]]) pipz.Chainable[*Envelope[T]]",{"id":898,"title":899,"titles":900,"content":901,"level":40},"/v1.0.5/reference/api#withretry","WithRetry",[810,145],"func WithRetry[T any](maxAttempts int) Option[T] Retry failed operations up to maxAttempts times.",{"id":903,"title":904,"titles":905,"content":906,"level":40},"/v1.0.5/reference/api#withbackoff","WithBackoff",[810,145],"func WithBackoff[T any](maxAttempts int, baseDelay time.Duration) Option[T] Retry with exponential backoff. Delays double each attempt.",{"id":908,"title":909,"titles":910,"content":911,"level":40},"/v1.0.5/reference/api#withtimeout","WithTimeout",[810,145],"func WithTimeout[T any](timeout time.Duration) Option[T] Cancel operations exceeding the timeout.",{"id":913,"title":914,"titles":915,"content":916,"level":40},"/v1.0.5/reference/api#withcircuitbreaker","WithCircuitBreaker",[810,145],"func WithCircuitBreaker[T any](maxFailures int, recoveryTime time.Duration) Option[T] Open circuit after maxFailures consecutive failures. Attempt recovery after recoveryTime.",{"id":918,"title":919,"titles":920,"content":921,"level":40},"/v1.0.5/reference/api#withratelimit","WithRateLimit",[810,145],"func WithRateLimit[T any](rate float64, burst int) Option[T] Limit operations to rate per second with burst capacity.",{"id":923,"title":924,"titles":925,"content":926,"level":40},"/v1.0.5/reference/api#witherrorhandler","WithErrorHandler",[810,145],"func WithErrorHandler[T any](handler pipz.Chainable[*pipz.Error[*Envelope[T]]]) Option[T] Custom error handling via a pipz chainable. The handler receives rich error context including the input Envelope, timestamp, and path through the pipeline. The handler observes errors without modifying the error flow. // Example: Log errors with context\nerrorLogger := pipz.Effect(\"log-error\", func(ctx context.Context, err *pipz.Error[*herald.Envelope[Order]]) error {\n    log.Printf(\"failed at %v: %v (input: %+v)\", err.Path, err.Err, err.InputData.Value)\n    return nil\n})\n\npub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithErrorHandler[Order](errorLogger),\n})",{"id":928,"title":929,"titles":930,"content":931,"level":40},"/v1.0.5/reference/api#withpipeline","WithPipeline",[810,145],"func WithPipeline[T any](custom pipz.Chainable[*Envelope[T]]) Option[T] Use a fully custom pipeline. Replaces any default processing.",{"id":933,"title":150,"titles":934,"content":935,"level":19},"/v1.0.5/reference/api#middleware",[810],"Middleware processors are used inside WithMiddleware to build processing pipelines. All processors operate on *Envelope[T], providing access to both Value and Metadata.",{"id":937,"title":938,"titles":939,"content":940,"level":40},"/v1.0.5/reference/api#withfilter","WithFilter",[810,150],"func WithFilter[T any](name string, condition func(context.Context, *Envelope[T]) bool) Option[T] Wrap the pipeline with a condition. If the condition returns false, the pipeline is skipped.",{"id":942,"title":943,"titles":944,"content":945,"level":40},"/v1.0.5/reference/api#withmiddleware","WithMiddleware",[810,150],"func WithMiddleware[T any](processors ...pipz.Chainable[*Envelope[T]]) Option[T] Wrap the pipeline with a sequence of processors. Processors execute in order, with the wrapped pipeline last. herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithMiddleware(\n        herald.UseEffect[Order](\"log\", logFn),\n        herald.UseApply[Order](\"validate\", validateFn),\n    ),\n})",{"id":947,"title":311,"titles":948,"content":949,"level":40},"/v1.0.5/reference/api#usetransform",[810,150],"func UseTransform[T any](name string, fn func(context.Context, *Envelope[T]) *Envelope[T]) pipz.Chainable[*Envelope[T]] Pure transformation. Cannot fail. Use for transformations that always succeed.",{"id":951,"title":301,"titles":952,"content":953,"level":40},"/v1.0.5/reference/api#useapply",[810,150],"func UseApply[T any](name string, fn func(context.Context, *Envelope[T]) (*Envelope[T], error)) pipz.Chainable[*Envelope[T]] Transform with possible error. Use for validation, enrichment, or fallible transformations.",{"id":955,"title":306,"titles":956,"content":957,"level":40},"/v1.0.5/reference/api#useeffect",[810,150],"func UseEffect[T any](name string, fn func(context.Context, *Envelope[T]) error) pipz.Chainable[*Envelope[T]] Side effect only. The envelope passes through unchanged. Use for logging, metrics, or notifications.",{"id":959,"title":960,"titles":961,"content":962,"level":40},"/v1.0.5/reference/api#usemutate","UseMutate",[810,150],"func UseMutate[T any](name string, transformer func(context.Context, *Envelope[T]) *Envelope[T], condition func(context.Context, *Envelope[T]) bool) pipz.Chainable[*Envelope[T]] Conditional transformation. The transformer is only applied if the condition returns true.",{"id":964,"title":965,"titles":966,"content":967,"level":40},"/v1.0.5/reference/api#useenrich","UseEnrich",[810,150],"func UseEnrich[T any](name string, fn func(context.Context, *Envelope[T]) (*Envelope[T], error)) pipz.Chainable[*Envelope[T]] Optional enhancement. If enrichment fails, processing continues with the original envelope.",{"id":969,"title":970,"titles":971,"content":972,"level":40},"/v1.0.5/reference/api#useretry","UseRetry",[810,150],"func UseRetry[T any](maxAttempts int, processor pipz.Chainable[*Envelope[T]]) pipz.Chainable[*Envelope[T]] Wrap a processor with retry logic. Failed operations retry immediately.",{"id":974,"title":975,"titles":976,"content":977,"level":40},"/v1.0.5/reference/api#usebackoff","UseBackoff",[810,150],"func UseBackoff[T any](maxAttempts int, baseDelay time.Duration, processor pipz.Chainable[*Envelope[T]]) pipz.Chainable[*Envelope[T]] Wrap a processor with exponential backoff retry logic.",{"id":979,"title":980,"titles":981,"content":982,"level":40},"/v1.0.5/reference/api#usetimeout","UseTimeout",[810,150],"func UseTimeout[T any](d time.Duration, processor pipz.Chainable[*Envelope[T]]) pipz.Chainable[*Envelope[T]] Wrap a processor with a deadline. Operations exceeding the duration fail.",{"id":984,"title":985,"titles":986,"content":987,"level":40},"/v1.0.5/reference/api#usefallback","UseFallback",[810,150],"func UseFallback[T any](primary pipz.Chainable[*Envelope[T]], fallbacks ...pipz.Chainable[*Envelope[T]]) pipz.Chainable[*Envelope[T]] Wrap a processor with fallback alternatives. If primary fails, each fallback is tried in order.",{"id":989,"title":990,"titles":991,"content":992,"level":40},"/v1.0.5/reference/api#usefilter","UseFilter",[810,150],"func UseFilter[T any](name string, condition func(context.Context, *Envelope[T]) bool, processor pipz.Chainable[*Envelope[T]]) pipz.Chainable[*Envelope[T]] Wrap a processor with a condition. If false, the envelope passes through unchanged.",{"id":994,"title":995,"titles":996,"content":997,"level":40},"/v1.0.5/reference/api#useratelimit","UseRateLimit",[810,150],"func UseRateLimit[T any](rate float64, burst int) pipz.Chainable[*Envelope[T]] Rate limiting processor using token bucket algorithm.",{"id":999,"title":1000,"titles":1001,"content":34,"level":19},"/v1.0.5/reference/api#envelope","Envelope",[810],{"id":1003,"title":1004,"titles":1005,"content":1006,"level":40},"/v1.0.5/reference/api#envelopet","EnvelopeT",[810,1000],"type Envelope[T any] struct {\n    Value    T\n    Metadata Metadata\n} Wraps a value with metadata for pipeline processing. Access metadata in middleware via env.Metadata.",{"id":1008,"title":1009,"titles":1010,"content":34,"level":19},"/v1.0.5/reference/api#error-signals","Error Signals",[810],{"id":1012,"title":1013,"titles":1014,"content":1015,"level":40},"/v1.0.5/reference/api#errorsignal","ErrorSignal",[810,1009],"var ErrorSignal = capitan.NewSignal(\"herald.error\", \"Herald operational error\") Signal for all herald errors.",{"id":1017,"title":1018,"titles":1019,"content":1020,"level":40},"/v1.0.5/reference/api#errorkey","ErrorKey",[810,1009],"var ErrorKey = capitan.NewKey[Error](\"error\", \"herald.Error\") Key for extracting error details.",{"id":1022,"title":1023,"titles":1024,"content":1025,"level":40},"/v1.0.5/reference/api#metadatakey","MetadataKey",[810,1009],"var MetadataKey = capitan.NewKey[Metadata](\"metadata\", \"herald.Metadata\") Key for extracting message metadata from subscriber events. Use in Capitan hooks to access broker headers.",{"id":1027,"title":549,"titles":1028,"content":1029,"level":19},"/v1.0.5/reference/api#sentinel-errors",[810],"var (\n    ErrNoWriter = errors.New(\"herald: no writer configured\")\n    ErrNoReader = errors.New(\"herald: no reader configured\")\n) ErrorDescriptionErrNoWriterProvider has no write capabilityErrNoReaderProvider has no read capability html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":1031,"title":1032,"titles":1033,"content":1034,"level":9},"/v1.0.5/reference/providers","Providers Reference",[],"Complete provider configuration reference",{"id":1036,"title":1032,"titles":1037,"content":1038,"level":9},"/v1.0.5/reference/providers#providers-reference",[],"Configuration reference for all herald providers.",{"id":1040,"title":1041,"titles":1042,"content":1043,"level":19},"/v1.0.5/reference/providers#kafka","Kafka",[1032],"import \"github.com/zoobz-io/herald/kafka\"",{"id":1045,"title":1046,"titles":1047,"content":1048,"level":40},"/v1.0.5/reference/providers#constructor","Constructor",[1032,1041],"func New(topic string, opts ...Option) *Provider",{"id":1050,"title":1051,"titles":1052,"content":1053,"level":40},"/v1.0.5/reference/providers#options","Options",[1032,1041],"OptionDescriptionWithWriter(w Writer)Set Kafka writer for publishingWithReader(r Reader)Set Kafka reader for subscribing",{"id":1055,"title":1056,"titles":1057,"content":1058,"level":40},"/v1.0.5/reference/providers#interfaces","Interfaces",[1032,1041],"type Writer interface {\n    WriteMessages(ctx context.Context, msgs ...kafka.Message) error\n    Close() error\n}\n\ntype Reader interface {\n    FetchMessage(ctx context.Context) (kafka.Message, error)\n    CommitMessages(ctx context.Context, msgs ...kafka.Message) error\n    Close() error\n}",{"id":1060,"title":1061,"titles":1062,"content":1063,"level":19},"/v1.0.5/reference/providers#nats","NATS",[1032],"import \"github.com/zoobz-io/herald/nats\"",{"id":1065,"title":1046,"titles":1066,"content":1067,"level":40},"/v1.0.5/reference/providers#constructor-1",[1032,1061],"func New(subject string, opts ...Option) *Provider",{"id":1069,"title":1051,"titles":1070,"content":1071,"level":40},"/v1.0.5/reference/providers#options-1",[1032,1061],"OptionDescriptionWithConn(c *nats.Conn)Set NATS connection Note: NATS core does not support headers. Metadata is ignored.",{"id":1073,"title":1074,"titles":1075,"content":1076,"level":19},"/v1.0.5/reference/providers#jetstream","JetStream",[1032],"import \"github.com/zoobz-io/herald/jetstream\"",{"id":1078,"title":1046,"titles":1079,"content":1067,"level":40},"/v1.0.5/reference/providers#constructor-2",[1032,1074],{"id":1081,"title":1051,"titles":1082,"content":1083,"level":40},"/v1.0.5/reference/providers#options-2",[1032,1074],"OptionDescriptionWithJetStream(js jetstream.JetStream)Set JetStream contextWithConsumer(c jetstream.Consumer)Set consumer for subscribingWithStream(name string)Set stream name for publishing",{"id":1085,"title":1086,"titles":1087,"content":1088,"level":19},"/v1.0.5/reference/providers#google-pubsub","Google Pub/Sub",[1032],"import \"github.com/zoobz-io/herald/pubsub\"",{"id":1090,"title":1046,"titles":1091,"content":1092,"level":40},"/v1.0.5/reference/providers#constructor-3",[1032,1086],"func New(opts ...Option) *Provider Unlike other providers, Pub/Sub requires no identifier in the constructor. Topic and subscription are configured via options.",{"id":1094,"title":1051,"titles":1095,"content":1096,"level":40},"/v1.0.5/reference/providers#options-3",[1032,1086],"OptionDescriptionWithTopic(t *pubsub.Topic)Set topic for publishingWithSubscription(s *pubsub.Subscription)Set subscription for consuming",{"id":1098,"title":1099,"titles":1100,"content":1101,"level":19},"/v1.0.5/reference/providers#redis-streams","Redis Streams",[1032],"import \"github.com/zoobz-io/herald/redis\"",{"id":1103,"title":1046,"titles":1104,"content":1105,"level":40},"/v1.0.5/reference/providers#constructor-4",[1032,1099],"func New(stream string, opts ...Option) *Provider",{"id":1107,"title":1051,"titles":1108,"content":1109,"level":40},"/v1.0.5/reference/providers#options-4",[1032,1099],"OptionDescriptionWithClient(c *redis.Client)Set Redis clientWithGroup(name string)Set consumer group nameWithConsumer(name string)Set consumer name within groupWithMaxLen(n int64)Set stream max length",{"id":1111,"title":1112,"titles":1113,"content":1114,"level":19},"/v1.0.5/reference/providers#aws-sqs","AWS SQS",[1032],"import \"github.com/zoobz-io/herald/sqs\"",{"id":1116,"title":1046,"titles":1117,"content":1118,"level":40},"/v1.0.5/reference/providers#constructor-5",[1032,1112],"func New(queueURL string, opts ...Option) *Provider",{"id":1120,"title":1051,"titles":1121,"content":1122,"level":40},"/v1.0.5/reference/providers#options-5",[1032,1112],"OptionDescriptionWithClient(c *sqs.Client)Set SQS clientWithWaitTimeSeconds(n int32)Long polling wait timeWithMaxMessages(n int32)Max messages per receiveWithVisibilityTimeout(n int32)Visibility timeout seconds",{"id":1124,"title":1125,"titles":1126,"content":1127,"level":19},"/v1.0.5/reference/providers#rabbitmqamqp","RabbitMQ/AMQP",[1032],"import \"github.com/zoobz-io/herald/amqp\"",{"id":1129,"title":1046,"titles":1130,"content":1131,"level":40},"/v1.0.5/reference/providers#constructor-6",[1032,1125],"func New(exchange string, opts ...Option) *Provider",{"id":1133,"title":1051,"titles":1134,"content":1135,"level":40},"/v1.0.5/reference/providers#options-6",[1032,1125],"OptionDescriptionWithChannel(ch *amqp.Channel)Set AMQP channelWithQueue(name string)Set queue name for consumingWithRoutingKey(key string)Set routing key for publishingWithConsumerTag(tag string)Set consumer tag",{"id":1137,"title":1138,"titles":1139,"content":1140,"level":19},"/v1.0.5/reference/providers#aws-sns","AWS SNS",[1032],"import \"github.com/zoobz-io/herald/sns\"",{"id":1142,"title":1046,"titles":1143,"content":1144,"level":40},"/v1.0.5/reference/providers#constructor-7",[1032,1138],"func New(topicARN string, opts ...Option) *Provider",{"id":1146,"title":1051,"titles":1147,"content":1148,"level":40},"/v1.0.5/reference/providers#options-7",[1032,1138],"OptionDescriptionWithClient(c *sns.Client)Set SNS client Note: SNS is publish-only. Subscribe returns closed channel.",{"id":1150,"title":1151,"titles":1152,"content":1153,"level":19},"/v1.0.5/reference/providers#boltdb","BoltDB",[1032],"import \"github.com/zoobz-io/herald/bolt\"",{"id":1155,"title":1046,"titles":1156,"content":1157,"level":40},"/v1.0.5/reference/providers#constructor-8",[1032,1151],"func New(bucket string, opts ...Option) *Provider",{"id":1159,"title":1051,"titles":1160,"content":1161,"level":40},"/v1.0.5/reference/providers#options-8",[1032,1151],"OptionDescriptionWithDB(db *bbolt.DB)Set BoltDB databaseWithPollInterval(d time.Duration)Polling interval (default 100ms)WithBatchSize(n int)Messages per poll (default 10) Note: BoltDB does not support metadata. Metadata is ignored.",{"id":1163,"title":1164,"titles":1165,"content":1166,"level":19},"/v1.0.5/reference/providers#firestore","Firestore",[1032],"import \"github.com/zoobz-io/herald/firestore\"",{"id":1168,"title":1046,"titles":1169,"content":1170,"level":40},"/v1.0.5/reference/providers#constructor-9",[1032,1164],"func New(collection string, opts ...Option) *Provider",{"id":1172,"title":1051,"titles":1173,"content":1174,"level":40},"/v1.0.5/reference/providers#options-9",[1032,1164],"OptionDescriptionWithClient(c *firestore.Client)Set Firestore clientWithPollInterval(d time.Duration)Polling intervalWithBatchSize(n int)Documents per poll",{"id":1176,"title":1177,"titles":1178,"content":1179,"level":19},"/v1.0.5/reference/providers#io","io",[1032],"import \"github.com/zoobz-io/herald/io\"",{"id":1181,"title":1046,"titles":1182,"content":1183,"level":40},"/v1.0.5/reference/providers#constructor-10",[1032,1177],"func New(opts ...Option) *Provider",{"id":1185,"title":1051,"titles":1186,"content":1187,"level":40},"/v1.0.5/reference/providers#options-10",[1032,1177],"OptionDescriptionWithWriter(w io.Writer)Set writer for publishingWithReader(r io.Reader)Set reader for subscribingWithDelimiter(b byte)Message delimiter (default newline) Useful for testing with bytes.Buffer or os.Stdout.",{"id":1189,"title":1190,"titles":1191,"content":1192,"level":19},"/v1.0.5/reference/providers#feature-matrix","Feature Matrix",[1032],"ProviderPublishSubscribeMetadataAck/NackKafka✓✓✓✓NATS✓✓✗✗JetStream✓✓✓✓Pub/Sub✓✓✓✓Redis✓✓✓✓SQS✓✓✓✓AMQP✓✓✓✓SNS✓✗✓N/ABoltDB✓✓✗✓Firestore✓✓✓✓io✓✓✗✗ html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}",[1194],{"title":1195,"path":1196,"stem":1197,"children":1198,"page":1212},"V105","/v1.0.5","v1.0.5",[1199,1201,1213,1230],{"title":6,"path":5,"stem":1200,"description":8},"v1.0.5/1.overview",{"title":1202,"path":1203,"stem":1204,"children":1205,"page":1212},"Learn","/v1.0.5/learn","v1.0.5/2.learn",[1206,1208,1210],{"title":53,"path":52,"stem":1207,"description":55},"v1.0.5/2.learn/1.quickstart",{"title":91,"path":90,"stem":1209,"description":93},"v1.0.5/2.learn/2.concepts",{"title":16,"path":178,"stem":1211,"description":180},"v1.0.5/2.learn/3.architecture",false,{"title":1214,"path":1215,"stem":1216,"children":1217,"page":1212},"Guides","/v1.0.5/guides","v1.0.5/3.guides",[1218,1220,1222,1224,1226,1228],{"title":43,"path":288,"stem":1219,"description":290},"v1.0.5/3.guides/1.reliability",{"title":155,"path":417,"stem":1221,"description":419},"v1.0.5/3.guides/2.codecs",{"title":165,"path":482,"stem":1223,"description":484},"v1.0.5/3.guides/3.errors",{"title":573,"path":572,"stem":1225,"description":575},"v1.0.5/3.guides/4.testing",{"title":659,"path":658,"stem":1227,"description":661},"v1.0.5/3.guides/5.publishing",{"title":727,"path":726,"stem":1229,"description":729},"v1.0.5/3.guides/6.subscribing",{"title":1231,"path":1232,"stem":1233,"children":1234,"page":1212},"Reference","/v1.0.5/reference","v1.0.5/5.reference",[1235,1237],{"title":810,"path":809,"stem":1236,"description":812},"v1.0.5/5.reference/1.api",{"title":1032,"path":1031,"stem":1238,"description":1034},"v1.0.5/5.reference/2.providers",[1240],{"title":1195,"path":1196,"stem":1197,"children":1241,"page":1212},[1242,1243,1248,1256],{"title":6,"path":5,"stem":1200},{"title":1202,"path":1203,"stem":1204,"children":1244,"page":1212},[1245,1246,1247],{"title":53,"path":52,"stem":1207},{"title":91,"path":90,"stem":1209},{"title":16,"path":178,"stem":1211},{"title":1214,"path":1215,"stem":1216,"children":1249,"page":1212},[1250,1251,1252,1253,1254,1255],{"title":43,"path":288,"stem":1219},{"title":155,"path":417,"stem":1221},{"title":165,"path":482,"stem":1223},{"title":573,"path":572,"stem":1225},{"title":659,"path":658,"stem":1227},{"title":727,"path":726,"stem":1229},{"title":1231,"path":1232,"stem":1233,"children":1257,"page":1212},[1258,1259],{"title":810,"path":809,"stem":1236},{"title":1032,"path":1031,"stem":1238},[1261,3736,4181],{"id":1262,"title":1263,"body":1264,"description":34,"extension":3729,"icon":3730,"meta":3731,"navigation":1439,"path":3732,"seo":3733,"stem":3734,"__hash__":3735},"resources/readme.md","README",{"type":1265,"value":1266,"toc":3709},"minimark",[1267,1271,1339,1348,1355,1360,1495,1498,1501,1519,1522,1526,2338,2341,2479,2483,2521,2525,2531,2538,2607,2610,2613,2782,2786,2789,3205,3222,3225,3231,3410,3417,3420,3423,3467,3470,3473,3600,3606,3610,3617,3621,3643,3646,3669,3672,3686,3690,3697,3700,3705],[1268,1269,1270],"h1",{"id":1270},"herald",[1272,1273,1274,1285,1293,1301,1309,1317,1324,1331],"p",{},[1275,1276,1280],"a",{"href":1277,"rel":1278},"https://github.com/zoobz-io/herald/actions/workflows/ci.yml",[1279],"nofollow",[1281,1282],"img",{"alt":1283,"src":1284},"CI Status","https://github.com/zoobz-io/herald/workflows/CI/badge.svg",[1275,1286,1289],{"href":1287,"rel":1288},"https://codecov.io/gh/zoobz-io/herald",[1279],[1281,1290],{"alt":1291,"src":1292},"codecov","https://codecov.io/gh/zoobz-io/herald/graph/badge.svg?branch=main",[1275,1294,1297],{"href":1295,"rel":1296},"https://goreportcard.com/report/github.com/zoobz-io/herald",[1279],[1281,1298],{"alt":1299,"src":1300},"Go Report Card","https://goreportcard.com/badge/github.com/zoobz-io/herald",[1275,1302,1305],{"href":1303,"rel":1304},"https://github.com/zoobz-io/herald/security/code-scanning",[1279],[1281,1306],{"alt":1307,"src":1308},"CodeQL","https://github.com/zoobz-io/herald/workflows/CodeQL/badge.svg",[1275,1310,1313],{"href":1311,"rel":1312},"https://pkg.go.dev/github.com/zoobz-io/herald",[1279],[1281,1314],{"alt":1315,"src":1316},"Go Reference","https://pkg.go.dev/badge/github.com/zoobz-io/herald.svg",[1275,1318,1320],{"href":1319},"LICENSE",[1281,1321],{"alt":1322,"src":1323},"License","https://img.shields.io/github/license/zoobz-io/herald",[1275,1325,1327],{"href":1326},"go.mod",[1281,1328],{"alt":1329,"src":1330},"Go Version","https://img.shields.io/github/go-mod/go-version/zoobz-io/herald",[1275,1332,1335],{"href":1333,"rel":1334},"https://github.com/zoobz-io/herald/releases",[1279],[1281,1336],{"alt":1337,"src":1338},"Release","https://img.shields.io/github/v/release/zoobz-io/herald",[1272,1340,1341,1342,1347],{},"Bidirectional bindings between ",[1275,1343,1346],{"href":1344,"rel":1345},"https://github.com/zoobz-io/capitan",[1279],"capitan"," events and message brokers.",[1272,1349,1350,1351,1354],{},"Emit a ",[1275,1352,1346],{"href":1344,"rel":1353},[1279]," event, herald publishes it. Herald receives a message, capitan emits it. Same types, same signals, automatic serialization.",[1356,1357,1359],"h2",{"id":1358},"bidirectional-event-distribution","Bidirectional Event Distribution",[1361,1362,1366],"pre",{"className":1363,"code":1364,"language":1365,"meta":34,"style":34},"language-go shiki shiki-themes","// 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","go",[1367,1368,1369,1377,1422,1434,1441,1447,1479],"code",{"__ignoreMap":34},[1370,1371,1373],"span",{"class":1372,"line":9},"line",[1370,1374,1376],{"class":1375},"sLkEo","// capitan → broker: Publish events to external systems\n",[1370,1378,1379,1383,1386,1389,1393,1396,1399,1402,1405,1408,1410,1413,1415,1419],{"class":1372,"line":19},[1370,1380,1382],{"class":1381},"sh8_p","pub",[1370,1384,1385],{"class":1381}," :=",[1370,1387,1388],{"class":1381}," herald",[1370,1390,1392],{"class":1391},"sq5bi",".",[1370,1394,851],{"class":1395},"s5klm",[1370,1397,1398],{"class":1391},"(",[1370,1400,1401],{"class":1381},"provider",[1370,1403,1404],{"class":1391},",",[1370,1406,1407],{"class":1381}," signal",[1370,1409,1404],{"class":1391},[1370,1411,1412],{"class":1381}," key",[1370,1414,1404],{"class":1391},[1370,1416,1418],{"class":1417},"sUt3r"," nil",[1370,1420,1421],{"class":1391},")\n",[1370,1423,1424,1426,1428,1431],{"class":1372,"line":40},[1370,1425,1382],{"class":1381},[1370,1427,1392],{"class":1391},[1370,1429,1430],{"class":1395},"Start",[1370,1432,1433],{"class":1391},"()\n",[1370,1435,1437],{"class":1372,"line":1436},4,[1370,1438,1440],{"emptyLinePlaceholder":1439},true,"\n",[1370,1442,1444],{"class":1372,"line":1443},5,[1370,1445,1446],{"class":1375},"// broker → capitan: Subscribe to external messages as events\n",[1370,1448,1450,1453,1455,1457,1459,1461,1463,1465,1467,1469,1471,1473,1475,1477],{"class":1372,"line":1449},6,[1370,1451,1452],{"class":1381},"sub",[1370,1454,1385],{"class":1381},[1370,1456,1388],{"class":1381},[1370,1458,1392],{"class":1391},[1370,1460,869],{"class":1395},[1370,1462,1398],{"class":1391},[1370,1464,1401],{"class":1381},[1370,1466,1404],{"class":1391},[1370,1468,1407],{"class":1381},[1370,1470,1404],{"class":1391},[1370,1472,1412],{"class":1381},[1370,1474,1404],{"class":1391},[1370,1476,1418],{"class":1417},[1370,1478,1421],{"class":1391},[1370,1480,1482,1484,1486,1488,1490,1493],{"class":1372,"line":1481},7,[1370,1483,1452],{"class":1381},[1370,1485,1392],{"class":1391},[1370,1487,1430],{"class":1395},[1370,1489,1398],{"class":1391},[1370,1491,1492],{"class":1381},"ctx",[1370,1494,1421],{"class":1391},[1272,1496,1497],{},"One provider, one signal, one key. Herald handles serialization, acknowledgment, and error routing.",[1356,1499,66],{"id":1500},"installation",[1361,1502,1506],{"className":1503,"code":1504,"language":1505,"meta":34,"style":34},"language-bash shiki shiki-themes","go get github.com/zoobz-io/herald\n","bash",[1367,1507,1508],{"__ignoreMap":34},[1370,1509,1510,1512,1516],{"class":1372,"line":9},[1370,1511,1365],{"class":1395},[1370,1513,1515],{"class":1514},"sxAnc"," get",[1370,1517,1518],{"class":1514}," github.com/zoobz-io/herald\n",[1272,1520,1521],{},"Requires Go 1.23+.",[1356,1523,1525],{"id":1524},"quick-start","Quick Start",[1361,1527,1529],{"className":1363,"code":1528,"language":1365,"meta":34,"style":34},"package 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 := &kafkago.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",[1367,1530,1531,1540,1544,1553,1558,1563,1567,1576,1582,1588,1594,1599,1604,1619,1632,1644,1650,1655,1669,1687,1692,1698,1726,1760,1765,1771,1826,1853,1874,1888,1901,1907,1959,1975,1980,1986,2020,2031,2045,2050,2056,2088,2103,2117,2122,2128,2180,2207,2246,2251,2256,2262,2316,2321,2333],{"__ignoreMap":34},[1370,1532,1533,1536],{"class":1372,"line":9},[1370,1534,1535],{"class":1417},"package",[1370,1537,1539],{"class":1538},"sYBwO"," main\n",[1370,1541,1542],{"class":1372,"line":19},[1370,1543,1440],{"emptyLinePlaceholder":1439},[1370,1545,1546,1549],{"class":1372,"line":40},[1370,1547,1548],{"class":1417},"import",[1370,1550,1552],{"class":1551},"soy-K"," (\n",[1370,1554,1555],{"class":1372,"line":1436},[1370,1556,1557],{"class":1514},"    \"context\"\n",[1370,1559,1560],{"class":1372,"line":1443},[1370,1561,1562],{"class":1514},"    \"fmt\"\n",[1370,1564,1565],{"class":1372,"line":1449},[1370,1566,1440],{"emptyLinePlaceholder":1439},[1370,1568,1569,1573],{"class":1372,"line":1481},[1370,1570,1572],{"class":1571},"sSYET","    kafkago",[1370,1574,1575],{"class":1514}," \"github.com/segmentio/kafka-go\"\n",[1370,1577,1579],{"class":1372,"line":1578},8,[1370,1580,1581],{"class":1514},"    \"github.com/zoobz-io/capitan\"\n",[1370,1583,1585],{"class":1372,"line":1584},9,[1370,1586,1587],{"class":1514},"    \"github.com/zoobz-io/herald\"\n",[1370,1589,1591],{"class":1372,"line":1590},10,[1370,1592,1593],{"class":1514},"    \"github.com/zoobz-io/herald/kafka\"\n",[1370,1595,1597],{"class":1372,"line":1596},11,[1370,1598,1421],{"class":1551},[1370,1600,1602],{"class":1372,"line":1601},12,[1370,1603,1440],{"emptyLinePlaceholder":1439},[1370,1605,1607,1610,1613,1616],{"class":1372,"line":1606},13,[1370,1608,1609],{"class":1417},"type",[1370,1611,1612],{"class":1538}," Order",[1370,1614,1615],{"class":1417}," struct",[1370,1617,1618],{"class":1391}," {\n",[1370,1620,1622,1626,1629],{"class":1372,"line":1621},14,[1370,1623,1625],{"class":1624},"sBGCq","    ID",[1370,1627,1628],{"class":1538},"    string",[1370,1630,1631],{"class":1514},"  `json:\"id\"`\n",[1370,1633,1635,1638,1641],{"class":1372,"line":1634},15,[1370,1636,1637],{"class":1624},"    Total",[1370,1639,1640],{"class":1538}," float64",[1370,1642,1643],{"class":1514}," `json:\"total\"`\n",[1370,1645,1647],{"class":1372,"line":1646},16,[1370,1648,1649],{"class":1391},"}\n",[1370,1651,1653],{"class":1372,"line":1652},17,[1370,1654,1440],{"emptyLinePlaceholder":1439},[1370,1656,1658,1661,1664,1667],{"class":1372,"line":1657},18,[1370,1659,1660],{"class":1417},"func",[1370,1662,1663],{"class":1395}," main",[1370,1665,1666],{"class":1391},"()",[1370,1668,1618],{"class":1391},[1370,1670,1672,1675,1677,1680,1682,1685],{"class":1372,"line":1671},19,[1370,1673,1674],{"class":1381},"    ctx",[1370,1676,1385],{"class":1381},[1370,1678,1679],{"class":1381}," context",[1370,1681,1392],{"class":1391},[1370,1683,1684],{"class":1395},"Background",[1370,1686,1433],{"class":1391},[1370,1688,1690],{"class":1372,"line":1689},20,[1370,1691,1440],{"emptyLinePlaceholder":1439},[1370,1693,1695],{"class":1372,"line":1694},21,[1370,1696,1697],{"class":1375},"    // Define signal and typed key\n",[1370,1699,1701,1704,1706,1709,1711,1714,1716,1719,1721,1724],{"class":1372,"line":1700},22,[1370,1702,1703],{"class":1381},"    orderCreated",[1370,1705,1385],{"class":1381},[1370,1707,1708],{"class":1381}," capitan",[1370,1710,1392],{"class":1391},[1370,1712,1713],{"class":1395},"NewSignal",[1370,1715,1398],{"class":1391},[1370,1717,1718],{"class":1514},"\"order.created\"",[1370,1720,1404],{"class":1391},[1370,1722,1723],{"class":1514}," \"New order\"",[1370,1725,1421],{"class":1391},[1370,1727,1729,1732,1734,1736,1738,1741,1744,1747,1750,1753,1755,1758],{"class":1372,"line":1728},23,[1370,1730,1731],{"class":1381},"    orderKey",[1370,1733,1385],{"class":1381},[1370,1735,1708],{"class":1381},[1370,1737,1392],{"class":1391},[1370,1739,1740],{"class":1395},"NewKey",[1370,1742,1743],{"class":1391},"[",[1370,1745,1746],{"class":1538},"Order",[1370,1748,1749],{"class":1391},"](",[1370,1751,1752],{"class":1514},"\"order\"",[1370,1754,1404],{"class":1391},[1370,1756,1757],{"class":1514}," \"app.Order\"",[1370,1759,1421],{"class":1391},[1370,1761,1763],{"class":1372,"line":1762},24,[1370,1764,1440],{"emptyLinePlaceholder":1439},[1370,1766,1768],{"class":1372,"line":1767},25,[1370,1769,1770],{"class":1375},"    // Create Kafka provider\n",[1370,1772,1774,1777,1779,1783,1786,1788,1791,1794,1797,1800,1803,1805,1808,1810,1813,1816,1819,1821,1824],{"class":1372,"line":1773},26,[1370,1775,1776],{"class":1381},"    writer",[1370,1778,1385],{"class":1381},[1370,1780,1782],{"class":1781},"sW3Qg"," &",[1370,1784,1785],{"class":1538},"kafkago",[1370,1787,1392],{"class":1391},[1370,1789,1790],{"class":1538},"Writer",[1370,1792,1793],{"class":1391},"{",[1370,1795,1796],{"class":1624},"Addr",[1370,1798,1799],{"class":1391},":",[1370,1801,1802],{"class":1381}," kafkago",[1370,1804,1392],{"class":1391},[1370,1806,1807],{"class":1395},"TCP",[1370,1809,1398],{"class":1391},[1370,1811,1812],{"class":1514},"\"localhost:9092\"",[1370,1814,1815],{"class":1391},"),",[1370,1817,1818],{"class":1624}," Topic",[1370,1820,1799],{"class":1391},[1370,1822,1823],{"class":1514}," \"orders\"",[1370,1825,1649],{"class":1391},[1370,1827,1829,1832,1834,1836,1838,1841,1843,1845,1847,1850],{"class":1372,"line":1828},27,[1370,1830,1831],{"class":1381},"    reader",[1370,1833,1385],{"class":1381},[1370,1835,1802],{"class":1381},[1370,1837,1392],{"class":1391},[1370,1839,1840],{"class":1395},"NewReader",[1370,1842,1398],{"class":1391},[1370,1844,1785],{"class":1538},[1370,1846,1392],{"class":1391},[1370,1848,1849],{"class":1538},"ReaderConfig",[1370,1851,1852],{"class":1391},"{\n",[1370,1854,1856,1859,1861,1864,1867,1869,1871],{"class":1372,"line":1855},28,[1370,1857,1858],{"class":1624},"        Brokers",[1370,1860,1799],{"class":1391},[1370,1862,1863],{"class":1391}," []",[1370,1865,1866],{"class":1538},"string",[1370,1868,1793],{"class":1391},[1370,1870,1812],{"class":1514},[1370,1872,1873],{"class":1391},"},\n",[1370,1875,1877,1880,1882,1885],{"class":1372,"line":1876},29,[1370,1878,1879],{"class":1624},"        Topic",[1370,1881,1799],{"class":1391},[1370,1883,1884],{"class":1514},"   \"orders\"",[1370,1886,1887],{"class":1391},",\n",[1370,1889,1891,1894,1896,1899],{"class":1372,"line":1890},30,[1370,1892,1893],{"class":1624},"        GroupID",[1370,1895,1799],{"class":1391},[1370,1897,1898],{"class":1514}," \"order-processor\"",[1370,1900,1887],{"class":1391},[1370,1902,1904],{"class":1372,"line":1903},31,[1370,1905,1906],{"class":1391},"    })\n",[1370,1908,1910,1913,1915,1918,1920,1923,1925,1928,1930,1932,1934,1937,1939,1942,1944,1946,1948,1951,1953,1956],{"class":1372,"line":1909},32,[1370,1911,1912],{"class":1381},"    provider",[1370,1914,1385],{"class":1381},[1370,1916,1917],{"class":1381}," kafka",[1370,1919,1392],{"class":1391},[1370,1921,1922],{"class":1395},"New",[1370,1924,1398],{"class":1391},[1370,1926,1927],{"class":1514},"\"orders\"",[1370,1929,1404],{"class":1391},[1370,1931,1917],{"class":1381},[1370,1933,1392],{"class":1391},[1370,1935,1936],{"class":1395},"WithWriter",[1370,1938,1398],{"class":1391},[1370,1940,1941],{"class":1381},"writer",[1370,1943,1815],{"class":1391},[1370,1945,1917],{"class":1381},[1370,1947,1392],{"class":1391},[1370,1949,1950],{"class":1395},"WithReader",[1370,1952,1398],{"class":1391},[1370,1954,1955],{"class":1381},"reader",[1370,1957,1958],{"class":1391},"))\n",[1370,1960,1962,1965,1968,1970,1973],{"class":1372,"line":1961},33,[1370,1963,1964],{"class":1781},"    defer",[1370,1966,1967],{"class":1381}," provider",[1370,1969,1392],{"class":1391},[1370,1971,1972],{"class":1395},"Close",[1370,1974,1433],{"class":1391},[1370,1976,1978],{"class":1372,"line":1977},34,[1370,1979,1440],{"emptyLinePlaceholder":1439},[1370,1981,1983],{"class":1372,"line":1982},35,[1370,1984,1985],{"class":1375},"    // Publish: capitan events → Kafka\n",[1370,1987,1989,1992,1994,1996,1998,2000,2002,2004,2006,2009,2011,2014,2016,2018],{"class":1372,"line":1988},36,[1370,1990,1991],{"class":1381},"    pub",[1370,1993,1385],{"class":1381},[1370,1995,1388],{"class":1381},[1370,1997,1392],{"class":1391},[1370,1999,851],{"class":1395},[1370,2001,1398],{"class":1391},[1370,2003,1401],{"class":1381},[1370,2005,1404],{"class":1391},[1370,2007,2008],{"class":1381}," orderCreated",[1370,2010,1404],{"class":1391},[1370,2012,2013],{"class":1381}," orderKey",[1370,2015,1404],{"class":1391},[1370,2017,1418],{"class":1417},[1370,2019,1421],{"class":1391},[1370,2021,2023,2025,2027,2029],{"class":1372,"line":2022},37,[1370,2024,1991],{"class":1381},[1370,2026,1392],{"class":1391},[1370,2028,1430],{"class":1395},[1370,2030,1433],{"class":1391},[1370,2032,2034,2036,2039,2041,2043],{"class":1372,"line":2033},38,[1370,2035,1964],{"class":1781},[1370,2037,2038],{"class":1381}," pub",[1370,2040,1392],{"class":1391},[1370,2042,1972],{"class":1395},[1370,2044,1433],{"class":1391},[1370,2046,2048],{"class":1372,"line":2047},39,[1370,2049,1440],{"emptyLinePlaceholder":1439},[1370,2051,2053],{"class":1372,"line":2052},40,[1370,2054,2055],{"class":1375},"    // Subscribe: Kafka → capitan events\n",[1370,2057,2059,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086],{"class":1372,"line":2058},41,[1370,2060,2061],{"class":1381},"    sub",[1370,2063,1385],{"class":1381},[1370,2065,1388],{"class":1381},[1370,2067,1392],{"class":1391},[1370,2069,869],{"class":1395},[1370,2071,1398],{"class":1391},[1370,2073,1401],{"class":1381},[1370,2075,1404],{"class":1391},[1370,2077,2008],{"class":1381},[1370,2079,1404],{"class":1391},[1370,2081,2013],{"class":1381},[1370,2083,1404],{"class":1391},[1370,2085,1418],{"class":1417},[1370,2087,1421],{"class":1391},[1370,2089,2091,2093,2095,2097,2099,2101],{"class":1372,"line":2090},42,[1370,2092,2061],{"class":1381},[1370,2094,1392],{"class":1391},[1370,2096,1430],{"class":1395},[1370,2098,1398],{"class":1391},[1370,2100,1492],{"class":1381},[1370,2102,1421],{"class":1391},[1370,2104,2106,2108,2111,2113,2115],{"class":1372,"line":2105},43,[1370,2107,1964],{"class":1781},[1370,2109,2110],{"class":1381}," sub",[1370,2112,1392],{"class":1391},[1370,2114,1972],{"class":1395},[1370,2116,1433],{"class":1391},[1370,2118,2120],{"class":1372,"line":2119},44,[1370,2121,1440],{"emptyLinePlaceholder":1439},[1370,2123,2125],{"class":1372,"line":2124},45,[1370,2126,2127],{"class":1375},"    // Handle incoming messages with standard capitan hooks\n",[1370,2129,2131,2134,2136,2139,2141,2144,2146,2149,2151,2153,2155,2157,2160,2162,2165,2168,2170,2172,2175,2178],{"class":1372,"line":2130},46,[1370,2132,2133],{"class":1381},"    capitan",[1370,2135,1392],{"class":1391},[1370,2137,2138],{"class":1395},"Hook",[1370,2140,1398],{"class":1391},[1370,2142,2143],{"class":1381},"orderCreated",[1370,2145,1404],{"class":1391},[1370,2147,2148],{"class":1417}," func",[1370,2150,1398],{"class":1391},[1370,2152,1492],{"class":1571},[1370,2154,1679],{"class":1538},[1370,2156,1392],{"class":1391},[1370,2158,2159],{"class":1538},"Context",[1370,2161,1404],{"class":1391},[1370,2163,2164],{"class":1571}," e",[1370,2166,2167],{"class":1781}," *",[1370,2169,1346],{"class":1538},[1370,2171,1392],{"class":1391},[1370,2173,2174],{"class":1538},"Event",[1370,2176,2177],{"class":1391},")",[1370,2179,1618],{"class":1391},[1370,2181,2183,2186,2188,2191,2193,2195,2197,2200,2202,2205],{"class":1372,"line":2182},47,[1370,2184,2185],{"class":1381},"        order",[1370,2187,1404],{"class":1391},[1370,2189,2190],{"class":1381}," _",[1370,2192,1385],{"class":1381},[1370,2194,2013],{"class":1381},[1370,2196,1392],{"class":1391},[1370,2198,2199],{"class":1395},"From",[1370,2201,1398],{"class":1391},[1370,2203,2204],{"class":1381},"e",[1370,2206,1421],{"class":1391},[1370,2208,2210,2213,2215,2218,2220,2223,2227,2231,2234,2236,2239,2241,2244],{"class":1372,"line":2209},48,[1370,2211,2212],{"class":1381},"        fmt",[1370,2214,1392],{"class":1391},[1370,2216,2217],{"class":1395},"Printf",[1370,2219,1398],{"class":1391},[1370,2221,2222],{"class":1514},"\"Received order: ",[1370,2224,2226],{"class":2225},"scyPU","%s",[1370,2228,2230],{"class":2229},"suWN2","\\n",[1370,2232,2233],{"class":1514},"\"",[1370,2235,1404],{"class":1391},[1370,2237,2238],{"class":1381}," order",[1370,2240,1392],{"class":1391},[1370,2242,2243],{"class":1381},"ID",[1370,2245,1421],{"class":1391},[1370,2247,2249],{"class":1372,"line":2248},49,[1370,2250,1906],{"class":1391},[1370,2252,2254],{"class":1372,"line":2253},50,[1370,2255,1440],{"emptyLinePlaceholder":1439},[1370,2257,2259],{"class":1372,"line":2258},51,[1370,2260,2261],{"class":1375},"    // Emit an event — automatically published to Kafka\n",[1370,2263,2265,2267,2269,2272,2274,2276,2278,2280,2282,2284,2286,2289,2291,2293,2295,2297,2299,2302,2304,2307,2309,2313],{"class":1372,"line":2264},52,[1370,2266,2133],{"class":1381},[1370,2268,1392],{"class":1391},[1370,2270,2271],{"class":1395},"Emit",[1370,2273,1398],{"class":1391},[1370,2275,1492],{"class":1381},[1370,2277,1404],{"class":1391},[1370,2279,2008],{"class":1381},[1370,2281,1404],{"class":1391},[1370,2283,2013],{"class":1381},[1370,2285,1392],{"class":1391},[1370,2287,2288],{"class":1395},"Field",[1370,2290,1398],{"class":1391},[1370,2292,1746],{"class":1538},[1370,2294,1793],{"class":1391},[1370,2296,2243],{"class":1624},[1370,2298,1799],{"class":1391},[1370,2300,2301],{"class":1514}," \"ORD-123\"",[1370,2303,1404],{"class":1391},[1370,2305,2306],{"class":1624}," Total",[1370,2308,1799],{"class":1391},[1370,2310,2312],{"class":2311},"sMAmT"," 99.99",[1370,2314,2315],{"class":1391},"}))\n",[1370,2317,2319],{"class":1372,"line":2318},53,[1370,2320,1440],{"emptyLinePlaceholder":1439},[1370,2322,2324,2326,2328,2331],{"class":1372,"line":2323},54,[1370,2325,2133],{"class":1381},[1370,2327,1392],{"class":1391},[1370,2329,2330],{"class":1395},"Shutdown",[1370,2332,1433],{"class":1391},[1370,2334,2336],{"class":1372,"line":2335},55,[1370,2337,1649],{"class":1391},[1356,2339,27],{"id":2340},"capabilities",[2342,2343,2344,2360],"table",{},[2345,2346,2347],"thead",{},[2348,2349,2350,2354,2357],"tr",{},[2351,2352,2353],"th",{},"Feature",[2351,2355,2356],{},"Description",[2351,2358,2359],{},"Docs",[2361,2362,2363,2381,2394,2407,2420,2437,2449,2462],"tbody",{},[2348,2364,2365,2369,2372],{},[2366,2367,2368],"td",{},"Bidirectional Flow",[2366,2370,2371],{},"Publish capitan events to brokers or subscribe broker messages as events",[2366,2373,2374,2377,2378],{},[1275,2375,664],{"href":2376},"docs/learn/publishing",", ",[1275,2379,81],{"href":2380},"docs/learn/subscribing",[2348,2382,2383,2386,2389],{},[2366,2384,2385],{},"Type-Safe Generics",[2366,2387,2388],{},"Compile-time checked publishers and subscribers",[2366,2390,2391],{},[1275,2392,6],{"href":2393},"docs/overview",[2348,2395,2396,2399,2402],{},[2366,2397,2398],{},"11 Providers",[2366,2400,2401],{},"Kafka, NATS, JetStream, Pub/Sub, Redis, SQS, AMQP, SNS, Bolt, Firestore, io",[2366,2403,2404],{},[1275,2405,120],{"href":2406},"docs/learn/providers",[2348,2408,2409,2412,2415],{},[2366,2410,2411],{},"Pipeline Middleware",[2366,2413,2414],{},"Validation, transformation, and side effects via processors",[2366,2416,2417],{},[1275,2418,43],{"href":2419},"docs/guides/reliability",[2348,2421,2422,2425,2433],{},[2366,2423,2424],{},"Reliability Patterns",[2366,2426,2427,2428],{},"Retry, backoff, timeout, circuit breaker, rate limiting via ",[1275,2429,2432],{"href":2430,"rel":2431},"https://github.com/zoobz-io/pipz",[1279],"pipz",[2366,2434,2435],{},[1275,2436,43],{"href":2419},[2348,2438,2439,2442,2445],{},[2366,2440,2441],{},"Auto Acknowledgment",[2366,2443,2444],{},"Messages acked/nacked based on processing outcome",[2366,2446,2447],{},[1275,2448,81],{"href":2380},[2348,2450,2451,2454,2457],{},[2366,2452,2453],{},"Custom Codecs",[2366,2455,2456],{},"Pluggable serialization (JSON default, custom supported)",[2366,2458,2459],{},[1275,2460,155],{"href":2461},"docs/guides/codecs",[2348,2463,2464,2467,2474],{},[2366,2465,2466],{},"Error Observability",[2366,2468,2469,2470,2473],{},"All errors emit as ",[1275,2471,1346],{"href":1344,"rel":2472},[1279]," events",[2366,2475,2476],{},[1275,2477,165],{"href":2478},"docs/guides/errors",[1356,2480,2482],{"id":2481},"why-herald","Why herald?",[2484,2485,2486,2494,2500,2506,2512],"ul",{},[2487,2488,2489,2493],"li",{},[2490,2491,2492],"strong",{},"Type-safe"," — Generic publishers and subscribers with compile-time checking",[2487,2495,2496,2499],{},[2490,2497,2498],{},"Bidirectional"," — Publish to brokers or subscribe from brokers",[2487,2501,2502,2505],{},[2490,2503,2504],{},"11 providers"," — Kafka, NATS, JetStream, Pub/Sub, Redis, SQS, RabbitMQ, SNS, BoltDB, Firestore, io",[2487,2507,2508,2511],{},[2490,2509,2510],{},"Reliable"," — Pipeline middleware for retry, backoff, timeout, circuit breaker, rate limiting",[2487,2513,2514,2517,2518],{},[2490,2515,2516],{},"Observable"," — Errors flow through ",[1275,2519,1346],{"href":1344,"rel":2520},[1279],[1356,2522,2524],{"id":2523},"unified-event-flow","Unified Event Flow",[1272,2526,2527,2528,1392],{},"Herald enables a pattern: ",[2490,2529,2530],{},"internal events become external messages, external messages become internal events",[1272,2532,2533,2534,2537],{},"Your application emits ",[1275,2535,1346],{"href":1344,"rel":2536},[1279]," 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.",[1361,2539,2541],{"className":1363,"code":2540,"language":1365,"meta":34,"style":34},"// 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",[1367,2542,2543,2548,2579,2583,2588],{"__ignoreMap":34},[1370,2544,2545],{"class":1372,"line":9},[1370,2546,2547],{"class":1375},"// Service A: emit locally, publish externally\n",[1370,2549,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568,2570,2572,2574,2577],{"class":1372,"line":19},[1370,2551,1346],{"class":1381},[1370,2553,1392],{"class":1391},[1370,2555,2271],{"class":1395},[1370,2557,1398],{"class":1391},[1370,2559,1492],{"class":1381},[1370,2561,1404],{"class":1391},[1370,2563,2008],{"class":1381},[1370,2565,1404],{"class":1391},[1370,2567,2013],{"class":1381},[1370,2569,1392],{"class":1391},[1370,2571,2288],{"class":1395},[1370,2573,1398],{"class":1391},[1370,2575,2576],{"class":1381},"order",[1370,2578,1958],{"class":1391},[1370,2580,2581],{"class":1372,"line":40},[1370,2582,1440],{"emptyLinePlaceholder":1439},[1370,2584,2585],{"class":1372,"line":1436},[1370,2586,2587],{"class":1375},"// Service B: subscribe externally, handle locally\n",[1370,2589,2590,2592,2594,2596,2598,2600,2602,2605],{"class":1372,"line":1443},[1370,2591,1346],{"class":1381},[1370,2593,1392],{"class":1391},[1370,2595,2138],{"class":1395},[1370,2597,1398],{"class":1391},[1370,2599,2143],{"class":1381},[1370,2601,1404],{"class":1391},[1370,2603,2604],{"class":1381}," processOrder",[1370,2606,1421],{"class":1391},[1272,2608,2609],{},"Two services, one event type, zero coupling. The broker is just a transport.",[1356,2611,120],{"id":2612},"providers",[2342,2614,2615,2627],{},[2345,2616,2617],{},[2348,2618,2619,2621,2624],{},[2351,2620,823],{},[2351,2622,2623],{},"Package",[2351,2625,2626],{},"Use Case",[2361,2628,2629,2643,2657,2671,2685,2699,2713,2727,2741,2755,2769],{},[2348,2630,2631,2633,2640],{},[2366,2632,1041],{},[2366,2634,2635],{},[1275,2636,2638],{"href":2637},"kafka",[1367,2639,2637],{},[2366,2641,2642],{},"High-throughput streaming",[2348,2644,2645,2647,2654],{},[2366,2646,1061],{},[2366,2648,2649],{},[1275,2650,2652],{"href":2651},"nats",[1367,2653,2651],{},[2366,2655,2656],{},"Lightweight cloud messaging",[2348,2658,2659,2661,2668],{},[2366,2660,1074],{},[2366,2662,2663],{},[1275,2664,2666],{"href":2665},"jetstream",[1367,2667,2665],{},[2366,2669,2670],{},"NATS with persistence and headers",[2348,2672,2673,2675,2682],{},[2366,2674,1086],{},[2366,2676,2677],{},[1275,2678,2680],{"href":2679},"pubsub",[1367,2681,2679],{},[2366,2683,2684],{},"GCP managed messaging",[2348,2686,2687,2689,2696],{},[2366,2688,1099],{},[2366,2690,2691],{},[1275,2692,2694],{"href":2693},"redis",[1367,2695,2693],{},[2366,2697,2698],{},"In-memory with persistence",[2348,2700,2701,2703,2710],{},[2366,2702,1112],{},[2366,2704,2705],{},[1275,2706,2708],{"href":2707},"sqs",[1367,2709,2707],{},[2366,2711,2712],{},"AWS managed queues",[2348,2714,2715,2717,2724],{},[2366,2716,1125],{},[2366,2718,2719],{},[1275,2720,2722],{"href":2721},"amqp",[1367,2723,2721],{},[2366,2725,2726],{},"Traditional message broker",[2348,2728,2729,2731,2738],{},[2366,2730,1138],{},[2366,2732,2733],{},[1275,2734,2736],{"href":2735},"sns",[1367,2737,2735],{},[2366,2739,2740],{},"Pub/sub fanout",[2348,2742,2743,2745,2752],{},[2366,2744,1151],{},[2366,2746,2747],{},[1275,2748,2750],{"href":2749},"bolt",[1367,2751,2749],{},[2366,2753,2754],{},"Embedded local queues",[2348,2756,2757,2759,2766],{},[2366,2758,1164],{},[2366,2760,2761],{},[1275,2762,2764],{"href":2763},"firestore",[1367,2765,2763],{},[2366,2767,2768],{},"Firebase/GCP document store",[2348,2770,2771,2773,2779],{},[2366,2772,1177],{},[2366,2774,2775],{},[1275,2776,2777],{"href":1177},[1367,2778,1177],{},[2366,2780,2781],{},"Testing with io.Reader/Writer",[1356,2783,2785],{"id":2784},"processing-hooks","Processing Hooks",[1272,2787,2788],{},"Add processing steps via middleware processors:",[1361,2790,2792],{"className":1363,"code":2791,"language":1365,"meta":34,"style":34},"pub := 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",[1367,2793,2794,2836,2848,2926,2951,2974,2979,2991,2996,3049,3081,3087,3091,3157,3184,3191,3195,3200],{"__ignoreMap":34},[1370,2795,2796,2798,2800,2802,2804,2806,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826,2829,2831,2833],{"class":1372,"line":9},[1370,2797,1382],{"class":1381},[1370,2799,1385],{"class":1381},[1370,2801,1388],{"class":1381},[1370,2803,1392],{"class":1391},[1370,2805,851],{"class":1395},[1370,2807,1398],{"class":1391},[1370,2809,1401],{"class":1381},[1370,2811,1404],{"class":1391},[1370,2813,1407],{"class":1381},[1370,2815,1404],{"class":1391},[1370,2817,1412],{"class":1381},[1370,2819,1404],{"class":1391},[1370,2821,1863],{"class":1391},[1370,2823,1270],{"class":1538},[1370,2825,1392],{"class":1391},[1370,2827,2828],{"class":1538},"Option",[1370,2830,1743],{"class":1391},[1370,2832,1746],{"class":1538},[1370,2834,2835],{"class":1391},"]{\n",[1370,2837,2838,2841,2843,2845],{"class":1372,"line":19},[1370,2839,2840],{"class":1381},"    herald",[1370,2842,1392],{"class":1391},[1370,2844,943],{"class":1395},[1370,2846,2847],{"class":1391},"(\n",[1370,2849,2850,2853,2855,2857,2859,2861,2863,2866,2868,2870,2872,2874,2876,2878,2880,2882,2885,2887,2889,2891,2893,2895,2897,2900,2903,2906,2908,2910,2912,2914,2916,2919,2922,2924],{"class":1372,"line":40},[1370,2851,2852],{"class":1381},"        herald",[1370,2854,1392],{"class":1391},[1370,2856,301],{"class":1395},[1370,2858,1743],{"class":1391},[1370,2860,1746],{"class":1538},[1370,2862,1749],{"class":1391},[1370,2864,2865],{"class":1514},"\"validate\"",[1370,2867,1404],{"class":1391},[1370,2869,2148],{"class":1417},[1370,2871,1398],{"class":1391},[1370,2873,1492],{"class":1571},[1370,2875,1679],{"class":1538},[1370,2877,1392],{"class":1391},[1370,2879,2159],{"class":1538},[1370,2881,1404],{"class":1391},[1370,2883,2884],{"class":1571}," env",[1370,2886,2167],{"class":1781},[1370,2888,1270],{"class":1538},[1370,2890,1392],{"class":1391},[1370,2892,1000],{"class":1538},[1370,2894,1743],{"class":1391},[1370,2896,1746],{"class":1538},[1370,2898,2899],{"class":1391},"])",[1370,2901,2902],{"class":1391}," (",[1370,2904,2905],{"class":1781},"*",[1370,2907,1270],{"class":1538},[1370,2909,1392],{"class":1391},[1370,2911,1000],{"class":1538},[1370,2913,1743],{"class":1391},[1370,2915,1746],{"class":1538},[1370,2917,2918],{"class":1391},"],",[1370,2920,2921],{"class":1538}," error",[1370,2923,2177],{"class":1391},[1370,2925,1618],{"class":1391},[1370,2927,2928,2931,2933,2935,2938,2940,2943,2946,2949],{"class":1372,"line":1436},[1370,2929,2930],{"class":1781},"            if",[1370,2932,2884],{"class":1381},[1370,2934,1392],{"class":1391},[1370,2936,2937],{"class":1381},"Value",[1370,2939,1392],{"class":1391},[1370,2941,2942],{"class":1381},"Total",[1370,2944,2945],{"class":1781}," \u003C",[1370,2947,2948],{"class":2311}," 0",[1370,2950,1618],{"class":1391},[1370,2952,2953,2956,2958,2960,2963,2965,2967,2969,2972],{"class":1372,"line":1443},[1370,2954,2955],{"class":1781},"                return",[1370,2957,2884],{"class":1381},[1370,2959,1404],{"class":1391},[1370,2961,2962],{"class":1381}," errors",[1370,2964,1392],{"class":1391},[1370,2966,1922],{"class":1395},[1370,2968,1398],{"class":1391},[1370,2970,2971],{"class":1514},"\"invalid total\"",[1370,2973,1421],{"class":1391},[1370,2975,2976],{"class":1372,"line":1449},[1370,2977,2978],{"class":1391},"            }\n",[1370,2980,2981,2984,2986,2988],{"class":1372,"line":1481},[1370,2982,2983],{"class":1781},"            return",[1370,2985,2884],{"class":1381},[1370,2987,1404],{"class":1391},[1370,2989,2990],{"class":1417}," nil\n",[1370,2992,2993],{"class":1372,"line":1578},[1370,2994,2995],{"class":1391},"        }),\n",[1370,2997,2998,3000,3002,3004,3006,3008,3010,3013,3015,3017,3019,3021,3023,3025,3027,3029,3031,3033,3035,3037,3039,3041,3043,3045,3047],{"class":1372,"line":1584},[1370,2999,2852],{"class":1381},[1370,3001,1392],{"class":1391},[1370,3003,306],{"class":1395},[1370,3005,1743],{"class":1391},[1370,3007,1746],{"class":1538},[1370,3009,1749],{"class":1391},[1370,3011,3012],{"class":1514},"\"log\"",[1370,3014,1404],{"class":1391},[1370,3016,2148],{"class":1417},[1370,3018,1398],{"class":1391},[1370,3020,1492],{"class":1571},[1370,3022,1679],{"class":1538},[1370,3024,1392],{"class":1391},[1370,3026,2159],{"class":1538},[1370,3028,1404],{"class":1391},[1370,3030,2884],{"class":1571},[1370,3032,2167],{"class":1781},[1370,3034,1270],{"class":1538},[1370,3036,1392],{"class":1391},[1370,3038,1000],{"class":1538},[1370,3040,1743],{"class":1391},[1370,3042,1746],{"class":1538},[1370,3044,2899],{"class":1391},[1370,3046,2921],{"class":1538},[1370,3048,1618],{"class":1391},[1370,3050,3051,3054,3056,3058,3060,3063,3065,3067,3069,3071,3073,3075,3077,3079],{"class":1372,"line":1590},[1370,3052,3053],{"class":1381},"            log",[1370,3055,1392],{"class":1391},[1370,3057,2217],{"class":1395},[1370,3059,1398],{"class":1391},[1370,3061,3062],{"class":1514},"\"order ",[1370,3064,2226],{"class":2225},[1370,3066,2233],{"class":1514},[1370,3068,1404],{"class":1391},[1370,3070,2884],{"class":1381},[1370,3072,1392],{"class":1391},[1370,3074,2937],{"class":1381},[1370,3076,1392],{"class":1391},[1370,3078,2243],{"class":1381},[1370,3080,1421],{"class":1391},[1370,3082,3083,3085],{"class":1372,"line":1596},[1370,3084,2983],{"class":1781},[1370,3086,2990],{"class":1417},[1370,3088,3089],{"class":1372,"line":1601},[1370,3090,2995],{"class":1391},[1370,3092,3093,3095,3097,3099,3101,3103,3105,3108,3110,3112,3114,3116,3118,3120,3122,3124,3126,3128,3130,3132,3134,3136,3138,3140,3142,3144,3146,3148,3150,3152,3155],{"class":1372,"line":1606},[1370,3094,2852],{"class":1381},[1370,3096,1392],{"class":1391},[1370,3098,311],{"class":1395},[1370,3100,1743],{"class":1391},[1370,3102,1746],{"class":1538},[1370,3104,1749],{"class":1391},[1370,3106,3107],{"class":1514},"\"enrich\"",[1370,3109,1404],{"class":1391},[1370,3111,2148],{"class":1417},[1370,3113,1398],{"class":1391},[1370,3115,1492],{"class":1571},[1370,3117,1679],{"class":1538},[1370,3119,1392],{"class":1391},[1370,3121,2159],{"class":1538},[1370,3123,1404],{"class":1391},[1370,3125,2884],{"class":1571},[1370,3127,2167],{"class":1781},[1370,3129,1270],{"class":1538},[1370,3131,1392],{"class":1391},[1370,3133,1000],{"class":1538},[1370,3135,1743],{"class":1391},[1370,3137,1746],{"class":1538},[1370,3139,2899],{"class":1391},[1370,3141,2167],{"class":1781},[1370,3143,1270],{"class":1538},[1370,3145,1392],{"class":1391},[1370,3147,1000],{"class":1538},[1370,3149,1743],{"class":1391},[1370,3151,1746],{"class":1538},[1370,3153,3154],{"class":1391},"]",[1370,3156,1618],{"class":1391},[1370,3158,3159,3162,3164,3166,3168,3171,3174,3177,3179,3182],{"class":1372,"line":1621},[1370,3160,3161],{"class":1381},"            env",[1370,3163,1392],{"class":1391},[1370,3165,2937],{"class":1381},[1370,3167,1392],{"class":1391},[1370,3169,3170],{"class":1381},"ProcessedAt",[1370,3172,3173],{"class":1381}," =",[1370,3175,3176],{"class":1381}," time",[1370,3178,1392],{"class":1391},[1370,3180,3181],{"class":1395},"Now",[1370,3183,1433],{"class":1391},[1370,3185,3186,3188],{"class":1372,"line":1634},[1370,3187,2983],{"class":1781},[1370,3189,3190],{"class":1381}," env\n",[1370,3192,3193],{"class":1372,"line":1646},[1370,3194,2995],{"class":1391},[1370,3196,3197],{"class":1372,"line":1652},[1370,3198,3199],{"class":1391},"    ),\n",[1370,3201,3202],{"class":1372,"line":1657},[1370,3203,3204],{"class":1391},"})\n",[2484,3206,3207,3212,3217],{},[2487,3208,3209,3211],{},[1367,3210,301],{}," — Transform envelope with possible error",[2487,3213,3214,3216],{},[1367,3215,306],{}," — Side effect, envelope passes through unchanged",[2487,3218,3219,3221],{},[1367,3220,311],{}," — Pure transform, cannot fail",[1356,3223,145],{"id":3224},"pipeline-options",[1272,3226,3227,3228,1799],{},"Add reliability features via ",[1275,3229,2432],{"href":2430,"rel":3230},[1279],[1361,3232,3234],{"className":1363,"code":3233,"language":1365,"meta":34,"style":34},"pub := 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",[1367,3235,3236,3276,3296,3327,3353,3382,3406],{"__ignoreMap":34},[1370,3237,3238,3240,3242,3244,3246,3248,3250,3252,3254,3256,3258,3260,3262,3264,3266,3268,3270,3272,3274],{"class":1372,"line":9},[1370,3239,1382],{"class":1381},[1370,3241,1385],{"class":1381},[1370,3243,1388],{"class":1381},[1370,3245,1392],{"class":1391},[1370,3247,851],{"class":1395},[1370,3249,1398],{"class":1391},[1370,3251,1401],{"class":1381},[1370,3253,1404],{"class":1391},[1370,3255,1407],{"class":1381},[1370,3257,1404],{"class":1391},[1370,3259,1412],{"class":1381},[1370,3261,1404],{"class":1391},[1370,3263,1863],{"class":1391},[1370,3265,1270],{"class":1538},[1370,3267,1392],{"class":1391},[1370,3269,2828],{"class":1538},[1370,3271,1743],{"class":1391},[1370,3273,1746],{"class":1538},[1370,3275,2835],{"class":1391},[1370,3277,3278,3280,3282,3284,3286,3288,3290,3293],{"class":1372,"line":19},[1370,3279,2840],{"class":1381},[1370,3281,1392],{"class":1391},[1370,3283,899],{"class":1395},[1370,3285,1743],{"class":1391},[1370,3287,1746],{"class":1538},[1370,3289,1749],{"class":1391},[1370,3291,3292],{"class":2311},"3",[1370,3294,3295],{"class":1391},"),\n",[1370,3297,3298,3300,3302,3304,3306,3308,3310,3312,3314,3317,3320,3322,3325],{"class":1372,"line":40},[1370,3299,2840],{"class":1381},[1370,3301,1392],{"class":1391},[1370,3303,904],{"class":1395},[1370,3305,1743],{"class":1391},[1370,3307,1746],{"class":1538},[1370,3309,1749],{"class":1391},[1370,3311,3292],{"class":2311},[1370,3313,1404],{"class":1391},[1370,3315,3316],{"class":2311}," 100",[1370,3318,3319],{"class":1381},"*time",[1370,3321,1392],{"class":1391},[1370,3323,3324],{"class":1381},"Millisecond",[1370,3326,3295],{"class":1391},[1370,3328,3329,3331,3333,3335,3337,3339,3341,3344,3346,3348,3351],{"class":1372,"line":1436},[1370,3330,2840],{"class":1381},[1370,3332,1392],{"class":1391},[1370,3334,909],{"class":1395},[1370,3336,1743],{"class":1391},[1370,3338,1746],{"class":1538},[1370,3340,1749],{"class":1391},[1370,3342,3343],{"class":2311},"5",[1370,3345,3319],{"class":1381},[1370,3347,1392],{"class":1391},[1370,3349,3350],{"class":1381},"Second",[1370,3352,3295],{"class":1391},[1370,3354,3355,3357,3359,3361,3363,3365,3367,3369,3371,3374,3376,3378,3380],{"class":1372,"line":1443},[1370,3356,2840],{"class":1381},[1370,3358,1392],{"class":1391},[1370,3360,914],{"class":1395},[1370,3362,1743],{"class":1391},[1370,3364,1746],{"class":1538},[1370,3366,1749],{"class":1391},[1370,3368,3343],{"class":2311},[1370,3370,1404],{"class":1391},[1370,3372,3373],{"class":2311}," 30",[1370,3375,3319],{"class":1381},[1370,3377,1392],{"class":1391},[1370,3379,3350],{"class":1381},[1370,3381,3295],{"class":1391},[1370,3383,3384,3386,3388,3390,3392,3394,3396,3399,3401,3404],{"class":1372,"line":1449},[1370,3385,2840],{"class":1381},[1370,3387,1392],{"class":1391},[1370,3389,919],{"class":1395},[1370,3391,1743],{"class":1391},[1370,3393,1746],{"class":1538},[1370,3395,1749],{"class":1391},[1370,3397,3398],{"class":2311},"100",[1370,3400,1404],{"class":1391},[1370,3402,3403],{"class":2311}," 10",[1370,3405,3295],{"class":1391},[1370,3407,3408],{"class":1372,"line":1481},[1370,3409,3204],{"class":1391},[1272,3411,3412,3413,3416],{},"See ",[1275,3414,3415],{"href":2419},"Reliability Guide"," for middleware and pipeline details.",[1356,3418,160],{"id":3419},"acknowledgment",[1272,3421,3422],{},"Herald handles message acknowledgment automatically:",[2342,3424,3425,3435],{},[2345,3426,3427],{},[2348,3428,3429,3432],{},[2351,3430,3431],{},"Outcome",[2351,3433,3434],{},"Action",[2361,3436,3437,3448,3459],{},[2348,3438,3439,3442],{},[2366,3440,3441],{},"Message processed successfully",[2366,3443,3444,3447],{},[1367,3445,3446],{},"Ack()"," — Message acknowledged",[2348,3449,3450,3453],{},[2366,3451,3452],{},"Deserialization fails",[2366,3454,3455,3458],{},[1367,3456,3457],{},"Nack()"," — Message returned for redelivery",[2348,3460,3461,3464],{},[2366,3462,3463],{},"Provider doesn't support ack",[2366,3465,3466],{},"No-op (e.g., NATS core, SNS)",[1356,3468,165],{"id":3469},"error-handling",[1272,3471,3472],{},"All errors flow through capitan:",[1361,3474,3476],{"className":1363,"code":3475,"language":1365,"meta":34,"style":34},"capitan.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",[1367,3477,3478,3524,3551,3596],{"__ignoreMap":34},[1370,3479,3480,3482,3484,3486,3488,3490,3492,3494,3496,3498,3500,3502,3504,3506,3508,3510,3512,3514,3516,3518,3520,3522],{"class":1372,"line":9},[1370,3481,1346],{"class":1381},[1370,3483,1392],{"class":1391},[1370,3485,2138],{"class":1395},[1370,3487,1398],{"class":1391},[1370,3489,1270],{"class":1381},[1370,3491,1392],{"class":1391},[1370,3493,1013],{"class":1381},[1370,3495,1404],{"class":1391},[1370,3497,2148],{"class":1417},[1370,3499,1398],{"class":1391},[1370,3501,1492],{"class":1571},[1370,3503,1679],{"class":1538},[1370,3505,1392],{"class":1391},[1370,3507,2159],{"class":1538},[1370,3509,1404],{"class":1391},[1370,3511,2164],{"class":1571},[1370,3513,2167],{"class":1781},[1370,3515,1346],{"class":1538},[1370,3517,1392],{"class":1391},[1370,3519,2174],{"class":1538},[1370,3521,2177],{"class":1391},[1370,3523,1618],{"class":1391},[1370,3525,3526,3529,3531,3533,3535,3537,3539,3541,3543,3545,3547,3549],{"class":1372,"line":19},[1370,3527,3528],{"class":1381},"    err",[1370,3530,1404],{"class":1391},[1370,3532,2190],{"class":1381},[1370,3534,1385],{"class":1381},[1370,3536,1388],{"class":1381},[1370,3538,1392],{"class":1391},[1370,3540,1018],{"class":1381},[1370,3542,1392],{"class":1391},[1370,3544,2199],{"class":1395},[1370,3546,1398],{"class":1391},[1370,3548,2204],{"class":1381},[1370,3550,1421],{"class":1391},[1370,3552,3553,3556,3558,3560,3562,3565,3567,3570,3573,3575,3577,3580,3582,3585,3587,3589,3591,3594],{"class":1372,"line":40},[1370,3554,3555],{"class":1381},"    log",[1370,3557,1392],{"class":1391},[1370,3559,2217],{"class":1395},[1370,3561,1398],{"class":1391},[1370,3563,3564],{"class":1514},"\"[herald] ",[1370,3566,2226],{"class":2225},[1370,3568,3569],{"class":1514},": ",[1370,3571,3572],{"class":2225},"%v",[1370,3574,2233],{"class":1514},[1370,3576,1404],{"class":1391},[1370,3578,3579],{"class":1381}," err",[1370,3581,1392],{"class":1391},[1370,3583,3584],{"class":1381},"Operation",[1370,3586,1404],{"class":1391},[1370,3588,3579],{"class":1381},[1370,3590,1392],{"class":1391},[1370,3592,3593],{"class":1381},"Err",[1370,3595,1421],{"class":1391},[1370,3597,3598],{"class":1372,"line":1436},[1370,3599,3204],{"class":1391},[1272,3601,3412,3602,3605],{},[1275,3603,3604],{"href":2478},"Error Handling Guide"," for details.",[1356,3607,3609],{"id":3608},"documentation","Documentation",[1272,3611,3612,3613,3616],{},"Full documentation is available in the ",[1275,3614,3615],{"href":3615},"docs/"," directory:",[3618,3619,1202],"h3",{"id":3620},"learn",[2484,3622,3623,3628,3633,3638],{},[2487,3624,3625,3627],{},[1275,3626,6],{"href":2393}," — Architecture and philosophy",[2487,3629,3630,3632],{},[1275,3631,664],{"href":2376}," — Forward capitan events to brokers",[2487,3634,3635,3637],{},[1275,3636,81],{"href":2380}," — Consume broker messages as capitan events",[2487,3639,3640,3642],{},[1275,3641,120],{"href":2406}," — Available broker implementations",[3618,3644,1214],{"id":3645},"guides",[2484,3647,3648,3653,3658,3663],{},[2487,3649,3650,3652],{},[1275,3651,43],{"href":2419}," — Retry, backoff, circuit breaker, rate limiting",[2487,3654,3655,3657],{},[1275,3656,155],{"href":2461}," — Custom serialization formats",[2487,3659,3660,3662],{},[1275,3661,165],{"href":2478}," — Centralized error management",[2487,3664,3665,3668],{},[1275,3666,573],{"href":3667},"docs/guides/testing"," — Testing herald-based applications",[3618,3670,1231],{"id":3671},"reference",[2484,3673,3674,3680],{},[2487,3675,3676,3679],{},[1275,3677,810],{"href":3678},"docs/reference/api"," — Complete function and type documentation",[2487,3681,3682,3685],{},[1275,3683,1032],{"href":3684},"docs/reference/providers"," — Provider configuration details",[1356,3687,3689],{"id":3688},"contributing","Contributing",[1272,3691,3412,3692,3696],{},[1275,3693,3695],{"href":3694},"CONTRIBUTING","CONTRIBUTING.md"," for guidelines.",[1356,3698,1322],{"id":3699},"license",[1272,3701,3702,3703,3605],{},"MIT License — see ",[1275,3704,1319],{"href":1319},[3706,3707,3708],"style",{},"html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"title":34,"searchDepth":19,"depth":19,"links":3710},[3711,3712,3713,3714,3715,3716,3717,3718,3719,3720,3721,3722,3727,3728],{"id":1358,"depth":19,"text":1359},{"id":1500,"depth":19,"text":66},{"id":1524,"depth":19,"text":1525},{"id":2340,"depth":19,"text":27},{"id":2481,"depth":19,"text":2482},{"id":2523,"depth":19,"text":2524},{"id":2612,"depth":19,"text":120},{"id":2784,"depth":19,"text":2785},{"id":3224,"depth":19,"text":145},{"id":3419,"depth":19,"text":160},{"id":3469,"depth":19,"text":165},{"id":3608,"depth":19,"text":3609,"children":3723},[3724,3725,3726],{"id":3620,"depth":40,"text":1202},{"id":3645,"depth":40,"text":1214},{"id":3671,"depth":40,"text":1231},{"id":3688,"depth":19,"text":3689},{"id":3699,"depth":19,"text":1322},"md","book-open",{},"/readme",{"title":1263,"description":34},"readme","xytYJgGPdSh9CtGTaDgk5KOYelSTph5QDdA7D1wKst4",{"id":3737,"title":3738,"body":3739,"description":34,"extension":3729,"icon":4175,"meta":4176,"navigation":1439,"path":4177,"seo":4178,"stem":4179,"__hash__":4180},"resources/security.md","Security",{"type":1265,"value":3740,"toc":4161},[3741,3745,3749,3752,3791,3795,3798,3802,3807,3810,3849,3853,3856,3905,3909,3934,3938,3941,3945,3948,4054,4058,4061,4092,4096,4099,4118,4122,4136,4140,4143,4149,4152,4158],[1268,3742,3744],{"id":3743},"security-policy","Security Policy",[1356,3746,3748],{"id":3747},"supported-versions","Supported Versions",[1272,3750,3751],{},"We release patches for security vulnerabilities. Which versions are eligible for receiving such patches depends on the CVSS v3.0 Rating:",[2342,3753,3754,3767],{},[2345,3755,3756],{},[2348,3757,3758,3761,3764],{},[2351,3759,3760],{},"Version",[2351,3762,3763],{},"Supported",[2351,3765,3766],{},"Status",[2361,3768,3769,3780],{},[2348,3770,3771,3774,3777],{},[2366,3772,3773],{},"latest",[2366,3775,3776],{},"✅",[2366,3778,3779],{},"Active development",[2348,3781,3782,3785,3788],{},[2366,3783,3784],{},"\u003C latest",[2366,3786,3787],{},"❌",[2366,3789,3790],{},"Security fixes only for critical issues",[1356,3792,3794],{"id":3793},"reporting-a-vulnerability","Reporting a Vulnerability",[1272,3796,3797],{},"We take the security of herald seriously. If you have discovered a security vulnerability in this project, please report it responsibly.",[3618,3799,3801],{"id":3800},"how-to-report","How to Report",[1272,3803,3804],{},[2490,3805,3806],{},"Please DO NOT report security vulnerabilities through public GitHub issues.",[1272,3808,3809],{},"Instead, please report them via one of the following methods:",[3811,3812,3813,3836],"ol",{},[2487,3814,3815,3818,3819],{},[2490,3816,3817],{},"GitHub Security Advisories"," (Preferred)",[2484,3820,3821,3830,3833],{},[2487,3822,3823,3824,3829],{},"Go to the ",[1275,3825,3828],{"href":3826,"rel":3827},"https://github.com/zoobz-io/herald/security",[1279],"Security tab"," of this repository",[2487,3831,3832],{},"Click \"Report a vulnerability\"",[2487,3834,3835],{},"Fill out the form with details about the vulnerability",[2487,3837,3838,3841],{},[2490,3839,3840],{},"Email",[2484,3842,3843,3846],{},[2487,3844,3845],{},"Send details to the repository maintainer through GitHub profile contact information",[2487,3847,3848],{},"Use PGP encryption if possible for sensitive details",[3618,3850,3852],{"id":3851},"what-to-include","What to Include",[1272,3854,3855],{},"Please include the following information (as much as you can provide) to help us better understand the nature and scope of the possible issue:",[2484,3857,3858,3864,3870,3876,3882,3887,3893,3899],{},[2487,3859,3860,3863],{},[2490,3861,3862],{},"Type of issue"," (e.g., race condition, deadlock, memory leak, etc.)",[2487,3865,3866,3869],{},[2490,3867,3868],{},"Full paths of source file(s)"," related to the manifestation of the issue",[2487,3871,3872,3875],{},[2490,3873,3874],{},"The location of the affected source code"," (tag/branch/commit or direct URL)",[2487,3877,3878,3881],{},[2490,3879,3880],{},"Any special configuration required"," to reproduce the issue",[2487,3883,3884,3881],{},[2490,3885,3886],{},"Step-by-step instructions",[2487,3888,3889,3892],{},[2490,3890,3891],{},"Proof-of-concept or exploit code"," (if possible)",[2487,3894,3895,3898],{},[2490,3896,3897],{},"Impact of the issue",", including how an attacker might exploit the issue",[2487,3900,3901,3904],{},[2490,3902,3903],{},"Your name and affiliation"," (optional)",[3618,3906,3908],{"id":3907},"what-to-expect","What to Expect",[2484,3910,3911,3916,3922,3928],{},[2487,3912,3913,3915],{},[2490,3914,160],{},": We will acknowledge receipt of your vulnerability report within 48 hours",[2487,3917,3918,3921],{},[2490,3919,3920],{},"Initial Assessment",": Within 7 days, we will provide an initial assessment of the report",[2487,3923,3924,3927],{},[2490,3925,3926],{},"Resolution Timeline",": We aim to resolve critical issues within 30 days",[2487,3929,3930,3933],{},[2490,3931,3932],{},"Disclosure",": We will coordinate with you on the disclosure timeline",[3618,3935,3937],{"id":3936},"preferred-languages","Preferred Languages",[1272,3939,3940],{},"We prefer all communications to be in English.",[1356,3942,3944],{"id":3943},"security-best-practices","Security Best Practices",[1272,3946,3947],{},"When using herald in your applications, we recommend:",[3811,3949,3950,3971,3987,4006,4022,4038],{},[2487,3951,3952,3955],{},[2490,3953,3954],{},"Keep Dependencies Updated",[1361,3956,3958],{"className":1503,"code":3957,"language":1505,"meta":34,"style":34},"go get -u github.com/zoobz-io/herald\n",[1367,3959,3960],{"__ignoreMap":34},[1370,3961,3962,3964,3966,3969],{"class":1372,"line":9},[1370,3963,1365],{"class":1395},[1370,3965,1515],{"class":1514},[1370,3967,3968],{"class":1417}," -u",[1370,3970,1518],{"class":1514},[2487,3972,3973,3976],{},[2490,3974,3975],{},"Resource Management",[2484,3977,3978,3981,3984],{},[2487,3979,3980],{},"Close publishers and subscribers when no longer needed",[2487,3982,3983],{},"Close providers to release broker connections",[2487,3985,3986],{},"Handle context cancellation properly",[2487,3988,3989,3991],{},[2490,3990,165],{},[2484,3992,3993,4000,4003],{},[2487,3994,3995,3996,3999],{},"Hook into ",[1367,3997,3998],{},"herald.ErrorSignal"," for operational errors",[2487,4001,4002],{},"Implement proper error handling in capitan hooks",[2487,4004,4005],{},"Log errors appropriately",[2487,4007,4008,4011],{},[2490,4009,4010],{},"Input Validation",[2484,4012,4013,4016,4019],{},[2487,4014,4015],{},"Validate message payloads in subscribers",[2487,4017,4018],{},"Use type assertions safely",[2487,4020,4021],{},"Handle deserialization errors gracefully",[2487,4023,4024,4027],{},[2490,4025,4026],{},"Broker Security",[2484,4028,4029,4032,4035],{},[2487,4030,4031],{},"Use TLS for broker connections",[2487,4033,4034],{},"Configure authentication where supported",[2487,4036,4037],{},"Follow broker-specific security guidelines",[2487,4039,4040,4043],{},[2490,4041,4042],{},"Codec Security",[2484,4044,4045,4048,4051],{},[2487,4046,4047],{},"Be cautious with custom codecs that deserialize untrusted data",[2487,4049,4050],{},"Consider payload size limits",[2487,4052,4053],{},"Validate deserialized data",[1356,4055,4057],{"id":4056},"security-features","Security Features",[1272,4059,4060],{},"herald includes several built-in security features:",[2484,4062,4063,4068,4074,4080,4086],{},[2487,4064,4065,4067],{},[2490,4066,37],{},": Generic publishers/subscribers provide compile-time type checking",[2487,4069,4070,4073],{},[2490,4071,4072],{},"Error Isolation",": Codec errors emit to ErrorSignal without crashing",[2487,4075,4076,4079],{},[2490,4077,4078],{},"Ack/Nack Semantics",": Failed messages can be redelivered",[2487,4081,4082,4085],{},[2490,4083,4084],{},"Metadata Immutability",": Context metadata is copied to prevent mutation",[2487,4087,4088,4091],{},[2490,4089,4090],{},"Nil Guards",": Nil codec defaults to safe JSON implementation",[1356,4093,4095],{"id":4094},"automated-security-scanning","Automated Security Scanning",[1272,4097,4098],{},"This project uses:",[2484,4100,4101,4106,4112],{},[2487,4102,4103,4105],{},[2490,4104,1307],{},": GitHub's semantic code analysis for security vulnerabilities",[2487,4107,4108,4111],{},[2490,4109,4110],{},"golangci-lint",": Static analysis including security linters (gosec)",[2487,4113,4114,4117],{},[2490,4115,4116],{},"Codecov",": Coverage tracking to ensure security-critical code is tested",[1356,4119,4121],{"id":4120},"vulnerability-disclosure-policy","Vulnerability Disclosure Policy",[2484,4123,4124,4127,4130,4133],{},[2487,4125,4126],{},"Security vulnerabilities will be disclosed via GitHub Security Advisories",[2487,4128,4129],{},"We follow a 90-day disclosure timeline for non-critical issues",[2487,4131,4132],{},"Critical vulnerabilities may be disclosed sooner after patches are available",[2487,4134,4135],{},"We will credit reporters who follow responsible disclosure practices",[1356,4137,4139],{"id":4138},"credits","Credits",[1272,4141,4142],{},"We thank the following individuals for responsibly disclosing security issues:",[1272,4144,4145],{},[4146,4147,4148],"em",{},"This list is currently empty. Be the first to help improve our security!",[4150,4151],"hr",{},[1272,4153,4154,4157],{},[2490,4155,4156],{},"Last Updated",": 2024-12-07",[3706,4159,4160],{},"html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":34,"searchDepth":19,"depth":19,"links":4162},[4163,4164,4170,4171,4172,4173,4174],{"id":3747,"depth":19,"text":3748},{"id":3793,"depth":19,"text":3794,"children":4165},[4166,4167,4168,4169],{"id":3800,"depth":40,"text":3801},{"id":3851,"depth":40,"text":3852},{"id":3907,"depth":40,"text":3908},{"id":3936,"depth":40,"text":3937},{"id":3943,"depth":19,"text":3944},{"id":4056,"depth":19,"text":4057},{"id":4094,"depth":19,"text":4095},{"id":4120,"depth":19,"text":4121},{"id":4138,"depth":19,"text":4139},"shield",{},"/security",{"title":3738,"description":34},"security","IFN0JtT4aG7T2ju0JCU-vqvzcpQrhhLD8hul9IHIgnw",{"id":4182,"title":3689,"body":4183,"description":4191,"extension":3729,"icon":1367,"meta":4661,"navigation":1439,"path":4662,"seo":4663,"stem":3688,"__hash__":4664},"resources/contributing.md",{"type":1265,"value":4184,"toc":4637},[4185,4189,4192,4196,4199,4203,4247,4251,4255,4273,4276,4292,4294,4305,4309,4313,4327,4331,4342,4346,4349,4363,4367,4395,4398,4401,4414,4417,4429,4432,4444,4447,4459,4463,4471,4475,4478,4522,4526,4530,4533,4544,4547,4561,4565,4592,4598,4602,4605,4616,4620,4631,4634],[1268,4186,4188],{"id":4187},"contributing-to-herald","Contributing to herald",[1272,4190,4191],{},"Thank you for your interest in contributing to herald! This guide will help you get started.",[1356,4193,4195],{"id":4194},"code-of-conduct","Code of Conduct",[1272,4197,4198],{},"By participating in this project, you agree to maintain a respectful and inclusive environment for all contributors.",[1356,4200,4202],{"id":4201},"getting-started","Getting Started",[3811,4204,4205,4208,4214,4220,4223,4229,4235,4238,4244],{},[2487,4206,4207],{},"Fork the repository",[2487,4209,4210,4211],{},"Clone your fork: ",[1367,4212,4213],{},"git clone https://github.com/yourusername/herald.git",[2487,4215,4216,4217],{},"Create a feature branch: ",[1367,4218,4219],{},"git checkout -b feature/your-feature-name",[2487,4221,4222],{},"Make your changes",[2487,4224,4225,4226],{},"Run tests: ",[1367,4227,4228],{},"make test",[2487,4230,4231,4232],{},"Run linters: ",[1367,4233,4234],{},"make lint",[2487,4236,4237],{},"Commit your changes with a descriptive message",[2487,4239,4240,4241],{},"Push to your fork: ",[1367,4242,4243],{},"git push origin feature/your-feature-name",[2487,4245,4246],{},"Create a Pull Request",[1356,4248,4250],{"id":4249},"development-guidelines","Development Guidelines",[3618,4252,4254],{"id":4253},"code-style","Code Style",[2484,4256,4257,4260,4267,4270],{},[2487,4258,4259],{},"Follow standard Go conventions",[2487,4261,4262,4263,4266],{},"Run ",[1367,4264,4265],{},"go fmt"," before committing",[2487,4268,4269],{},"Add comments for exported functions and types",[2487,4271,4272],{},"Keep functions small and focused",[3618,4274,573],{"id":4275},"testing",[2484,4277,4278,4281,4286,4289],{},[2487,4279,4280],{},"Write tests for new functionality",[2487,4282,4283,4284],{},"Ensure all tests pass: ",[1367,4285,4228],{},[2487,4287,4288],{},"Maintain 1:1 file-to-test ratio",[2487,4290,4291],{},"Aim for 80%+ test coverage",[3618,4293,3609],{"id":3608},[2484,4295,4296,4299,4302],{},[2487,4297,4298],{},"Update README.md for API changes",[2487,4300,4301],{},"Add comments to all exported types",[2487,4303,4304],{},"Keep doc comments clear and concise",[1356,4306,4308],{"id":4307},"types-of-contributions","Types of Contributions",[3618,4310,4312],{"id":4311},"bug-reports","Bug Reports",[2484,4314,4315,4318,4321,4324],{},[2487,4316,4317],{},"Use GitHub Issues",[2487,4319,4320],{},"Include minimal reproduction code",[2487,4322,4323],{},"Describe expected vs actual behavior",[2487,4325,4326],{},"Include Go version and OS",[3618,4328,4330],{"id":4329},"feature-requests","Feature Requests",[2484,4332,4333,4336,4339],{},[2487,4334,4335],{},"Open an issue for discussion first",[2487,4337,4338],{},"Explain the use case",[2487,4340,4341],{},"Consider backwards compatibility",[3618,4343,4345],{"id":4344},"code-contributions","Code Contributions",[1272,4347,4348],{},"All contributions should:",[2484,4350,4351,4354,4357,4360],{},[2487,4352,4353],{},"Include comprehensive tests",[2487,4355,4356],{},"Pass linter checks",[2487,4358,4359],{},"Maintain existing code style",[2487,4361,4362],{},"Update documentation as needed",[1356,4364,4366],{"id":4365},"pull-request-process","Pull Request Process",[3811,4368,4369,4375,4380,4385,4390],{},[2487,4370,4371,4374],{},[2490,4372,4373],{},"Keep PRs focused"," - One feature/fix per PR",[2487,4376,4377],{},[2490,4378,4379],{},"Write descriptive commit messages",[2487,4381,4382],{},[2490,4383,4384],{},"Update tests and documentation",[2487,4386,4387],{},[2490,4388,4389],{},"Ensure CI passes",[2487,4391,4392],{},[2490,4393,4394],{},"Respond to review feedback",[1356,4396,573],{"id":4397},"testing-1",[1272,4399,4400],{},"Run the full test suite:",[1361,4402,4404],{"className":1503,"code":4403,"language":1505,"meta":34,"style":34},"make test\n",[1367,4405,4406],{"__ignoreMap":34},[1370,4407,4408,4411],{"class":1372,"line":9},[1370,4409,4410],{"class":1395},"make",[1370,4412,4413],{"class":1514}," test\n",[1272,4415,4416],{},"Run with coverage:",[1361,4418,4420],{"className":1503,"code":4419,"language":1505,"meta":34,"style":34},"make coverage\n",[1367,4421,4422],{"__ignoreMap":34},[1370,4423,4424,4426],{"class":1372,"line":9},[1370,4425,4410],{"class":1395},[1370,4427,4428],{"class":1514}," coverage\n",[1272,4430,4431],{},"Run linters:",[1361,4433,4435],{"className":1503,"code":4434,"language":1505,"meta":34,"style":34},"make lint\n",[1367,4436,4437],{"__ignoreMap":34},[1370,4438,4439,4441],{"class":1372,"line":9},[1370,4440,4410],{"class":1395},[1370,4442,4443],{"class":1514}," lint\n",[1272,4445,4446],{},"Run full CI simulation:",[1361,4448,4450],{"className":1503,"code":4449,"language":1505,"meta":34,"style":34},"make ci\n",[1367,4451,4452],{"__ignoreMap":34},[1370,4453,4454,4456],{"class":1372,"line":9},[1370,4455,4410],{"class":1395},[1370,4457,4458],{"class":1514}," ci\n",[1356,4460,4462],{"id":4461},"project-structure","Project Structure",[1361,4464,4469],{"className":4465,"code":4467,"language":4468},[4466],"language-text","herald/\n├── *.go              # Core library files\n├── *_test.go         # Tests (1:1 with source files)\n├── kafka/            # Kafka provider\n├── nats/             # NATS provider\n├── pubsub/           # Google Pub/Sub provider\n├── redis/            # Redis Streams provider\n├── sqs/              # AWS SQS provider\n├── amqp/             # RabbitMQ/AMQP provider\n├── sns/              # AWS SNS provider\n├── sql/              # SQL provider\n├── bolt/             # BoltDB provider\n├── firestore/        # Firestore provider\n├── io/               # io.Reader/Writer provider\n├── docs/             # Documentation\n├── testing/          # Integration tests\n├── .github/          # GitHub workflows and templates\n├── README.md         # Project documentation\n└── Makefile          # Build and test commands\n","text",[1367,4470,4467],{"__ignoreMap":34},[1356,4472,4474],{"id":4473},"commit-messages","Commit Messages",[1272,4476,4477],{},"Follow conventional commits:",[2484,4479,4480,4486,4492,4498,4504,4510,4516],{},[2487,4481,4482,4485],{},[1367,4483,4484],{},"feat:"," New feature",[2487,4487,4488,4491],{},[1367,4489,4490],{},"fix:"," Bug fix",[2487,4493,4494,4497],{},[1367,4495,4496],{},"docs:"," Documentation changes",[2487,4499,4500,4503],{},[1367,4501,4502],{},"test:"," Test additions/changes",[2487,4505,4506,4509],{},[1367,4507,4508],{},"refactor:"," Code refactoring",[2487,4511,4512,4515],{},[1367,4513,4514],{},"perf:"," Performance improvements",[2487,4517,4518,4521],{},[1367,4519,4520],{},"chore:"," Maintenance tasks",[1356,4523,4525],{"id":4524},"release-process","Release Process",[3618,4527,4529],{"id":4528},"automated-releases","Automated Releases",[1272,4531,4532],{},"This project uses automated release versioning. To create a release:",[3811,4534,4535,4538,4541],{},[2487,4536,4537],{},"Go to Actions → Release → Run workflow",[2487,4539,4540],{},"Leave \"Version override\" empty for automatic version inference",[2487,4542,4543],{},"Click \"Run workflow\"",[1272,4545,4546],{},"The system will:",[2484,4548,4549,4552,4555,4558],{},[2487,4550,4551],{},"Automatically determine the next version from conventional commits",[2487,4553,4554],{},"Create a git tag",[2487,4556,4557],{},"Generate release notes via GoReleaser",[2487,4559,4560],{},"Publish the release to GitHub",[3618,4562,4564],{"id":4563},"commit-conventions-for-versioning","Commit Conventions for Versioning",[2484,4566,4567,4572,4577,4583],{},[2487,4568,4569,4571],{},[1367,4570,4484],{}," new features (minor version: 1.2.0 → 1.3.0)",[2487,4573,4574,4576],{},[1367,4575,4490],{}," bug fixes (patch version: 1.2.0 → 1.2.1)",[2487,4578,4579,4582],{},[1367,4580,4581],{},"feat!:"," breaking changes (major version: 1.2.0 → 2.0.0)",[2487,4584,4585,2377,4587,2377,4589,4591],{},[1367,4586,4496],{},[1367,4588,4502],{},[1367,4590,4520],{}," no version change",[1272,4593,4594,4595],{},"Example: ",[1367,4596,4597],{},"feat(kafka): add header compression support",[3618,4599,4601],{"id":4600},"version-preview-on-pull-requests","Version Preview on Pull Requests",[1272,4603,4604],{},"Every PR automatically shows the next version that will be created:",[2484,4606,4607,4610,4613],{},[2487,4608,4609],{},"Check PR comments for \"Version Preview\"",[2487,4611,4612],{},"Updates automatically as you add commits",[2487,4614,4615],{},"Helps verify your commits have the intended effect",[1356,4617,4619],{"id":4618},"questions","Questions?",[2484,4621,4622,4625,4628],{},[2487,4623,4624],{},"Open an issue for questions",[2487,4626,4627],{},"Check existing issues first",[2487,4629,4630],{},"Be patient and respectful",[1272,4632,4633],{},"Thank you for contributing to herald!",[3706,4635,4636],{},"html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":34,"searchDepth":19,"depth":19,"links":4638},[4639,4640,4641,4646,4651,4652,4653,4654,4655,4660],{"id":4194,"depth":19,"text":4195},{"id":4201,"depth":19,"text":4202},{"id":4249,"depth":19,"text":4250,"children":4642},[4643,4644,4645],{"id":4253,"depth":40,"text":4254},{"id":4275,"depth":40,"text":573},{"id":3608,"depth":40,"text":3609},{"id":4307,"depth":19,"text":4308,"children":4647},[4648,4649,4650],{"id":4311,"depth":40,"text":4312},{"id":4329,"depth":40,"text":4330},{"id":4344,"depth":40,"text":4345},{"id":4365,"depth":19,"text":4366},{"id":4397,"depth":19,"text":573},{"id":4461,"depth":19,"text":4462},{"id":4473,"depth":19,"text":4474},{"id":4524,"depth":19,"text":4525,"children":4656},[4657,4658,4659],{"id":4528,"depth":40,"text":4529},{"id":4563,"depth":40,"text":4564},{"id":4600,"depth":40,"text":4601},{"id":4618,"depth":19,"text":4619},{},"/contributing",{"title":3689,"description":4191},"6gLVwr_LlZe2-oUO2U5R-W_PlCbcIvEEqlCcQab8CYw",{"id":4666,"title":165,"author":4667,"body":4668,"description":484,"extension":3729,"meta":6817,"navigation":1439,"path":482,"published":6818,"readtime":6819,"seo":6820,"stem":1223,"tags":6821,"updated":6818,"__hash__":6823},"herald/v1.0.5/3.guides/3.errors.md","zoobzio",{"type":1265,"value":4669,"toc":6795},[4670,4672,4674,4677,4682,4800,4803,4880,4893,4896,4899,4902,5053,5056,5070,5073,5076,5142,5144,5155,5158,5161,5271,5277,5279,5293,5296,5299,5404,5406,5417,5420,5423,5574,5577,5717,5720,5877,5880,6108,6111,6114,6170,6173,6230,6233,6236,6239,6339,6342,6345,6481,6484,6792],[1268,4671,165],{"id":3469},[1272,4673,488],{},[1356,4675,491],{"id":4676},"error-signal",[1272,4678,4679,4680,1799],{},"All herald errors are emitted on ",[1367,4681,3998],{},[1361,4683,4684],{"className":1363,"code":3475,"language":1365,"meta":34,"style":34},[1367,4685,4686,4732,4758,4796],{"__ignoreMap":34},[1370,4687,4688,4690,4692,4694,4696,4698,4700,4702,4704,4706,4708,4710,4712,4714,4716,4718,4720,4722,4724,4726,4728,4730],{"class":1372,"line":9},[1370,4689,1346],{"class":1381},[1370,4691,1392],{"class":1391},[1370,4693,2138],{"class":1395},[1370,4695,1398],{"class":1391},[1370,4697,1270],{"class":1381},[1370,4699,1392],{"class":1391},[1370,4701,1013],{"class":1381},[1370,4703,1404],{"class":1391},[1370,4705,2148],{"class":1417},[1370,4707,1398],{"class":1391},[1370,4709,1492],{"class":1571},[1370,4711,1679],{"class":1538},[1370,4713,1392],{"class":1391},[1370,4715,2159],{"class":1538},[1370,4717,1404],{"class":1391},[1370,4719,2164],{"class":1571},[1370,4721,2167],{"class":1781},[1370,4723,1346],{"class":1538},[1370,4725,1392],{"class":1391},[1370,4727,2174],{"class":1538},[1370,4729,2177],{"class":1391},[1370,4731,1618],{"class":1391},[1370,4733,4734,4736,4738,4740,4742,4744,4746,4748,4750,4752,4754,4756],{"class":1372,"line":19},[1370,4735,3528],{"class":1381},[1370,4737,1404],{"class":1391},[1370,4739,2190],{"class":1381},[1370,4741,1385],{"class":1381},[1370,4743,1388],{"class":1381},[1370,4745,1392],{"class":1391},[1370,4747,1018],{"class":1381},[1370,4749,1392],{"class":1391},[1370,4751,2199],{"class":1395},[1370,4753,1398],{"class":1391},[1370,4755,2204],{"class":1381},[1370,4757,1421],{"class":1391},[1370,4759,4760,4762,4764,4766,4768,4770,4772,4774,4776,4778,4780,4782,4784,4786,4788,4790,4792,4794],{"class":1372,"line":40},[1370,4761,3555],{"class":1381},[1370,4763,1392],{"class":1391},[1370,4765,2217],{"class":1395},[1370,4767,1398],{"class":1391},[1370,4769,3564],{"class":1514},[1370,4771,2226],{"class":2225},[1370,4773,3569],{"class":1514},[1370,4775,3572],{"class":2225},[1370,4777,2233],{"class":1514},[1370,4779,1404],{"class":1391},[1370,4781,3579],{"class":1381},[1370,4783,1392],{"class":1391},[1370,4785,3584],{"class":1381},[1370,4787,1404],{"class":1391},[1370,4789,3579],{"class":1381},[1370,4791,1392],{"class":1391},[1370,4793,3593],{"class":1381},[1370,4795,1421],{"class":1391},[1370,4797,4798],{"class":1372,"line":1436},[1370,4799,3204],{"class":1391},[1356,4801,496],{"id":4802},"error-structure",[1361,4804,4806],{"className":1363,"code":4805,"language":1365,"meta":34,"style":34},"type Error struct {\n    Operation string // \"publish\", \"subscribe\", \"unmarshal\", \"ack\", \"nack\"\n    Signal    string // The signal name involved\n    Err       string // The error message (string for JSON serialization)\n    Nack      bool   // Whether the message was nack'd\n    Raw       []byte // Raw message data (for unmarshal errors)\n}\n",[1367,4807,4808,4819,4830,4840,4851,4862,4876],{"__ignoreMap":34},[1370,4809,4810,4812,4815,4817],{"class":1372,"line":9},[1370,4811,1609],{"class":1417},[1370,4813,4814],{"class":1538}," Error",[1370,4816,1615],{"class":1417},[1370,4818,1618],{"class":1391},[1370,4820,4821,4824,4827],{"class":1372,"line":19},[1370,4822,4823],{"class":1624},"    Operation",[1370,4825,4826],{"class":1538}," string",[1370,4828,4829],{"class":1375}," // \"publish\", \"subscribe\", \"unmarshal\", \"ack\", \"nack\"\n",[1370,4831,4832,4835,4837],{"class":1372,"line":40},[1370,4833,4834],{"class":1624},"    Signal",[1370,4836,1628],{"class":1538},[1370,4838,4839],{"class":1375}," // The signal name involved\n",[1370,4841,4842,4845,4848],{"class":1372,"line":1436},[1370,4843,4844],{"class":1624},"    Err",[1370,4846,4847],{"class":1538},"       string",[1370,4849,4850],{"class":1375}," // The error message (string for JSON serialization)\n",[1370,4852,4853,4856,4859],{"class":1372,"line":1443},[1370,4854,4855],{"class":1624},"    Nack",[1370,4857,4858],{"class":1538},"      bool",[1370,4860,4861],{"class":1375},"   // Whether the message was nack'd\n",[1370,4863,4864,4867,4870,4873],{"class":1372,"line":1449},[1370,4865,4866],{"class":1624},"    Raw",[1370,4868,4869],{"class":1391},"       []",[1370,4871,4872],{"class":1538},"byte",[1370,4874,4875],{"class":1375}," // Raw message data (for unmarshal errors)\n",[1370,4877,4878],{"class":1372,"line":1481},[1370,4879,1649],{"class":1391},[1272,4881,4882,4883,4885,4886,4888,4889,4892],{},"Note: ",[1367,4884,3593],{}," is a ",[1367,4887,1866],{}," rather than ",[1367,4890,4891],{},"error"," to support JSON serialization of error events.",[1356,4894,501],{"id":4895},"error-types",[3618,4897,505],{"id":4898},"publish-errors",[1272,4900,4901],{},"Occur when publishing to a broker fails:",[1361,4903,4905],{"className":1363,"code":4904,"language":1365,"meta":34,"style":34},"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n    if err.Operation == \"publish\" {\n        log.Printf(\"Failed to publish to %s: %v\", err.Signal, err.Err)\n        // Retry logic, alerting, etc.\n    }\n})\n",[1367,4906,4907,4953,4979,4998,5039,5044,5049],{"__ignoreMap":34},[1370,4908,4909,4911,4913,4915,4917,4919,4921,4923,4925,4927,4929,4931,4933,4935,4937,4939,4941,4943,4945,4947,4949,4951],{"class":1372,"line":9},[1370,4910,1346],{"class":1381},[1370,4912,1392],{"class":1391},[1370,4914,2138],{"class":1395},[1370,4916,1398],{"class":1391},[1370,4918,1270],{"class":1381},[1370,4920,1392],{"class":1391},[1370,4922,1013],{"class":1381},[1370,4924,1404],{"class":1391},[1370,4926,2148],{"class":1417},[1370,4928,1398],{"class":1391},[1370,4930,1492],{"class":1571},[1370,4932,1679],{"class":1538},[1370,4934,1392],{"class":1391},[1370,4936,2159],{"class":1538},[1370,4938,1404],{"class":1391},[1370,4940,2164],{"class":1571},[1370,4942,2167],{"class":1781},[1370,4944,1346],{"class":1538},[1370,4946,1392],{"class":1391},[1370,4948,2174],{"class":1538},[1370,4950,2177],{"class":1391},[1370,4952,1618],{"class":1391},[1370,4954,4955,4957,4959,4961,4963,4965,4967,4969,4971,4973,4975,4977],{"class":1372,"line":19},[1370,4956,3528],{"class":1381},[1370,4958,1404],{"class":1391},[1370,4960,2190],{"class":1381},[1370,4962,1385],{"class":1381},[1370,4964,1388],{"class":1381},[1370,4966,1392],{"class":1391},[1370,4968,1018],{"class":1381},[1370,4970,1392],{"class":1391},[1370,4972,2199],{"class":1395},[1370,4974,1398],{"class":1391},[1370,4976,2204],{"class":1381},[1370,4978,1421],{"class":1391},[1370,4980,4981,4984,4986,4988,4990,4993,4996],{"class":1372,"line":40},[1370,4982,4983],{"class":1781},"    if",[1370,4985,3579],{"class":1381},[1370,4987,1392],{"class":1391},[1370,4989,3584],{"class":1381},[1370,4991,4992],{"class":1781}," ==",[1370,4994,4995],{"class":1514}," \"publish\"",[1370,4997,1618],{"class":1391},[1370,4999,5000,5003,5005,5007,5009,5012,5014,5016,5018,5020,5022,5024,5026,5029,5031,5033,5035,5037],{"class":1372,"line":1436},[1370,5001,5002],{"class":1381},"        log",[1370,5004,1392],{"class":1391},[1370,5006,2217],{"class":1395},[1370,5008,1398],{"class":1391},[1370,5010,5011],{"class":1514},"\"Failed to publish to ",[1370,5013,2226],{"class":2225},[1370,5015,3569],{"class":1514},[1370,5017,3572],{"class":2225},[1370,5019,2233],{"class":1514},[1370,5021,1404],{"class":1391},[1370,5023,3579],{"class":1381},[1370,5025,1392],{"class":1391},[1370,5027,5028],{"class":1381},"Signal",[1370,5030,1404],{"class":1391},[1370,5032,3579],{"class":1381},[1370,5034,1392],{"class":1391},[1370,5036,3593],{"class":1381},[1370,5038,1421],{"class":1391},[1370,5040,5041],{"class":1372,"line":1443},[1370,5042,5043],{"class":1375},"        // Retry logic, alerting, etc.\n",[1370,5045,5046],{"class":1372,"line":1449},[1370,5047,5048],{"class":1391},"    }\n",[1370,5050,5051],{"class":1372,"line":1481},[1370,5052,3204],{"class":1391},[1272,5054,5055],{},"Common causes:",[2484,5057,5058,5061,5064,5067],{},[2487,5059,5060],{},"Broker unavailable",[2487,5062,5063],{},"Network timeout",[2487,5065,5066],{},"Authentication failure",[2487,5068,5069],{},"Topic/queue doesn't exist",[3618,5071,510],{"id":5072},"subscribe-errors",[1272,5074,5075],{},"Occur when consuming from a broker fails:",[1361,5077,5079],{"className":1363,"code":5078,"language":1365,"meta":34,"style":34},"if err.Operation == \"subscribe\" {\n    log.Printf(\"Subscription error on %s: %v\", err.Signal, err.Err)\n}\n",[1367,5080,5081,5099,5138],{"__ignoreMap":34},[1370,5082,5083,5086,5088,5090,5092,5094,5097],{"class":1372,"line":9},[1370,5084,5085],{"class":1781},"if",[1370,5087,3579],{"class":1381},[1370,5089,1392],{"class":1391},[1370,5091,3584],{"class":1381},[1370,5093,4992],{"class":1781},[1370,5095,5096],{"class":1514}," \"subscribe\"",[1370,5098,1618],{"class":1391},[1370,5100,5101,5103,5105,5107,5109,5112,5114,5116,5118,5120,5122,5124,5126,5128,5130,5132,5134,5136],{"class":1372,"line":19},[1370,5102,3555],{"class":1381},[1370,5104,1392],{"class":1391},[1370,5106,2217],{"class":1395},[1370,5108,1398],{"class":1391},[1370,5110,5111],{"class":1514},"\"Subscription error on ",[1370,5113,2226],{"class":2225},[1370,5115,3569],{"class":1514},[1370,5117,3572],{"class":2225},[1370,5119,2233],{"class":1514},[1370,5121,1404],{"class":1391},[1370,5123,3579],{"class":1381},[1370,5125,1392],{"class":1391},[1370,5127,5028],{"class":1381},[1370,5129,1404],{"class":1391},[1370,5131,3579],{"class":1381},[1370,5133,1392],{"class":1391},[1370,5135,3593],{"class":1381},[1370,5137,1421],{"class":1391},[1370,5139,5140],{"class":1372,"line":40},[1370,5141,1649],{"class":1391},[1272,5143,5055],{},[2484,5145,5146,5149,5152],{},[2487,5147,5148],{},"Connection lost",[2487,5150,5151],{},"Consumer group rebalance",[2487,5153,5154],{},"Permission denied",[3618,5156,515],{"id":5157},"unmarshal-errors",[1272,5159,5160],{},"Occur when deserializing a message fails:",[1361,5162,5164],{"className":1363,"code":5163,"language":1365,"meta":34,"style":34},"if err.Operation == \"unmarshal\" {\n    log.Printf(\"Deserialization failed: %v\", err.Err)\n    log.Printf(\"Raw payload: %s\", err.Raw)\n    log.Printf(\"Message was nack'd: %t\", err.Nack)\n}\n",[1367,5165,5166,5183,5210,5238,5267],{"__ignoreMap":34},[1370,5167,5168,5170,5172,5174,5176,5178,5181],{"class":1372,"line":9},[1370,5169,5085],{"class":1781},[1370,5171,3579],{"class":1381},[1370,5173,1392],{"class":1391},[1370,5175,3584],{"class":1381},[1370,5177,4992],{"class":1781},[1370,5179,5180],{"class":1514}," \"unmarshal\"",[1370,5182,1618],{"class":1391},[1370,5184,5185,5187,5189,5191,5193,5196,5198,5200,5202,5204,5206,5208],{"class":1372,"line":19},[1370,5186,3555],{"class":1381},[1370,5188,1392],{"class":1391},[1370,5190,2217],{"class":1395},[1370,5192,1398],{"class":1391},[1370,5194,5195],{"class":1514},"\"Deserialization failed: ",[1370,5197,3572],{"class":2225},[1370,5199,2233],{"class":1514},[1370,5201,1404],{"class":1391},[1370,5203,3579],{"class":1381},[1370,5205,1392],{"class":1391},[1370,5207,3593],{"class":1381},[1370,5209,1421],{"class":1391},[1370,5211,5212,5214,5216,5218,5220,5223,5225,5227,5229,5231,5233,5236],{"class":1372,"line":40},[1370,5213,3555],{"class":1381},[1370,5215,1392],{"class":1391},[1370,5217,2217],{"class":1395},[1370,5219,1398],{"class":1391},[1370,5221,5222],{"class":1514},"\"Raw payload: ",[1370,5224,2226],{"class":2225},[1370,5226,2233],{"class":1514},[1370,5228,1404],{"class":1391},[1370,5230,3579],{"class":1381},[1370,5232,1392],{"class":1391},[1370,5234,5235],{"class":1381},"Raw",[1370,5237,1421],{"class":1391},[1370,5239,5240,5242,5244,5246,5248,5251,5254,5256,5258,5260,5262,5265],{"class":1372,"line":1436},[1370,5241,3555],{"class":1381},[1370,5243,1392],{"class":1391},[1370,5245,2217],{"class":1395},[1370,5247,1398],{"class":1391},[1370,5249,5250],{"class":1514},"\"Message was nack'd: ",[1370,5252,5253],{"class":2225},"%t",[1370,5255,2233],{"class":1514},[1370,5257,1404],{"class":1391},[1370,5259,3579],{"class":1381},[1370,5261,1392],{"class":1391},[1370,5263,5264],{"class":1381},"Nack",[1370,5266,1421],{"class":1391},[1370,5268,5269],{"class":1372,"line":1443},[1370,5270,1649],{"class":1391},[1272,5272,5273,5274,5276],{},"The ",[1367,5275,5235],{}," field contains the original message bytes for debugging.",[1272,5278,5055],{},[2484,5280,5281,5284,5287,5290],{},[2487,5282,5283],{},"Codec mismatch (JSON vs Protobuf)",[2487,5285,5286],{},"Schema incompatibility",[2487,5288,5289],{},"Corrupted message",[2487,5291,5292],{},"Empty payload",[3618,5294,520],{"id":5295},"acknack-errors",[1272,5297,5298],{},"Occur when acknowledging or rejecting a message fails:",[1361,5300,5302],{"className":1363,"code":5301,"language":1365,"meta":34,"style":34},"if err.Operation == \"ack\" {\n    log.Printf(\"Failed to acknowledge message: %v\", err.Err)\n}\n\nif err.Operation == \"nack\" {\n    log.Printf(\"Failed to nack message: %v\", err.Err)\n}\n",[1367,5303,5304,5321,5348,5352,5356,5373,5400],{"__ignoreMap":34},[1370,5305,5306,5308,5310,5312,5314,5316,5319],{"class":1372,"line":9},[1370,5307,5085],{"class":1781},[1370,5309,3579],{"class":1381},[1370,5311,1392],{"class":1391},[1370,5313,3584],{"class":1381},[1370,5315,4992],{"class":1781},[1370,5317,5318],{"class":1514}," \"ack\"",[1370,5320,1618],{"class":1391},[1370,5322,5323,5325,5327,5329,5331,5334,5336,5338,5340,5342,5344,5346],{"class":1372,"line":19},[1370,5324,3555],{"class":1381},[1370,5326,1392],{"class":1391},[1370,5328,2217],{"class":1395},[1370,5330,1398],{"class":1391},[1370,5332,5333],{"class":1514},"\"Failed to acknowledge message: ",[1370,5335,3572],{"class":2225},[1370,5337,2233],{"class":1514},[1370,5339,1404],{"class":1391},[1370,5341,3579],{"class":1381},[1370,5343,1392],{"class":1391},[1370,5345,3593],{"class":1381},[1370,5347,1421],{"class":1391},[1370,5349,5350],{"class":1372,"line":40},[1370,5351,1649],{"class":1391},[1370,5353,5354],{"class":1372,"line":1436},[1370,5355,1440],{"emptyLinePlaceholder":1439},[1370,5357,5358,5360,5362,5364,5366,5368,5371],{"class":1372,"line":1443},[1370,5359,5085],{"class":1781},[1370,5361,3579],{"class":1381},[1370,5363,1392],{"class":1391},[1370,5365,3584],{"class":1381},[1370,5367,4992],{"class":1781},[1370,5369,5370],{"class":1514}," \"nack\"",[1370,5372,1618],{"class":1391},[1370,5374,5375,5377,5379,5381,5383,5386,5388,5390,5392,5394,5396,5398],{"class":1372,"line":1449},[1370,5376,3555],{"class":1381},[1370,5378,1392],{"class":1391},[1370,5380,2217],{"class":1395},[1370,5382,1398],{"class":1391},[1370,5384,5385],{"class":1514},"\"Failed to nack message: ",[1370,5387,3572],{"class":2225},[1370,5389,2233],{"class":1514},[1370,5391,1404],{"class":1391},[1370,5393,3579],{"class":1381},[1370,5395,1392],{"class":1391},[1370,5397,3593],{"class":1381},[1370,5399,1421],{"class":1391},[1370,5401,5402],{"class":1372,"line":1481},[1370,5403,1649],{"class":1391},[1272,5405,5055],{},[2484,5407,5408,5411,5414],{},[2487,5409,5410],{},"Connection lost after processing",[2487,5412,5413],{},"Message already ack'd/expired",[2487,5415,5416],{},"Broker timeout",[1356,5418,525],{"id":5419},"error-handling-patterns",[3618,5421,529],{"id":5422},"centralized-logging",[1361,5424,5426],{"className":1363,"code":5425,"language":1365,"meta":34,"style":34},"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    slog.Error(\"herald error\",\n        \"operation\", err.Operation,\n        \"signal\", err.Signal,\n        \"error\", err.Err,\n    )\n})\n",[1367,5427,5428,5474,5500,5504,5520,5535,5550,5565,5570],{"__ignoreMap":34},[1370,5429,5430,5432,5434,5436,5438,5440,5442,5444,5446,5448,5450,5452,5454,5456,5458,5460,5462,5464,5466,5468,5470,5472],{"class":1372,"line":9},[1370,5431,1346],{"class":1381},[1370,5433,1392],{"class":1391},[1370,5435,2138],{"class":1395},[1370,5437,1398],{"class":1391},[1370,5439,1270],{"class":1381},[1370,5441,1392],{"class":1391},[1370,5443,1013],{"class":1381},[1370,5445,1404],{"class":1391},[1370,5447,2148],{"class":1417},[1370,5449,1398],{"class":1391},[1370,5451,1492],{"class":1571},[1370,5453,1679],{"class":1538},[1370,5455,1392],{"class":1391},[1370,5457,2159],{"class":1538},[1370,5459,1404],{"class":1391},[1370,5461,2164],{"class":1571},[1370,5463,2167],{"class":1781},[1370,5465,1346],{"class":1538},[1370,5467,1392],{"class":1391},[1370,5469,2174],{"class":1538},[1370,5471,2177],{"class":1391},[1370,5473,1618],{"class":1391},[1370,5475,5476,5478,5480,5482,5484,5486,5488,5490,5492,5494,5496,5498],{"class":1372,"line":19},[1370,5477,3528],{"class":1381},[1370,5479,1404],{"class":1391},[1370,5481,2190],{"class":1381},[1370,5483,1385],{"class":1381},[1370,5485,1388],{"class":1381},[1370,5487,1392],{"class":1391},[1370,5489,1018],{"class":1381},[1370,5491,1392],{"class":1391},[1370,5493,2199],{"class":1395},[1370,5495,1398],{"class":1391},[1370,5497,2204],{"class":1381},[1370,5499,1421],{"class":1391},[1370,5501,5502],{"class":1372,"line":40},[1370,5503,1440],{"emptyLinePlaceholder":1439},[1370,5505,5506,5509,5511,5513,5515,5518],{"class":1372,"line":1436},[1370,5507,5508],{"class":1381},"    slog",[1370,5510,1392],{"class":1391},[1370,5512,842],{"class":1395},[1370,5514,1398],{"class":1391},[1370,5516,5517],{"class":1514},"\"herald error\"",[1370,5519,1887],{"class":1391},[1370,5521,5522,5525,5527,5529,5531,5533],{"class":1372,"line":1443},[1370,5523,5524],{"class":1514},"        \"operation\"",[1370,5526,1404],{"class":1391},[1370,5528,3579],{"class":1381},[1370,5530,1392],{"class":1391},[1370,5532,3584],{"class":1381},[1370,5534,1887],{"class":1391},[1370,5536,5537,5540,5542,5544,5546,5548],{"class":1372,"line":1449},[1370,5538,5539],{"class":1514},"        \"signal\"",[1370,5541,1404],{"class":1391},[1370,5543,3579],{"class":1381},[1370,5545,1392],{"class":1391},[1370,5547,5028],{"class":1381},[1370,5549,1887],{"class":1391},[1370,5551,5552,5555,5557,5559,5561,5563],{"class":1372,"line":1481},[1370,5553,5554],{"class":1514},"        \"error\"",[1370,5556,1404],{"class":1391},[1370,5558,3579],{"class":1381},[1370,5560,1392],{"class":1391},[1370,5562,3593],{"class":1381},[1370,5564,1887],{"class":1391},[1370,5566,5567],{"class":1372,"line":1578},[1370,5568,5569],{"class":1391},"    )\n",[1370,5571,5572],{"class":1372,"line":1584},[1370,5573,3204],{"class":1391},[3618,5575,534],{"id":5576},"metrics-collection",[1361,5578,5580],{"className":1363,"code":5579,"language":1365,"meta":34,"style":34},"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    metrics.Counter(\"herald_errors_total\",\n        \"operation\", err.Operation,\n        \"signal\", err.Signal,\n    ).Inc()\n})\n",[1367,5581,5582,5628,5654,5658,5675,5689,5703,5713],{"__ignoreMap":34},[1370,5583,5584,5586,5588,5590,5592,5594,5596,5598,5600,5602,5604,5606,5608,5610,5612,5614,5616,5618,5620,5622,5624,5626],{"class":1372,"line":9},[1370,5585,1346],{"class":1381},[1370,5587,1392],{"class":1391},[1370,5589,2138],{"class":1395},[1370,5591,1398],{"class":1391},[1370,5593,1270],{"class":1381},[1370,5595,1392],{"class":1391},[1370,5597,1013],{"class":1381},[1370,5599,1404],{"class":1391},[1370,5601,2148],{"class":1417},[1370,5603,1398],{"class":1391},[1370,5605,1492],{"class":1571},[1370,5607,1679],{"class":1538},[1370,5609,1392],{"class":1391},[1370,5611,2159],{"class":1538},[1370,5613,1404],{"class":1391},[1370,5615,2164],{"class":1571},[1370,5617,2167],{"class":1781},[1370,5619,1346],{"class":1538},[1370,5621,1392],{"class":1391},[1370,5623,2174],{"class":1538},[1370,5625,2177],{"class":1391},[1370,5627,1618],{"class":1391},[1370,5629,5630,5632,5634,5636,5638,5640,5642,5644,5646,5648,5650,5652],{"class":1372,"line":19},[1370,5631,3528],{"class":1381},[1370,5633,1404],{"class":1391},[1370,5635,2190],{"class":1381},[1370,5637,1385],{"class":1381},[1370,5639,1388],{"class":1381},[1370,5641,1392],{"class":1391},[1370,5643,1018],{"class":1381},[1370,5645,1392],{"class":1391},[1370,5647,2199],{"class":1395},[1370,5649,1398],{"class":1391},[1370,5651,2204],{"class":1381},[1370,5653,1421],{"class":1391},[1370,5655,5656],{"class":1372,"line":40},[1370,5657,1440],{"emptyLinePlaceholder":1439},[1370,5659,5660,5663,5665,5668,5670,5673],{"class":1372,"line":1436},[1370,5661,5662],{"class":1381},"    metrics",[1370,5664,1392],{"class":1391},[1370,5666,5667],{"class":1395},"Counter",[1370,5669,1398],{"class":1391},[1370,5671,5672],{"class":1514},"\"herald_errors_total\"",[1370,5674,1887],{"class":1391},[1370,5676,5677,5679,5681,5683,5685,5687],{"class":1372,"line":1443},[1370,5678,5524],{"class":1514},[1370,5680,1404],{"class":1391},[1370,5682,3579],{"class":1381},[1370,5684,1392],{"class":1391},[1370,5686,3584],{"class":1381},[1370,5688,1887],{"class":1391},[1370,5690,5691,5693,5695,5697,5699,5701],{"class":1372,"line":1449},[1370,5692,5539],{"class":1514},[1370,5694,1404],{"class":1391},[1370,5696,3579],{"class":1381},[1370,5698,1392],{"class":1391},[1370,5700,5028],{"class":1381},[1370,5702,1887],{"class":1391},[1370,5704,5705,5708,5711],{"class":1372,"line":1481},[1370,5706,5707],{"class":1391},"    ).",[1370,5709,5710],{"class":1395},"Inc",[1370,5712,1433],{"class":1391},[1370,5714,5715],{"class":1372,"line":1578},[1370,5716,3204],{"class":1391},[3618,5718,539],{"id":5719},"alerting",[1361,5721,5723],{"className":1363,"code":5722,"language":1365,"meta":34,"style":34},"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    if err.Operation == \"publish\" {\n        // Critical: messages not being delivered\n        alertOps(fmt.Sprintf(\"Publishing failed: %s - %v\", err.Signal, err.Err))\n    }\n})\n",[1367,5724,5725,5771,5797,5801,5817,5822,5869,5873],{"__ignoreMap":34},[1370,5726,5727,5729,5731,5733,5735,5737,5739,5741,5743,5745,5747,5749,5751,5753,5755,5757,5759,5761,5763,5765,5767,5769],{"class":1372,"line":9},[1370,5728,1346],{"class":1381},[1370,5730,1392],{"class":1391},[1370,5732,2138],{"class":1395},[1370,5734,1398],{"class":1391},[1370,5736,1270],{"class":1381},[1370,5738,1392],{"class":1391},[1370,5740,1013],{"class":1381},[1370,5742,1404],{"class":1391},[1370,5744,2148],{"class":1417},[1370,5746,1398],{"class":1391},[1370,5748,1492],{"class":1571},[1370,5750,1679],{"class":1538},[1370,5752,1392],{"class":1391},[1370,5754,2159],{"class":1538},[1370,5756,1404],{"class":1391},[1370,5758,2164],{"class":1571},[1370,5760,2167],{"class":1781},[1370,5762,1346],{"class":1538},[1370,5764,1392],{"class":1391},[1370,5766,2174],{"class":1538},[1370,5768,2177],{"class":1391},[1370,5770,1618],{"class":1391},[1370,5772,5773,5775,5777,5779,5781,5783,5785,5787,5789,5791,5793,5795],{"class":1372,"line":19},[1370,5774,3528],{"class":1381},[1370,5776,1404],{"class":1391},[1370,5778,2190],{"class":1381},[1370,5780,1385],{"class":1381},[1370,5782,1388],{"class":1381},[1370,5784,1392],{"class":1391},[1370,5786,1018],{"class":1381},[1370,5788,1392],{"class":1391},[1370,5790,2199],{"class":1395},[1370,5792,1398],{"class":1391},[1370,5794,2204],{"class":1381},[1370,5796,1421],{"class":1391},[1370,5798,5799],{"class":1372,"line":40},[1370,5800,1440],{"emptyLinePlaceholder":1439},[1370,5802,5803,5805,5807,5809,5811,5813,5815],{"class":1372,"line":1436},[1370,5804,4983],{"class":1781},[1370,5806,3579],{"class":1381},[1370,5808,1392],{"class":1391},[1370,5810,3584],{"class":1381},[1370,5812,4992],{"class":1781},[1370,5814,4995],{"class":1514},[1370,5816,1618],{"class":1391},[1370,5818,5819],{"class":1372,"line":1443},[1370,5820,5821],{"class":1375},"        // Critical: messages not being delivered\n",[1370,5823,5824,5827,5829,5832,5834,5837,5839,5842,5844,5847,5849,5851,5853,5855,5857,5859,5861,5863,5865,5867],{"class":1372,"line":1449},[1370,5825,5826],{"class":1395},"        alertOps",[1370,5828,1398],{"class":1391},[1370,5830,5831],{"class":1381},"fmt",[1370,5833,1392],{"class":1391},[1370,5835,5836],{"class":1395},"Sprintf",[1370,5838,1398],{"class":1391},[1370,5840,5841],{"class":1514},"\"Publishing failed: ",[1370,5843,2226],{"class":2225},[1370,5845,5846],{"class":1514}," - ",[1370,5848,3572],{"class":2225},[1370,5850,2233],{"class":1514},[1370,5852,1404],{"class":1391},[1370,5854,3579],{"class":1381},[1370,5856,1392],{"class":1391},[1370,5858,5028],{"class":1381},[1370,5860,1404],{"class":1391},[1370,5862,3579],{"class":1381},[1370,5864,1392],{"class":1391},[1370,5866,3593],{"class":1381},[1370,5868,1958],{"class":1391},[1370,5870,5871],{"class":1372,"line":1481},[1370,5872,5048],{"class":1391},[1370,5874,5875],{"class":1372,"line":1578},[1370,5876,3204],{"class":1391},[3618,5878,544],{"id":5879},"dead-letter-handling",[1361,5881,5883],{"className":1363,"code":5882,"language":1365,"meta":34,"style":34},"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    if err.Operation == \"unmarshal\" && err.Raw != nil {\n        // Store failed messages for later analysis\n        deadLetterQueue.Send(ctx, err.Raw, map[string]string{\n            \"signal\":    err.Signal,\n            \"error\":     err.Err,\n            \"timestamp\": time.Now().Format(time.RFC3339),\n        })\n    }\n})\n",[1367,5884,5885,5931,5957,5961,5991,5996,6033,6048,6064,6095,6100,6104],{"__ignoreMap":34},[1370,5886,5887,5889,5891,5893,5895,5897,5899,5901,5903,5905,5907,5909,5911,5913,5915,5917,5919,5921,5923,5925,5927,5929],{"class":1372,"line":9},[1370,5888,1346],{"class":1381},[1370,5890,1392],{"class":1391},[1370,5892,2138],{"class":1395},[1370,5894,1398],{"class":1391},[1370,5896,1270],{"class":1381},[1370,5898,1392],{"class":1391},[1370,5900,1013],{"class":1381},[1370,5902,1404],{"class":1391},[1370,5904,2148],{"class":1417},[1370,5906,1398],{"class":1391},[1370,5908,1492],{"class":1571},[1370,5910,1679],{"class":1538},[1370,5912,1392],{"class":1391},[1370,5914,2159],{"class":1538},[1370,5916,1404],{"class":1391},[1370,5918,2164],{"class":1571},[1370,5920,2167],{"class":1781},[1370,5922,1346],{"class":1538},[1370,5924,1392],{"class":1391},[1370,5926,2174],{"class":1538},[1370,5928,2177],{"class":1391},[1370,5930,1618],{"class":1391},[1370,5932,5933,5935,5937,5939,5941,5943,5945,5947,5949,5951,5953,5955],{"class":1372,"line":19},[1370,5934,3528],{"class":1381},[1370,5936,1404],{"class":1391},[1370,5938,2190],{"class":1381},[1370,5940,1385],{"class":1381},[1370,5942,1388],{"class":1381},[1370,5944,1392],{"class":1391},[1370,5946,1018],{"class":1381},[1370,5948,1392],{"class":1391},[1370,5950,2199],{"class":1395},[1370,5952,1398],{"class":1391},[1370,5954,2204],{"class":1381},[1370,5956,1421],{"class":1391},[1370,5958,5959],{"class":1372,"line":40},[1370,5960,1440],{"emptyLinePlaceholder":1439},[1370,5962,5963,5965,5967,5969,5971,5973,5975,5978,5980,5982,5984,5987,5989],{"class":1372,"line":1436},[1370,5964,4983],{"class":1781},[1370,5966,3579],{"class":1381},[1370,5968,1392],{"class":1391},[1370,5970,3584],{"class":1381},[1370,5972,4992],{"class":1781},[1370,5974,5180],{"class":1514},[1370,5976,5977],{"class":1781}," &&",[1370,5979,3579],{"class":1381},[1370,5981,1392],{"class":1391},[1370,5983,5235],{"class":1381},[1370,5985,5986],{"class":1781}," !=",[1370,5988,1418],{"class":1417},[1370,5990,1618],{"class":1391},[1370,5992,5993],{"class":1372,"line":1443},[1370,5994,5995],{"class":1375},"        // Store failed messages for later analysis\n",[1370,5997,5998,6001,6003,6006,6008,6010,6012,6014,6016,6018,6020,6023,6025,6027,6029,6031],{"class":1372,"line":1449},[1370,5999,6000],{"class":1381},"        deadLetterQueue",[1370,6002,1392],{"class":1391},[1370,6004,6005],{"class":1395},"Send",[1370,6007,1398],{"class":1391},[1370,6009,1492],{"class":1381},[1370,6011,1404],{"class":1391},[1370,6013,3579],{"class":1381},[1370,6015,1392],{"class":1391},[1370,6017,5235],{"class":1381},[1370,6019,1404],{"class":1391},[1370,6021,6022],{"class":1417}," map",[1370,6024,1743],{"class":1391},[1370,6026,1866],{"class":1538},[1370,6028,3154],{"class":1391},[1370,6030,1866],{"class":1538},[1370,6032,1852],{"class":1391},[1370,6034,6035,6038,6040,6042,6044,6046],{"class":1372,"line":1481},[1370,6036,6037],{"class":1514},"            \"signal\"",[1370,6039,1799],{"class":1391},[1370,6041,3528],{"class":1381},[1370,6043,1392],{"class":1391},[1370,6045,5028],{"class":1381},[1370,6047,1887],{"class":1391},[1370,6049,6050,6053,6055,6058,6060,6062],{"class":1372,"line":1578},[1370,6051,6052],{"class":1514},"            \"error\"",[1370,6054,1799],{"class":1391},[1370,6056,6057],{"class":1381},"     err",[1370,6059,1392],{"class":1391},[1370,6061,3593],{"class":1381},[1370,6063,1887],{"class":1391},[1370,6065,6066,6069,6071,6073,6075,6077,6080,6083,6085,6088,6090,6093],{"class":1372,"line":1584},[1370,6067,6068],{"class":1514},"            \"timestamp\"",[1370,6070,1799],{"class":1391},[1370,6072,3176],{"class":1381},[1370,6074,1392],{"class":1391},[1370,6076,3181],{"class":1395},[1370,6078,6079],{"class":1391},"().",[1370,6081,6082],{"class":1395},"Format",[1370,6084,1398],{"class":1391},[1370,6086,6087],{"class":1381},"time",[1370,6089,1392],{"class":1391},[1370,6091,6092],{"class":1381},"RFC3339",[1370,6094,3295],{"class":1391},[1370,6096,6097],{"class":1372,"line":1590},[1370,6098,6099],{"class":1391},"        })\n",[1370,6101,6102],{"class":1372,"line":1596},[1370,6103,5048],{"class":1391},[1370,6105,6106],{"class":1372,"line":1601},[1370,6107,3204],{"class":1391},[1356,6109,549],{"id":6110},"sentinel-errors",[1272,6112,6113],{},"Herald provides sentinel errors for common cases:",[1361,6115,6117],{"className":1363,"code":6116,"language":1365,"meta":34,"style":34},"var (\n    ErrNoWriter = errors.New(\"herald: no writer configured\")\n    ErrNoReader = errors.New(\"herald: no reader configured\")\n)\n",[1367,6118,6119,6126,6146,6166],{"__ignoreMap":34},[1370,6120,6121,6124],{"class":1372,"line":9},[1370,6122,6123],{"class":1417},"var",[1370,6125,1552],{"class":1391},[1370,6127,6128,6131,6133,6135,6137,6139,6141,6144],{"class":1372,"line":19},[1370,6129,6130],{"class":1381},"    ErrNoWriter",[1370,6132,3173],{"class":1381},[1370,6134,2962],{"class":1381},[1370,6136,1392],{"class":1391},[1370,6138,1922],{"class":1395},[1370,6140,1398],{"class":1391},[1370,6142,6143],{"class":1514},"\"herald: no writer configured\"",[1370,6145,1421],{"class":1391},[1370,6147,6148,6151,6153,6155,6157,6159,6161,6164],{"class":1372,"line":40},[1370,6149,6150],{"class":1381},"    ErrNoReader",[1370,6152,3173],{"class":1381},[1370,6154,2962],{"class":1381},[1370,6156,1392],{"class":1391},[1370,6158,1922],{"class":1395},[1370,6160,1398],{"class":1391},[1370,6162,6163],{"class":1514},"\"herald: no reader configured\"",[1370,6165,1421],{"class":1391},[1370,6167,6168],{"class":1372,"line":1436},[1370,6169,1421],{"class":1391},[1272,6171,6172],{},"Check for specific errors:",[1361,6174,6176],{"className":1363,"code":6175,"language":1365,"meta":34,"style":34},"if errors.Is(err.Err, herald.ErrNoWriter) {\n    log.Printf(\"Provider not configured for publishing\")\n}\n",[1367,6177,6178,6211,6226],{"__ignoreMap":34},[1370,6179,6180,6182,6184,6186,6189,6191,6194,6196,6198,6200,6202,6204,6207,6209],{"class":1372,"line":9},[1370,6181,5085],{"class":1781},[1370,6183,2962],{"class":1381},[1370,6185,1392],{"class":1391},[1370,6187,6188],{"class":1395},"Is",[1370,6190,1398],{"class":1391},[1370,6192,6193],{"class":1381},"err",[1370,6195,1392],{"class":1391},[1370,6197,3593],{"class":1381},[1370,6199,1404],{"class":1391},[1370,6201,1388],{"class":1381},[1370,6203,1392],{"class":1391},[1370,6205,6206],{"class":1381},"ErrNoWriter",[1370,6208,2177],{"class":1391},[1370,6210,1618],{"class":1391},[1370,6212,6213,6215,6217,6219,6221,6224],{"class":1372,"line":19},[1370,6214,3555],{"class":1381},[1370,6216,1392],{"class":1391},[1370,6218,2217],{"class":1395},[1370,6220,1398],{"class":1391},[1370,6222,6223],{"class":1514},"\"Provider not configured for publishing\"",[1370,6225,1421],{"class":1391},[1370,6227,6228],{"class":1372,"line":40},[1370,6229,1649],{"class":1391},[1356,6231,554],{"id":6232},"error-recovery",[3618,6234,558],{"id":6235},"automatic-recovery",[1272,6237,6238],{},"Pipeline options handle transient errors:",[1361,6240,6242],{"className":1363,"code":6241,"language":1365,"meta":34,"style":34},"pub := herald.NewPublisher(provider, signal, key, []herald.Option[Order]{\n    herald.WithRetry[Order](3),\n    herald.WithBackoff[Order](3, 100*time.Millisecond),\n})\n// Retries automatically before emitting error signal\n",[1367,6243,6244,6284,6302,6330,6334],{"__ignoreMap":34},[1370,6245,6246,6248,6250,6252,6254,6256,6258,6260,6262,6264,6266,6268,6270,6272,6274,6276,6278,6280,6282],{"class":1372,"line":9},[1370,6247,1382],{"class":1381},[1370,6249,1385],{"class":1381},[1370,6251,1388],{"class":1381},[1370,6253,1392],{"class":1391},[1370,6255,851],{"class":1395},[1370,6257,1398],{"class":1391},[1370,6259,1401],{"class":1381},[1370,6261,1404],{"class":1391},[1370,6263,1407],{"class":1381},[1370,6265,1404],{"class":1391},[1370,6267,1412],{"class":1381},[1370,6269,1404],{"class":1391},[1370,6271,1863],{"class":1391},[1370,6273,1270],{"class":1538},[1370,6275,1392],{"class":1391},[1370,6277,2828],{"class":1538},[1370,6279,1743],{"class":1391},[1370,6281,1746],{"class":1538},[1370,6283,2835],{"class":1391},[1370,6285,6286,6288,6290,6292,6294,6296,6298,6300],{"class":1372,"line":19},[1370,6287,2840],{"class":1381},[1370,6289,1392],{"class":1391},[1370,6291,899],{"class":1395},[1370,6293,1743],{"class":1391},[1370,6295,1746],{"class":1538},[1370,6297,1749],{"class":1391},[1370,6299,3292],{"class":2311},[1370,6301,3295],{"class":1391},[1370,6303,6304,6306,6308,6310,6312,6314,6316,6318,6320,6322,6324,6326,6328],{"class":1372,"line":40},[1370,6305,2840],{"class":1381},[1370,6307,1392],{"class":1391},[1370,6309,904],{"class":1395},[1370,6311,1743],{"class":1391},[1370,6313,1746],{"class":1538},[1370,6315,1749],{"class":1391},[1370,6317,3292],{"class":2311},[1370,6319,1404],{"class":1391},[1370,6321,3316],{"class":2311},[1370,6323,3319],{"class":1381},[1370,6325,1392],{"class":1391},[1370,6327,3324],{"class":1381},[1370,6329,3295],{"class":1391},[1370,6331,6332],{"class":1372,"line":1436},[1370,6333,3204],{"class":1391},[1370,6335,6336],{"class":1372,"line":1443},[1370,6337,6338],{"class":1375},"// Retries automatically before emitting error signal\n",[3618,6340,563],{"id":6341},"manual-recovery",[1272,6343,6344],{},"For persistent errors, implement custom recovery:",[1361,6346,6348],{"className":1363,"code":6347,"language":1365,"meta":34,"style":34},"capitan.Hook(herald.ErrorSignal, func(ctx context.Context, e *capitan.Event) {\n    err, _ := herald.ErrorKey.From(e)\n\n    if err.Operation == \"publish\" {\n        // Fallback to backup broker\n        backupProvider.Publish(ctx, originalData, metadata)\n    }\n})\n",[1367,6349,6350,6396,6422,6426,6442,6447,6473,6477],{"__ignoreMap":34},[1370,6351,6352,6354,6356,6358,6360,6362,6364,6366,6368,6370,6372,6374,6376,6378,6380,6382,6384,6386,6388,6390,6392,6394],{"class":1372,"line":9},[1370,6353,1346],{"class":1381},[1370,6355,1392],{"class":1391},[1370,6357,2138],{"class":1395},[1370,6359,1398],{"class":1391},[1370,6361,1270],{"class":1381},[1370,6363,1392],{"class":1391},[1370,6365,1013],{"class":1381},[1370,6367,1404],{"class":1391},[1370,6369,2148],{"class":1417},[1370,6371,1398],{"class":1391},[1370,6373,1492],{"class":1571},[1370,6375,1679],{"class":1538},[1370,6377,1392],{"class":1391},[1370,6379,2159],{"class":1538},[1370,6381,1404],{"class":1391},[1370,6383,2164],{"class":1571},[1370,6385,2167],{"class":1781},[1370,6387,1346],{"class":1538},[1370,6389,1392],{"class":1391},[1370,6391,2174],{"class":1538},[1370,6393,2177],{"class":1391},[1370,6395,1618],{"class":1391},[1370,6397,6398,6400,6402,6404,6406,6408,6410,6412,6414,6416,6418,6420],{"class":1372,"line":19},[1370,6399,3528],{"class":1381},[1370,6401,1404],{"class":1391},[1370,6403,2190],{"class":1381},[1370,6405,1385],{"class":1381},[1370,6407,1388],{"class":1381},[1370,6409,1392],{"class":1391},[1370,6411,1018],{"class":1381},[1370,6413,1392],{"class":1391},[1370,6415,2199],{"class":1395},[1370,6417,1398],{"class":1391},[1370,6419,2204],{"class":1381},[1370,6421,1421],{"class":1391},[1370,6423,6424],{"class":1372,"line":40},[1370,6425,1440],{"emptyLinePlaceholder":1439},[1370,6427,6428,6430,6432,6434,6436,6438,6440],{"class":1372,"line":1436},[1370,6429,4983],{"class":1781},[1370,6431,3579],{"class":1381},[1370,6433,1392],{"class":1391},[1370,6435,3584],{"class":1381},[1370,6437,4992],{"class":1781},[1370,6439,4995],{"class":1514},[1370,6441,1618],{"class":1391},[1370,6443,6444],{"class":1372,"line":1443},[1370,6445,6446],{"class":1375},"        // Fallback to backup broker\n",[1370,6448,6449,6452,6454,6457,6459,6461,6463,6466,6468,6471],{"class":1372,"line":1449},[1370,6450,6451],{"class":1381},"        backupProvider",[1370,6453,1392],{"class":1391},[1370,6455,6456],{"class":1395},"Publish",[1370,6458,1398],{"class":1391},[1370,6460,1492],{"class":1381},[1370,6462,1404],{"class":1391},[1370,6464,6465],{"class":1381}," originalData",[1370,6467,1404],{"class":1391},[1370,6469,6470],{"class":1381}," metadata",[1370,6472,1421],{"class":1391},[1370,6474,6475],{"class":1372,"line":1481},[1370,6476,5048],{"class":1391},[1370,6478,6479],{"class":1372,"line":1578},[1370,6480,3204],{"class":1391},[1356,6482,568],{"id":6483},"testing-error-handling",[1361,6485,6487],{"className":1363,"code":6486,"language":1365,"meta":34,"style":34},"func TestErrorHandling(t *testing.T) {\n    var capturedError herald.Error\n\n    capitan.Hook(herald.ErrorSignal, func(_ context.Context, e *capitan.Event) {\n        capturedError, _ = herald.ErrorKey.From(e)\n    })\n\n    // Trigger error condition\n    provider := &failingProvider{err: errors.New(\"connection refused\")}\n    pub := herald.NewPublisher(provider, signal, key, nil)\n    pub.Start()\n\n    capitan.Emit(ctx, signal, key.Field(order))\n    capitan.Shutdown()\n\n    if capturedError.Operation != \"publish\" {\n        t.Errorf(\"expected publish error, got %s\", capturedError.Operation)\n    }\n}\n",[1367,6488,6489,6514,6529,6533,6580,6607,6611,6615,6620,6651,6681,6691,6695,6725,6735,6739,6755,6784,6788],{"__ignoreMap":34},[1370,6490,6491,6493,6496,6498,6501,6503,6505,6507,6510,6512],{"class":1372,"line":9},[1370,6492,1660],{"class":1417},[1370,6494,6495],{"class":1395}," TestErrorHandling",[1370,6497,1398],{"class":1391},[1370,6499,6500],{"class":1571},"t",[1370,6502,2167],{"class":1781},[1370,6504,4275],{"class":1538},[1370,6506,1392],{"class":1391},[1370,6508,6509],{"class":1538},"T",[1370,6511,2177],{"class":1391},[1370,6513,1618],{"class":1391},[1370,6515,6516,6519,6522,6524,6526],{"class":1372,"line":19},[1370,6517,6518],{"class":1417},"    var",[1370,6520,6521],{"class":1381}," capturedError",[1370,6523,1388],{"class":1538},[1370,6525,1392],{"class":1391},[1370,6527,6528],{"class":1538},"Error\n",[1370,6530,6531],{"class":1372,"line":40},[1370,6532,1440],{"emptyLinePlaceholder":1439},[1370,6534,6535,6537,6539,6541,6543,6545,6547,6549,6551,6553,6555,6558,6560,6562,6564,6566,6568,6570,6572,6574,6576,6578],{"class":1372,"line":1436},[1370,6536,2133],{"class":1381},[1370,6538,1392],{"class":1391},[1370,6540,2138],{"class":1395},[1370,6542,1398],{"class":1391},[1370,6544,1270],{"class":1381},[1370,6546,1392],{"class":1391},[1370,6548,1013],{"class":1381},[1370,6550,1404],{"class":1391},[1370,6552,2148],{"class":1417},[1370,6554,1398],{"class":1391},[1370,6556,6557],{"class":1571},"_",[1370,6559,1679],{"class":1538},[1370,6561,1392],{"class":1391},[1370,6563,2159],{"class":1538},[1370,6565,1404],{"class":1391},[1370,6567,2164],{"class":1571},[1370,6569,2167],{"class":1781},[1370,6571,1346],{"class":1538},[1370,6573,1392],{"class":1391},[1370,6575,2174],{"class":1538},[1370,6577,2177],{"class":1391},[1370,6579,1618],{"class":1391},[1370,6581,6582,6585,6587,6589,6591,6593,6595,6597,6599,6601,6603,6605],{"class":1372,"line":1443},[1370,6583,6584],{"class":1381},"        capturedError",[1370,6586,1404],{"class":1391},[1370,6588,2190],{"class":1381},[1370,6590,3173],{"class":1381},[1370,6592,1388],{"class":1381},[1370,6594,1392],{"class":1391},[1370,6596,1018],{"class":1381},[1370,6598,1392],{"class":1391},[1370,6600,2199],{"class":1395},[1370,6602,1398],{"class":1391},[1370,6604,2204],{"class":1381},[1370,6606,1421],{"class":1391},[1370,6608,6609],{"class":1372,"line":1449},[1370,6610,1906],{"class":1391},[1370,6612,6613],{"class":1372,"line":1481},[1370,6614,1440],{"emptyLinePlaceholder":1439},[1370,6616,6617],{"class":1372,"line":1578},[1370,6618,6619],{"class":1375},"    // Trigger error condition\n",[1370,6621,6622,6624,6626,6628,6631,6633,6635,6637,6639,6641,6643,6645,6648],{"class":1372,"line":1584},[1370,6623,1912],{"class":1381},[1370,6625,1385],{"class":1381},[1370,6627,1782],{"class":1781},[1370,6629,6630],{"class":1538},"failingProvider",[1370,6632,1793],{"class":1391},[1370,6634,6193],{"class":1624},[1370,6636,1799],{"class":1391},[1370,6638,2962],{"class":1381},[1370,6640,1392],{"class":1391},[1370,6642,1922],{"class":1395},[1370,6644,1398],{"class":1391},[1370,6646,6647],{"class":1514},"\"connection refused\"",[1370,6649,6650],{"class":1391},")}\n",[1370,6652,6653,6655,6657,6659,6661,6663,6665,6667,6669,6671,6673,6675,6677,6679],{"class":1372,"line":1590},[1370,6654,1991],{"class":1381},[1370,6656,1385],{"class":1381},[1370,6658,1388],{"class":1381},[1370,6660,1392],{"class":1391},[1370,6662,851],{"class":1395},[1370,6664,1398],{"class":1391},[1370,6666,1401],{"class":1381},[1370,6668,1404],{"class":1391},[1370,6670,1407],{"class":1381},[1370,6672,1404],{"class":1391},[1370,6674,1412],{"class":1381},[1370,6676,1404],{"class":1391},[1370,6678,1418],{"class":1417},[1370,6680,1421],{"class":1391},[1370,6682,6683,6685,6687,6689],{"class":1372,"line":1596},[1370,6684,1991],{"class":1381},[1370,6686,1392],{"class":1391},[1370,6688,1430],{"class":1395},[1370,6690,1433],{"class":1391},[1370,6692,6693],{"class":1372,"line":1601},[1370,6694,1440],{"emptyLinePlaceholder":1439},[1370,6696,6697,6699,6701,6703,6705,6707,6709,6711,6713,6715,6717,6719,6721,6723],{"class":1372,"line":1606},[1370,6698,2133],{"class":1381},[1370,6700,1392],{"class":1391},[1370,6702,2271],{"class":1395},[1370,6704,1398],{"class":1391},[1370,6706,1492],{"class":1381},[1370,6708,1404],{"class":1391},[1370,6710,1407],{"class":1381},[1370,6712,1404],{"class":1391},[1370,6714,1412],{"class":1381},[1370,6716,1392],{"class":1391},[1370,6718,2288],{"class":1395},[1370,6720,1398],{"class":1391},[1370,6722,2576],{"class":1381},[1370,6724,1958],{"class":1391},[1370,6726,6727,6729,6731,6733],{"class":1372,"line":1621},[1370,6728,2133],{"class":1381},[1370,6730,1392],{"class":1391},[1370,6732,2330],{"class":1395},[1370,6734,1433],{"class":1391},[1370,6736,6737],{"class":1372,"line":1634},[1370,6738,1440],{"emptyLinePlaceholder":1439},[1370,6740,6741,6743,6745,6747,6749,6751,6753],{"class":1372,"line":1646},[1370,6742,4983],{"class":1781},[1370,6744,6521],{"class":1381},[1370,6746,1392],{"class":1391},[1370,6748,3584],{"class":1381},[1370,6750,5986],{"class":1781},[1370,6752,4995],{"class":1514},[1370,6754,1618],{"class":1391},[1370,6756,6757,6760,6762,6765,6767,6770,6772,6774,6776,6778,6780,6782],{"class":1372,"line":1652},[1370,6758,6759],{"class":1381},"        t",[1370,6761,1392],{"class":1391},[1370,6763,6764],{"class":1395},"Errorf",[1370,6766,1398],{"class":1391},[1370,6768,6769],{"class":1514},"\"expected publish error, got ",[1370,6771,2226],{"class":2225},[1370,6773,2233],{"class":1514},[1370,6775,1404],{"class":1391},[1370,6777,6521],{"class":1381},[1370,6779,1392],{"class":1391},[1370,6781,3584],{"class":1381},[1370,6783,1421],{"class":1391},[1370,6785,6786],{"class":1372,"line":1657},[1370,6787,5048],{"class":1391},[1370,6789,6790],{"class":1372,"line":1671},[1370,6791,1649],{"class":1391},[3706,6793,6794],{},"html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"title":34,"searchDepth":19,"depth":19,"links":6796},[6797,6798,6799,6805,6811,6812,6816],{"id":4676,"depth":19,"text":491},{"id":4802,"depth":19,"text":496},{"id":4895,"depth":19,"text":501,"children":6800},[6801,6802,6803,6804],{"id":4898,"depth":40,"text":505},{"id":5072,"depth":40,"text":510},{"id":5157,"depth":40,"text":515},{"id":5295,"depth":40,"text":520},{"id":5419,"depth":19,"text":525,"children":6806},[6807,6808,6809,6810],{"id":5422,"depth":40,"text":529},{"id":5576,"depth":40,"text":534},{"id":5719,"depth":40,"text":539},{"id":5879,"depth":40,"text":544},{"id":6110,"depth":19,"text":549},{"id":6232,"depth":19,"text":554,"children":6813},[6814,6815],{"id":6235,"depth":40,"text":558},{"id":6341,"depth":40,"text":563},{"id":6483,"depth":19,"text":568},{},"2025-12-11T00:00:00.000Z",null,{"title":165,"description":484},[1214,6822],"Errors","JDGxK6KiwuC0rlA3O3WzdsMU6wRY5_ZeS31LOy4Nkww",[6825,6826],{"title":155,"path":417,"stem":1221,"description":419,"children":-1},{"title":573,"path":572,"stem":1225,"description":575,"children":-1},1776113325457]