This page describes the event pipeline architecture in src/websocket/pipeline/. Events are categorized into pipelines by type, providing a framework for validation, rate limiting, idempotency and tracing.
connection-handler.tsdoes not callrouteEventToPipelinetoday. Payload validation runs increateValidationMiddleware, and handlers call the rate limit stage (enforceGameRateLimit) and idempotency helpers directly.
Overview
Client Event → Zod Validation → Rate Limit Stage → Handler → Response
↑ ↓
schemas/client-events.ts game-events/
lobby-events.ts
state-sync.ts
Pipelines
1. Game Pipeline (game-pipeline.ts)
Handles all player actions at poker tables.
Events:
join_table– Player joins a tableleave_table– Player leaves a tableplayer_action– Betting actions (fold, check, call, bet, raise, all-in)sit_out– Player marks themselves as sitting outsit_in– Player returns to active playadd_chips– Player adds chips to their stackchat_message– Player sends a chat messagerequest_table_state– Player requests current table state
Stages (each wrapped with tracing):
- Validation – Verify request data and user authentication
- Authorization – Check player is at a table (for table-required events)
- Rate limit – Apply per-user limits
- Idempotency – Return the cached response for duplicate events
- Logging – Log the event for observability
- Completion – Record processing time
Rate limiting (RATE_LIMITED_EVENTS) also covers tournament_rebuy, tournament_addon, tournament_reentry, request_lobby_state, watch_table and stop_watching, which are not in EVENT_PIPELINE_MAP.
2. Lobby Pipeline (lobby-pipeline.ts)
Handles lobby subscriptions and state requests.
Events:
subscribe_lobby– Client subscribes to lobby updatesunsubscribe_lobby– Client unsubscribes from lobby updatesrequest_lobby_state– Client requests current lobby state
Stages:
- Validation – Verify user authentication
- Logging – Log the event for observability
- Completion – Record processing time
3. State Sync Pipeline (state-sync-pipeline.ts)
Handles server-initiated state synchronization broadcasts.
Operations:
full_state_broadcast– Send complete table state to all playersshowdown_broadcast– Reveal cards during showdownplayer_notification– Send targeted notification to specific player
Stages:
- State Filtering – Filter state based on viewer (hide opponent cards)
- Delivery – Send to appropriate recipients
- Logging – Log the broadcast for observability
Event Context
Every event flowing through a pipeline has an associated EventContext:
interface EventContext {
correlationId: string; // Unique ID for tracing (evt_<time>_<random>)
timestamp: number; // When event was received
socket: PokerSocket; // The socket that sent the event
userId: string; // User ID
username: string; // Username
currentTableId?: string; // Table ID (if at a table)
metadata: {
eventName: string; // The event type
pipeline: PipelineType; // Which pipeline is processing
stages: string[]; // Processing stages completed
warnings: string[]; // Any warnings
processingTimeMs?: number; // Total processing time
};
}
Event Routing
EVENT_PIPELINE_MAP in pipeline/types.ts maps events to pipelines:
// Game events
join_table → game pipeline
leave_table → game pipeline
player_action → game pipeline
sit_out → game pipeline
sit_in → game pipeline
add_chips → game pipeline
chat_message → game pipeline
request_table_state → game pipeline
// Lobby events
subscribe_lobby → lobby pipeline
unsubscribe_lobby → lobby pipeline
request_lobby_state → lobby pipeline
Usage
Routing events through a pipeline:
import { createEventContext, routeEventToPipeline, getPipelineForEvent } from './pipeline/index.js';
// In connection handler
socket.on('some_event', async (data, callback) => {
const pipelineType = getPipelineForEvent('some_event');
if (!pipelineType) return;
const context = createEventContext(socket, 'some_event', pipelineType);
const result = await routeEventToPipeline(context, data);
if (callback) {
callback({ success: result.success, error: result.error });
}
});
Checking event type:
import { isGameEvent, isLobbyEvent, isStateSyncEvent } from './pipeline/index.js';
if (isGameEvent(eventName)) {
// Handle as game event
}
Logging state sync operations:
import { logStateSyncOperation } from './pipeline/index.js';
logStateSyncOperation(tableId, 'full_state_broadcast', {
playerCount: 5,
seq: 42,
});
Architecture Benefits
- Consistent Handling – Events routed through a pipeline go through the same stages
- Centralized Validation – Validation happens before handler execution
- Event Tracing – Correlation IDs allow tracing events through the system
- Observability – Structured logging at each pipeline stage
- Clean Separation – Game, lobby, and state-sync concerns are separated
- Extensibility – New stages can be added without modifying handlers
File Structure
src/websocket/pipeline/
├── index.ts # Main exports and pipeline router
├── types.ts # Core types (EventContext, Pipeline, etc.)
├── game-pipeline.ts # Game event pipeline
├── lobby-pipeline.ts # Lobby event pipeline
├── state-sync-pipeline.ts # State sync pipeline
├── rate-limit-stage.ts # Per-user rate limits
├── idempotency-stage.ts # Duplicate event handling
├── idempotency-store.ts # Idempotency key storage
├── tracing-stage.ts # Event tracing
├── event-trace-store.ts # Trace storage
├── guaranteed-delivery-stage.ts # Delivery wrapping and acks
├── delivery-tracker.ts # Retry tracking
└── README.md
Integration with Existing Code
The pipeline modules work alongside the handlers:
- Zod Validation (
schemas/client-events.ts) – Validates event payloads before pipeline - Event Handlers (
game-events/,lobby-events.ts) – Execute business logic - State Sync (
state-sync.ts) – Handles state synchronization - Table Manager (
table-manager.ts) – Manages table instances
The pipelines provide the framework; handlers provide the implementation.