# `Channel` _(no submodules)_ ## Purpose Pub/sub channel primitive with topic-based pattern matching. The gateway uses one `Channel` per registered logical channel id and routes `channel.publish` frames through it. Channels are independent of rooms — they let a single TCP connection carry multiple logical streams without interleaving (the daemon's "channel multiplexing" requirement). ## Features - Per-client subscription set, keyed by exact topic OR pattern. - Two wildcard verbs when `allowPatterns: true`: `*` matches one segment, `**` matches zero or more segments (separator `.`). - `publish(topic, message, excludeSender?)` returns deterministic recipient ids so the daemon dispatches outside the channel. - Invalid topics (`""`, or wildcard tokens under `allowPatterns: false`) throw `WS_GATEWAY.CHANNEL_INVALID` so the daemon can surface the typed denial back to the offending client. - Empty / negative subscriber sets clean up automatically so the channel can be observed for membership-based cleanup. ## Usage: Basic Example ```ts import { Channel } from "@suaveplan/websocket-gateway"; const ch = new Channel({ name: "events", allowPatterns: true }); ch.subscribe("alice", "user.*"); ch.publish("user.created", message); ``` ## Usage: Advanced Example ```ts import { Channel, WS_GATEWAY_CODES, WsGatewayError } from "@suaveplan/websocket-gateway"; const ch = new Channel({ name: "events", allowPatterns: false }); try { ch.subscribe("alice", "user.*"); } catch (err) { if (err instanceof WsGatewayError && err.code === WS_GATEWAY_CODES.CHANNEL_INVALID) { // wildcard rejected under allowPatterns:false } } const patterns = new Channel({ name: "logs", allowPatterns: true }); patterns.subscribe("bob", "logs.**"); // matches logs.app, logs.app.error, etc. patterns.unsubscribe("bob", "logs.**"); patterns.unsubscribeAll("bob"); ``` ## API Reference - `new Channel(config: ChannelConfig)` — throws `CHANNEL_INVALID` on empty `name`. - `name` / `allowPatterns` / `subscriberCount` getters. - `subscribe(clientId, topic): boolean` — false if already subscribed, throws on invalid topic. - `unsubscribe(clientId, topic): boolean` — false if not present. - `unsubscribeAll(clientId): boolean` — drops every subscription for one client. - `publish(topic, message, excludeSender?): string[]` — deterministic recipient set. - `getSubscriptions(clientId)` / `getSubscribers()` / `hasSubscriber(clientId)`. - `topicMatches(pattern, topic): boolean` — exposed for callers that want to check match without subscribing. ## Implementation Notes The wildcard matcher is a hand-rolled recursive descent over `.`-delimited segments. `**` recurses with the same pattern index across every possible tail of the topic, enabling zero-segment matches at the trailing position (`logs.**` matches `logs`). The flat per-client `Set` storage keeps subscribe/unsubscribe O(topics-per-client) and lets the daemon's disconnect path call `unsubscribeAll` in O(1). Publish is O(subscribers × topics-per-subscriber) — the design assumes channels stay under a few thousand subscribers in practice; sharded channels are the future job of a `websocket-gateway-store-redis` adapter.