Problem
The WebSocket gateway's stream-status emit helpers are never called from production code. StreamsGateway (api/src/gateways/streams.gateway.ts) provides emitStarted, emitStopped, and emitError — the only production paths that would broadcast stream:started / stream:stopped / stream:error to subscribed clients — and a repo-wide search shows they are referenced only in the gateway's own spec (api/src/gateways/streams.gateway.spec.ts). No service or controller invokes them.
Meanwhile StreamsService.update() (api/src/streams/streams.service.ts) already fires the status-transition side effects: it validates transitions, persists the change, and dispatches webhooks via dispatchStatusWebhook — but nothing emits the socket event. The dashboard's realtime status feature (app/hooks/useStreamSocket.ts, which subscribes to stream:started/stream:stopped/stream:error and updates streamStatus) can never receive a status update, so the "live" status badge is permanently stale and issue #362's realtime requirement is unmet in practice.
The gateway class JSDoc promises this behaviour:
Wire events (server → client):
stream:started { streamId, userId, startedAt }
stream:stopped { streamId, userId, stoppedAt, reason? }
stream:error { streamId, userId?, occurredAt, code, message }
Root cause
// api/src/streams/streams.service.ts — update()
if (changes.status !== undefined && changes.status !== stream.status) {
this.dispatchStatusWebhook(updated, changes.status) // ← webhooks, not sockets
}
// api/src/gateways/streams.gateway.ts
emitStarted(payload: StreamStartedPayload): void { ... } // ← never called anywhere
StreamsService depends on WebhooksService but not on StreamsGateway; the gateway is only injected where notifications are emitted (NotificationsService).
Why this is architecturally hard
StreamsGateway is not a dependency of StreamsModule today, so wiring the emit means adding the dependency and deciding how the service reaches the gateway without a circular import (the gateway lives in GatewaysModule; NotificationsService injects it via @Optional()). There is an established pattern to copy (api/src/notifications/notifications.service.ts).
- The emit payloads (
StreamStartedPayload, StreamStoppedPayload, StreamErrorPayload in api/src/gateways/stream-events.ts) must be built from the updated stream state and the transition — mirroring the payload shapes dispatchStatusWebhook already builds in StreamsService — so the two side-effect paths (webhook + socket) should share a single event-shape source to avoid drifting.
- The
error transition is special: PATCH /streams/:id can set status: "error" but the payload contract for stream:error (code, message) has no source in UpdateStreamDto today. Decide whether the error event is emitted with defaults or whether UpdateStreamDto gains optional error fields — that is a small API contract decision.
- Scope of the broadcast:
emitStarted/emitStopped/emitError broadcast to the per-stream room; the ownership story for who may subscribe to a room is a separate security issue, but the wiring here should not accidentally widen visibility.
Acceptance criteria
Service
Tests
Documentation
Out of scope
Ownership enforcement on stream:subscribe (separate issue), and emitting events for anything other than status transitions.
Getting started
Real files in scope: api/src/streams/streams.service.ts, api/src/gateways/streams.gateway.ts, api/src/gateways/stream-events.ts, api/src/gateways/streams.gateway.spec.ts, api/src/streams/streams.service.spec.ts, api/src/notifications/notifications.service.ts (injection pattern), api/src/streams/streams.module.ts.
Verify with:
cd api && npm run typecheck && npm test
Good first files to read: api/src/streams/streams.service.ts (dispatchStatusWebhook), api/src/gateways/streams.gateway.ts (emit helpers), api/src/gateways/stream-events.ts.
Problem
The WebSocket gateway's stream-status emit helpers are never called from production code.
StreamsGateway(api/src/gateways/streams.gateway.ts) providesemitStarted,emitStopped, andemitError— the only production paths that would broadcaststream:started/stream:stopped/stream:errorto subscribed clients — and a repo-wide search shows they are referenced only in the gateway's own spec (api/src/gateways/streams.gateway.spec.ts). No service or controller invokes them.Meanwhile
StreamsService.update()(api/src/streams/streams.service.ts) already fires the status-transition side effects: it validates transitions, persists the change, and dispatches webhooks viadispatchStatusWebhook— but nothing emits the socket event. The dashboard's realtime status feature (app/hooks/useStreamSocket.ts, which subscribes tostream:started/stream:stopped/stream:errorand updatesstreamStatus) can never receive a status update, so the "live" status badge is permanently stale and issue #362's realtime requirement is unmet in practice.The gateway class JSDoc promises this behaviour:
Root cause
StreamsServicedepends onWebhooksServicebut not onStreamsGateway; the gateway is only injected where notifications are emitted (NotificationsService).Why this is architecturally hard
StreamsGatewayis not a dependency ofStreamsModuletoday, so wiring the emit means adding the dependency and deciding how the service reaches the gateway without a circular import (the gateway lives inGatewaysModule;NotificationsServiceinjects it via@Optional()). There is an established pattern to copy (api/src/notifications/notifications.service.ts).StreamStartedPayload,StreamStoppedPayload,StreamErrorPayloadinapi/src/gateways/stream-events.ts) must be built from the updated stream state and the transition — mirroring the payload shapesdispatchStatusWebhookalready builds inStreamsService— so the two side-effect paths (webhook + socket) should share a single event-shape source to avoid drifting.errortransition is special:PATCH /streams/:idcan setstatus: "error"but the payload contract forstream:error(code,message) has no source inUpdateStreamDtotoday. Decide whether the error event is emitted with defaults or whetherUpdateStreamDtogains optional error fields — that is a small API contract decision.emitStarted/emitStopped/emitErrorbroadcast to the per-stream room; the ownership story for who may subscribe to a room is a separate security issue, but the wiring here should not accidentally widen visibility.Acceptance criteria
Service
PATCH /streams/:id(e.g.inactive -> active) emits the matchingstream:started/stream:stopped/stream:errorevent on the/streamsnamespace, scoped to the affected stream's room, with the payload shapes fromapi/src/gateways/stream-events.ts.validateStatusTransition) do not emit any event.Tests
StreamsServicetest asserts the gateway emit is invoked with the correct event name and payload for each valid transition (inactive->active,active->inactive,*->error,error->inactive).stream:subscribe.Documentation
StreamsGatewayclass JSDoc's wire-event list matches what the service actually emits after the change.Out of scope
Ownership enforcement on
stream:subscribe(separate issue), and emitting events for anything other than status transitions.Getting started
Real files in scope:
api/src/streams/streams.service.ts,api/src/gateways/streams.gateway.ts,api/src/gateways/stream-events.ts,api/src/gateways/streams.gateway.spec.ts,api/src/streams/streams.service.spec.ts,api/src/notifications/notifications.service.ts(injection pattern),api/src/streams/streams.module.ts.Verify with:
Good first files to read:
api/src/streams/streams.service.ts(dispatchStatusWebhook),api/src/gateways/streams.gateway.ts(emit helpers),api/src/gateways/stream-events.ts.