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

2.7 KiB

Gateway channel operations

Purpose

Free-function module containing every channel-level mutation the gateway exposes — subscribeToChannel, unsubscribeFromChannel, publishToChannel. Extracted from gateway.ts so the daemon file stays under the 300-line cap; consumed via import * as channelOps from "./channel-ops.js" from the gateway.

Features

  • subscribeToChannel runs the rate-limit + authorization gate before the subscribe.
  • unsubscribeFromChannel is a pure passthrough (no gating on unsubscribe — the proposal treats teardown actions as always-allowed).
  • publishToChannel gates first, then routes through the channel manager, returning the deterministic recipient set.

Usage: Basic Example

import * as channelOps from "@suaveplan/websocket-gateway/channel-ops";

await channelOps.subscribeToChannel(deps, "events", "alice", "user.created");

Usage: Advanced Example

import * as channelOps from "@suaveplan/websocket-gateway/channel-ops";

const result = await channelOps.publishToChannel(
    deps,
    "events",
    "alice",
    "user.created",
    { id: "u1", at: Date.now() }
);
// result.recipients contains every subscriber that matched, excluding alice.

API Reference

  • subscribeToChannel(deps, channel, clientId, topic): Promise<boolean>
  • unsubscribeFromChannel(mgr, channel, clientId, topic): boolean
  • publishToChannel(deps, channel, sender, topic, payload): Promise<{message, recipients} | null>
  • ChannelOpsDeps — structurally-typed dependency bundle.

Implementation Notes

unsubscribe does not consult the gate because rejecting an unsubscribe would leave the client subscribed when they explicitly asked to stop receiving — a worse failure mode than allowing the teardown. publishToChannel returns null when the channel hasn't been registered (no subscribers exist yet); the daemon translates that into a no-op rather than a typed error so empty channels are silently ignored.

The dep bundle is structural (ChannelOpsDeps), not nominal. Tests that exercise the ops module directly can construct minimal bundles wiring only the parts each test needs — the gate closures, the requireAuthCtx fallback, the clock — without instantiating the full daemon. The factoring follows the same pattern room-ops.ts uses and keeps the gateway file under the 300-line cap.

The subscribe path is the only place a channel gets created today; the getOrCreateChannel call hides inside ChannelManager.subscribe. This keeps the wire protocol implicit: a client subscribes, the channel exists. Publishes to never-subscribed channels stay no-ops by design — a future stronger contract (rejecting publishes to unknown channels) is a follow-up policy, not a current requirement.