first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.