first commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# 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
|
||||
|
||||
```ts
|
||||
import * as channelOps from "@suaveplan/websocket-gateway/channel-ops";
|
||||
|
||||
await channelOps.subscribeToChannel(deps, "events", "alice", "user.created");
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user