# dispatch
```go
import "github.com/lukaszraczylo/go-telegram/dispatch"
```
Package dispatch provides a typed router for Telegram updates. It consumes any transport.Updater and dispatches updates to handlers registered by command, regex, or update\-payload kind.
## Index
- [Variables](<#variables>)
- [type Context](<#Context>)
- [func NewContext\(ctx context.Context, b \*client.Bot, u \*api.Update\) \*Context](<#NewContext>)
- [func \(c \*Context\) Set\(key string, val any\)](<#Context.Set>)
- [type Filter](<#Filter>)
- [func All\[T any\]\(filters ...Filter\[T\]\) Filter\[T\]](<#All>)
- [func Any\[T any\]\(filters ...Filter\[T\]\) Filter\[T\]](<#Any>)
- [func \(f Filter\[T\]\) And\(others ...Filter\[T\]\) Filter\[T\]](<#Filter[T].And>)
- [func \(f Filter\[T\]\) Not\(\) Filter\[T\]](<#Filter[T].Not>)
- [func \(f Filter\[T\]\) Or\(others ...Filter\[T\]\) Filter\[T\]](<#Filter[T].Or>)
- [type Handler](<#Handler>)
- [type Middleware](<#Middleware>)
- [func Chain\[T any\]\(mws ...Middleware\[T\]\) Middleware\[T\]](<#Chain>)
- [func Recovery\(\) Middleware\[\*api.Update\]](<#Recovery>)
- [type NamedHandlers](<#NamedHandlers>)
- [func NewNamedHandlers\[T any\]\(\) \*NamedHandlers\[T\]](<#NewNamedHandlers>)
- [func \(n \*NamedHandlers\[T\]\) Handler\(\) Handler\[T\]](<#NamedHandlers[T].Handler>)
- [func \(n \*NamedHandlers\[T\]\) Has\(name string\) bool](<#NamedHandlers[T].Has>)
- [func \(n \*NamedHandlers\[T\]\) Names\(\) \[\]string](<#NamedHandlers[T].Names>)
- [func \(n \*NamedHandlers\[T\]\) Remove\(name string\) bool](<#NamedHandlers[T].Remove>)
- [func \(n \*NamedHandlers\[T\]\) Set\(name string, h Handler\[T\]\)](<#NamedHandlers[T].Set>)
- [type Router](<#Router>)
- [func New\(b \*client.Bot, opts ...RouterOption\) \*Router](<#New>)
- [func \(r \*Router\) Group\(group int\) \*RouterScope](<#Router.Group>)
- [func \(r \*Router\) OnBusinessConnection\(h Handler\[\*api.BusinessConnection\]\)](<#Router.OnBusinessConnection>)
- [func \(r \*Router\) OnCallback\(pattern string, h Handler\[\*api.CallbackQuery\]\)](<#Router.OnCallback>)
- [func \(r \*Router\) OnCallbackFilter\(f Filter\[\*api.CallbackQuery\], h Handler\[\*api.CallbackQuery\]\)](<#Router.OnCallbackFilter>)
- [func \(r \*Router\) OnChannelPost\(h Handler\[\*api.Message\]\)](<#Router.OnChannelPost>)
- [func \(r \*Router\) OnChatBoost\(h Handler\[\*api.ChatBoostUpdated\]\)](<#Router.OnChatBoost>)
- [func \(r \*Router\) OnChatJoinRequest\(h Handler\[\*api.ChatJoinRequest\]\)](<#Router.OnChatJoinRequest>)
- [func \(r \*Router\) OnChatJoinRequestFilter\(f Filter\[\*api.ChatJoinRequest\], h Handler\[\*api.ChatJoinRequest\]\)](<#Router.OnChatJoinRequestFilter>)
- [func \(r \*Router\) OnChatMember\(h Handler\[\*api.ChatMemberUpdated\]\)](<#Router.OnChatMember>)
- [func \(r \*Router\) OnChatMemberFilter\(f Filter\[\*api.ChatMemberUpdated\], h Handler\[\*api.ChatMemberUpdated\]\)](<#Router.OnChatMemberFilter>)
- [func \(r \*Router\) OnChosenInlineResult\(h Handler\[\*api.ChosenInlineResult\]\)](<#Router.OnChosenInlineResult>)
- [func \(r \*Router\) OnCommand\(cmd string, h Handler\[\*api.Message\]\)](<#Router.OnCommand>)
- [func \(r \*Router\) OnEditedChannelPost\(h Handler\[\*api.Message\]\)](<#Router.OnEditedChannelPost>)
- [func \(r \*Router\) OnEditedMessage\(h Handler\[\*api.Message\]\)](<#Router.OnEditedMessage>)
- [func \(r \*Router\) OnInlineQuery\(h Handler\[\*api.InlineQuery\]\)](<#Router.OnInlineQuery>)
- [func \(r \*Router\) OnInlineQueryFilter\(f Filter\[\*api.InlineQuery\], h Handler\[\*api.InlineQuery\]\)](<#Router.OnInlineQueryFilter>)
- [func \(r \*Router\) OnMessageFilter\(f Filter\[\*api.Message\], h Handler\[\*api.Message\]\)](<#Router.OnMessageFilter>)
- [func \(r \*Router\) OnMessageReaction\(h Handler\[\*api.MessageReactionUpdated\]\)](<#Router.OnMessageReaction>)
- [func \(r \*Router\) OnMessageReactionCount\(h Handler\[\*api.MessageReactionCountUpdated\]\)](<#Router.OnMessageReactionCount>)
- [func \(r \*Router\) OnMyChatMember\(h Handler\[\*api.ChatMemberUpdated\]\)](<#Router.OnMyChatMember>)
- [func \(r \*Router\) OnMyChatMemberFilter\(f Filter\[\*api.ChatMemberUpdated\], h Handler\[\*api.ChatMemberUpdated\]\)](<#Router.OnMyChatMemberFilter>)
- [func \(r \*Router\) OnPoll\(h Handler\[\*api.Poll\]\)](<#Router.OnPoll>)
- [func \(r \*Router\) OnPollAnswer\(h Handler\[\*api.PollAnswer\]\)](<#Router.OnPollAnswer>)
- [func \(r \*Router\) OnPreCheckoutQuery\(h Handler\[\*api.PreCheckoutQuery\]\)](<#Router.OnPreCheckoutQuery>)
- [func \(r \*Router\) OnPreCheckoutQueryFilter\(f Filter\[\*api.PreCheckoutQuery\], h Handler\[\*api.PreCheckoutQuery\]\)](<#Router.OnPreCheckoutQueryFilter>)
- [func \(r \*Router\) OnPurchasedPaidMedia\(h Handler\[\*api.PaidMediaPurchased\]\)](<#Router.OnPurchasedPaidMedia>)
- [func \(r \*Router\) OnRemovedChatBoost\(h Handler\[\*api.ChatBoostRemoved\]\)](<#Router.OnRemovedChatBoost>)
- [func \(r \*Router\) OnShippingQuery\(h Handler\[\*api.ShippingQuery\]\)](<#Router.OnShippingQuery>)
- [func \(r \*Router\) OnText\(pattern string, h Handler\[\*api.Message\]\)](<#Router.OnText>)
- [func \(r \*Router\) Process\(ctx context.Context, u \*api.Update\) error](<#Router.Process>)
- [func \(r \*Router\) Run\(ctx context.Context, u transport.Updater\) error](<#Router.Run>)
- [func \(r \*Router\) Use\(mw Middleware\[\*api.Update\]\)](<#Router.Use>)
- [type RouterOption](<#RouterOption>)
- [func WithMaxConcurrency\(n int\) RouterOption](<#WithMaxConcurrency>)
- [type RouterScope](<#RouterScope>)
- [func \(s \*RouterScope\) OnCommand\(cmd string, h Handler\[\*api.Message\]\)](<#RouterScope.OnCommand>)
- [func \(s \*RouterScope\) OnMessageFilter\(f Filter\[\*api.Message\], h Handler\[\*api.Message\]\)](<#RouterScope.OnMessageFilter>)
- [func \(s \*RouterScope\) OnText\(pattern string, h Handler\[\*api.Message\]\)](<#RouterScope.OnText>)
## Variables
ErrContinueGroups signals that this group's handler should be treated as not\-matching when returned by a handler: dispatch moves on to the next handler in the same group, then to subsequent groups.
Without ErrContinueGroups, a non\-error return from a matched handler stops dispatch \(default first\-match\-wins semantics\).
```go
var ErrContinueGroups = errors.New("dispatch: continue groups")
```
ErrEndGroups stops dispatch from running any further handlers in any group for this update when returned by a handler. Use it to indicate the update has been definitively handled.
errors.Is\(err, ErrEndGroups\) is the canonical check, though dispatch itself recognises it by exact identity.
```go
var ErrEndGroups = errors.New("dispatch: end groups")
```
## type [Context]()
Context bundles the per\-update state every handler receives.
Ctx is the request context propagated from Router.Run; cancelling the run cancels every handler.
Bot is the API client. Handlers reply by calling api.SendMessage\(c.Ctx, c.Bot, ...\) etc.
Update is the raw update; payload\-typed handlers also receive a narrowed pointer to one of its sub\-fields.
Command, CommandArgs and RegexMatch are populated by the router for the matching route kind; they replace the previous "command", "command\_args" and "regex\_match" entries in Values, which were the only conventional keys. Values remains for user\-defined custom keys.
Command is the matched bot command \(e.g. "/start"\); empty when the route is not a command match.
CommandArgs is everything after the command; empty when no command matched or the command had no trailing text.
RegexMatch is the regex sub\-matches when an OnText/OnCallback regex route matched; nil otherwise.
Values is lazily allocated for user\-defined keys. Handlers that don't write pay no allocation. Reads against a nil map return the zero value. Writers must use Set instead of indexing the map directly.
```go
type Context struct {
Ctx context.Context
Bot *client.Bot
Update *api.Update
Command string
CommandArgs string
RegexMatch []string
Values map[string]any
}
```
### func [NewContext]()
```go
func NewContext(ctx context.Context, b *client.Bot, u *api.Update) *Context
```
NewContext constructs a Context. Used by Router internally; exposed for custom test harnesses.
### func \(\*Context\) [Set]()
```go
func (c *Context) Set(key string, val any)
```
Set writes key/val into Values, allocating the map on first use. Use this instead of \`c.Values\[k\] = v\` so the no\-write path stays allocation\-free.
## type [Filter]()
Filter is a predicate over a typed payload \(e.g. \*api.Message\). Filters compose via And/Or/Not for multi\-condition matching.
Example:
```
f := message.HasPhoto().And(message.InChat(-100123456789))
```
```go
type Filter[T any] func(payload T) bool
```
### func [All]()
```go
func All[T any](filters ...Filter[T]) Filter[T]
```
All combines filters with AND. Returns a Filter that matches when all match. Returns a filter that always matches when filters is empty.
### func [Any]()
```go
func Any[T any](filters ...Filter[T]) Filter[T]
```
Any combines filters with OR. Returns a Filter that matches when at least one matches. Returns a filter that never matches when filters is empty.
### func \(Filter\[T\]\) [And]()
```go
func (f Filter[T]) And(others ...Filter[T]) Filter[T]
```
And returns a Filter that matches iff f and every one of others matches.
### func \(Filter\[T\]\) [Not]()
```go
func (f Filter[T]) Not() Filter[T]
```
Not returns a Filter that inverts f.
### func \(Filter\[T\]\) [Or]()
```go
func (f Filter[T]) Or(others ...Filter[T]) Filter[T]
```
Or returns a Filter that matches iff f matches OR any of others matches.
## type [Handler]()
Handler is a generic handler over update payload type T. T is typically \*api.Message, \*api.CallbackQuery, \*api.InlineQuery, or \*api.Update for global middleware.
```go
type Handler[T any] func(ctx *Context, payload T) error
```
## type [Middleware]()
Middleware wraps a Handler\[T\] with cross\-cutting behaviour \(logging, recovery, auth\). Middleware composition is left\-to\-right: Use\(a,b,c\) runs as a\(b\(c\(handler\)\)\).
```go
type Middleware[T any] func(Handler[T]) Handler[T]
```
### func [Chain]()
```go
func Chain[T any](mws ...Middleware[T]) Middleware[T]
```
Chain composes a slice of middleware into a single Middleware\[T\].
### func [Recovery]()
```go
func Recovery() Middleware[*api.Update]
```
Recovery returns middleware that recovers from panics in downstream handlers, converting them into a returned error and logging via the bot's configured logger. Registered automatically by NewRouter.
## type [NamedHandlers]()
NamedHandlers manages handlers by string name, allowing runtime registration, replacement, and removal. This complements the Router's registration methods: each registration via Named\*\(\) also gets a name for later lookup.
Use case: a plugin system that loads/unloads command handlers without restarting the bot.
```go
type NamedHandlers[T any] struct {
// contains filtered or unexported fields
}
```
### func [NewNamedHandlers]()
```go
func NewNamedHandlers[T any]() *NamedHandlers[T]
```
NewNamedHandlers returns a new, empty NamedHandlers\[T\].
### func \(\*NamedHandlers\[T\]\) [Handler]()
```go
func (n *NamedHandlers[T]) Handler() Handler[T]
```
Handler returns a single Handler\[T\] that runs each registered handler in registration order, first non\-nil error stops the chain. Use this to wire NamedHandlers into a Router.OnXxx call:
```
names := dispatch.NewNamedHandlers[*api.Message]()
names.Set("logger", loggingHandler)
names.Set("audit", auditHandler)
router.OnCommand("/admin", names.Handler())
```
Subsequent Set/Remove calls take effect on the next dispatch.
### func \(\*NamedHandlers\[T\]\) [Has]()
```go
func (n *NamedHandlers[T]) Has(name string) bool
```
Has reports whether name is registered.
### func \(\*NamedHandlers\[T\]\) [Names]()
```go
func (n *NamedHandlers[T]) Names() []string
```
Names returns the registered names in registration order.
### func \(\*NamedHandlers\[T\]\) [Remove]()
```go
func (n *NamedHandlers[T]) Remove(name string) bool
```
Remove unregisters the handler under name. Returns true if it existed.
### func \(\*NamedHandlers\[T\]\) [Set]()
```go
func (n *NamedHandlers[T]) Set(name string, h Handler[T])
```
Set registers or replaces the handler under name. If name is new, it is appended to the end of the registration order.
## type [Router]()
Router dispatches updates from any Updater to typed handlers.
Matchers run in registration order; first match wins. A panic\-recovery middleware is attached automatically and runs around every dispatch.
```go
type Router struct {
// contains filtered or unexported fields
}
```
### func [New]()
```go
func New(b *client.Bot, opts ...RouterOption) *Router
```
New constructs a Router. Recovery middleware is added by default; users can disable it by passing WithoutRecovery \(not implemented here, but the hook is in place via Use\).
### func \(\*Router\) [Group]()
```go
func (r *Router) Group(group int) *RouterScope
```
Group returns a RouterScope that registers handlers in the given group. Group 0 \(the default\) runs first, then group 1, etc. Within a group, handlers run in registration order; the first non\-skipped match terminates dispatch unless the handler returns ErrContinueGroups.
### func \(\*Router\) [OnBusinessConnection]()
```go
func (r *Router) OnBusinessConnection(h Handler[*api.BusinessConnection])
```
OnBusinessConnection registers a handler for business connection updates.
### func \(\*Router\) [OnCallback]()
```go
func (r *Router) OnCallback(pattern string, h Handler[*api.CallbackQuery])
```
OnCallback registers a handler for callback queries whose Data matches the regex.
Panics at registration time if pattern is not a valid regular expression.
### func \(\*Router\) [OnCallbackFilter]()
```go
func (r *Router) OnCallbackFilter(f Filter[*api.CallbackQuery], h Handler[*api.CallbackQuery])
```
OnCallbackFilter registers a typed callback\-query handler gated by filter f. Filter routes are checked after pattern\-based OnCallback routes; first match wins.
### func \(\*Router\) [OnChannelPost]()
```go
func (r *Router) OnChannelPost(h Handler[*api.Message])
```
OnChannelPost registers a handler for channel post updates.
### func \(\*Router\) [OnChatBoost]()
```go
func (r *Router) OnChatBoost(h Handler[*api.ChatBoostUpdated])
```
OnChatBoost registers a handler for chat boost updates.
### func \(\*Router\) [OnChatJoinRequest]()
```go
func (r *Router) OnChatJoinRequest(h Handler[*api.ChatJoinRequest])
```
OnChatJoinRequest registers a handler for chat join requests.
### func \(\*Router\) [OnChatJoinRequestFilter]()
```go
func (r *Router) OnChatJoinRequestFilter(f Filter[*api.ChatJoinRequest], h Handler[*api.ChatJoinRequest])
```
OnChatJoinRequestFilter registers a filtered handler for chat join requests.
### func \(\*Router\) [OnChatMember]()
```go
func (r *Router) OnChatMember(h Handler[*api.ChatMemberUpdated])
```
OnChatMember registers a handler for chat member status changes.
### func \(\*Router\) [OnChatMemberFilter]()
```go
func (r *Router) OnChatMemberFilter(f Filter[*api.ChatMemberUpdated], h Handler[*api.ChatMemberUpdated])
```
OnChatMemberFilter registers a filtered handler for chat member status changes.
### func \(\*Router\) [OnChosenInlineResult]()
```go
func (r *Router) OnChosenInlineResult(h Handler[*api.ChosenInlineResult])
```
OnChosenInlineResult registers a handler for chosen inline results.
### func \(\*Router\) [OnCommand]()
```go
func (r *Router) OnCommand(cmd string, h Handler[*api.Message])
```
OnCommand registers a handler for a slash command. The command string includes the leading slash \(e.g. "/start"\). Matching strips an optional "@BotName" suffix.
### func \(\*Router\) [OnEditedChannelPost]()
```go
func (r *Router) OnEditedChannelPost(h Handler[*api.Message])
```
OnEditedChannelPost registers a handler for edited channel post updates.
### func \(\*Router\) [OnEditedMessage]()
```go
func (r *Router) OnEditedMessage(h Handler[*api.Message])
```
OnEditedMessage registers a handler for edited message updates.
### func \(\*Router\) [OnInlineQuery]()
```go
func (r *Router) OnInlineQuery(h Handler[*api.InlineQuery])
```
OnInlineQuery registers a handler for inline queries \(one matcher only; inline queries are not partitioned by content here\).
### func \(\*Router\) [OnInlineQueryFilter]()
```go
func (r *Router) OnInlineQueryFilter(f Filter[*api.InlineQuery], h Handler[*api.InlineQuery])
```
OnInlineQueryFilter registers an inline\-query handler gated by filter f. Filter routes are checked after bare OnInlineQuery handlers; first match wins.
### func \(\*Router\) [OnMessageFilter]()
```go
func (r *Router) OnMessageFilter(f Filter[*api.Message], h Handler[*api.Message])
```
OnMessageFilter registers a typed message handler gated by filter f. Filter routes are checked after command and text routes; first match wins.
### func \(\*Router\) [OnMessageReaction]()
```go
func (r *Router) OnMessageReaction(h Handler[*api.MessageReactionUpdated])
```
OnMessageReaction registers a handler for message reaction updates.
### func \(\*Router\) [OnMessageReactionCount]()
```go
func (r *Router) OnMessageReactionCount(h Handler[*api.MessageReactionCountUpdated])
```
OnMessageReactionCount registers a handler for anonymous message reaction count updates.
### func \(\*Router\) [OnMyChatMember]()
```go
func (r *Router) OnMyChatMember(h Handler[*api.ChatMemberUpdated])
```
OnMyChatMember registers a handler for bot's own chat member status changes.
### func \(\*Router\) [OnMyChatMemberFilter]()
```go
func (r *Router) OnMyChatMemberFilter(f Filter[*api.ChatMemberUpdated], h Handler[*api.ChatMemberUpdated])
```
OnMyChatMemberFilter registers a filtered handler for bot's own chat member status changes.
### func \(\*Router\) [OnPoll]()
```go
func (r *Router) OnPoll(h Handler[*api.Poll])
```
OnPoll registers a handler for poll state updates.
### func \(\*Router\) [OnPollAnswer]()
```go
func (r *Router) OnPollAnswer(h Handler[*api.PollAnswer])
```
OnPollAnswer registers a handler for poll answer updates.
### func \(\*Router\) [OnPreCheckoutQuery]()
```go
func (r *Router) OnPreCheckoutQuery(h Handler[*api.PreCheckoutQuery])
```
OnPreCheckoutQuery registers a handler for pre\-checkout queries.
### func \(\*Router\) [OnPreCheckoutQueryFilter]()
```go
func (r *Router) OnPreCheckoutQueryFilter(f Filter[*api.PreCheckoutQuery], h Handler[*api.PreCheckoutQuery])
```
OnPreCheckoutQueryFilter registers a filtered handler for pre\-checkout queries.
### func \(\*Router\) [OnPurchasedPaidMedia]()
```go
func (r *Router) OnPurchasedPaidMedia(h Handler[*api.PaidMediaPurchased])
```
OnPurchasedPaidMedia registers a handler for purchased paid media updates.
### func \(\*Router\) [OnRemovedChatBoost]()
```go
func (r *Router) OnRemovedChatBoost(h Handler[*api.ChatBoostRemoved])
```
OnRemovedChatBoost registers a handler for removed chat boost updates.
### func \(\*Router\) [OnShippingQuery]()
```go
func (r *Router) OnShippingQuery(h Handler[*api.ShippingQuery])
```
OnShippingQuery registers a handler for shipping queries.
### func \(\*Router\) [OnText]()
```go
func (r *Router) OnText(pattern string, h Handler[*api.Message])
```
OnText registers a handler for messages whose Text matches the regex.
Panics at registration time if pattern is not a valid regular expression.
### func \(\*Router\) [Process]()
```go
func (r *Router) Process(ctx context.Context, u *api.Update) error
```
Run consumes the Updater and dispatches each update. It blocks until the Updater's channel is closed or ctx is cancelled.
By default updates are processed concurrently \(up to WithMaxConcurrency\(50\) goroutines\). Handlers for different updates may therefore run simultaneously; shared state must be protected. Pass WithMaxConcurrency\(0\) to New to restore serial \(legacy\) behaviour.
Run waits for all in\-flight handlers to finish before returning. Process runs a single update through the router's middleware and handler chain synchronously. Entry point for callers sourcing updates outside the standard transport.Updater flow — custom webhook frameworks, message\-bus consumers, or tests driving the router without spinning up Run.
Honours the router's global middleware \(Use\) but bypasses the concurrency semaphore wired up by Run; the caller controls parallelism.
### func \(\*Router\) [Run]()
```go
func (r *Router) Run(ctx context.Context, u transport.Updater) error
```
### func \(\*Router\) [Use]()
```go
func (r *Router) Use(mw Middleware[*api.Update])
```
Use registers a global middleware applied to every Update dispatch.
## type [RouterOption]()
RouterOption configures a Router at construction time.
```go
type RouterOption func(*Router)
```
### func [WithMaxConcurrency]()
```go
func WithMaxConcurrency(n int) RouterOption
```
WithMaxConcurrency sets the maximum number of updates processed in parallel. Default is 50. Pass 0 to dispatch serially \(one update at a time, in the calling goroutine — the legacy behaviour before v1.1.0\).
Note: concurrent dispatch means handlers for different updates may run simultaneously. Handlers that mutate shared state must be safe for concurrent access.
## type [RouterScope]()
RouterScope registers handlers into a specific priority group on its parent Router. Group 0 runs first, then group 1, etc. Within a group, handlers run in registration order; the first non\-skipped match terminates dispatch unless the handler returns ErrContinueGroups.
```go
type RouterScope struct {
// contains filtered or unexported fields
}
```
### func \(\*RouterScope\) [OnCommand]()
```go
func (s *RouterScope) OnCommand(cmd string, h Handler[*api.Message])
```
OnCommand registers a command handler in this group.
### func \(\*RouterScope\) [OnMessageFilter]()
```go
func (s *RouterScope) OnMessageFilter(f Filter[*api.Message], h Handler[*api.Message])
```
OnMessageFilter registers a filter\-based message handler in this group.
### func \(\*RouterScope\) [OnText]()
```go
func (s *RouterScope) OnText(pattern string, h Handler[*api.Message])
```
OnText registers a regex text handler in this group. Panics at registration time if pattern is not a valid regular expression.
Generated by [gomarkdoc]()