# Schema-validated socket handlers

Status: unreleased.

Each message handler owns its inbound schema. `SocketRoute` builds its dispatch table from the handler classes it is given: the route path chooses the service, and the message `type` chooses the handler. There is no separate message-contract object to keep synchronized with the route.

The maintained [socket recipe](../recipes/socket/README.md) includes the [schema definitions](../recipes/socket/schema.ts), handlers, route, and real-network tests.

```ts
import { BaseHandler, SocketRoute, defineRedwebSchemas } from 'redweb';
import type { MessageOf, RedWebSocket, SocketMessageFrame } from 'redweb';
import { z } from 'zod';

const SCHEMAS = defineRedwebSchemas({
  join: z.object({ username: z.string().trim().min(1) }).strict(),
});

class Join extends BaseHandler {
  constructor() { super(SCHEMAS.join); }

  override handle(socket: RedWebSocket, message: MessageOf<typeof SCHEMAS.join>, frame?: SocketMessageFrame) {
    // The inbound payload has been validated before this method runs.
    socket.sendEvent('joined', { username: message.username }, { requestId: frame?.requestId });
  }
}

class ChatRoute extends SocketRoute {
  constructor() {
    super({ path: '/chat', handlers: [Join], protocol: { versions: ['1'] } });
  }
}
```

`defineRedwebSchemas` turns each key into a message `type` and associates it with a Standard Schema validator. A handler selects its schema and gets an inferred payload through `MessageOf`. Redweb validates client messages before calling the handler. For legacy JSON routes, put the fields next to `type`; versioned routes place them under `payload` and negotiate the version configured by `protocol`.

The `protocol` option is independent: it enables version negotiation and protocol envelopes. It does not define message schemas. The route derives accepted message names from its handler classes; unknown names get the normal `UNKNOWN_HANDLER` failure.

Server-originated event payloads are application output, not client input. Redweb does not validate them against a duplicate schema map. TypeScript types and unit/integration tests should verify the events handlers actually emit. The client is responsible for validating or interpreting server events if its application requires that.

`socket.sendEvent(type, payload, metadata?)` builds the versioned envelope after negotiation. `redweb/client` exposes `ProtocolClient` for framing; it does not know application-specific message schemas. A shared Zod schema or generated declaration can be used by both sides, but Redweb does not require duplicate directional declarations.

Schema validation is not authentication, authorization, or game-rule validation. Keep bearer session IDs private, apply authorization in handlers, and persist important data outside the starter's bounded in-memory sessions. See [room authorization](ROOM_AUTHORIZATION.md), the [shared room example](snippets/room-access.tsx), [socket pages](SOCKET_PAGES.md), and [multiplayer operations](MULTIPLAYER_OPERATIONS.md).
