3.2 KiB
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 underallowPatterns: false) throwWS_GATEWAY.CHANNEL_INVALIDso 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
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
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)— throwsCHANNEL_INVALIDon emptyname.name/allowPatterns/subscriberCountgetters.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<string> 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.