Files
skills/docs/networking/network/websocket-gateway/src/channel/channel.md
2026-05-29 08:20:56 +01:00

3.2 KiB
Raw Blame History

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

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) — 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<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.