Middleware & lifecycle
Middleware
Middleware runs before request and subscribe handlers — for rate-limiting, per-operation authz, logging, and metrics. It's a flat chain: call next() to proceed, or throw to short-circuit (rejecting the operation).
ts
const srv = createSocketServer(api, {
server, authenticate,
use: [
async (ctx, info, next) => {
rateLimit(info.conn.role, info.name) // throw to reject
await next()
},
async (_ctx, info, next) => {
const t = Date.now()
await next()
metric(info.name, Date.now() - t) // wrap the handler for timing
},
],
})The info argument is { kind: 'request' | 'subscribe', name, conn } — so the same chain can gate both requests and topic subscribes:
ts
async (_ctx, info, next) => {
if (info.kind === 'subscribe' && info.name === 'feed') throw new SocketError('FORBIDDEN')
await next()
}Middleware does not change ctx's type — it's a cross-cutting gate, not a context transformer. ctx is the union of role ctxs; branch on info.conn.role if you need to narrow.
Lifecycle hooks
ts
createSocketServer(api, {
server, authenticate,
onConnection: (conn, ctx) => log('joined', conn.role),
onDisconnect: (conn, ctx, code) => cleanup(conn), // code = WebSocket close code
onError: (err, info) => report(err, info), // any throw in middleware/handlers
})onConnection— once per accepted connection. A handy place to add a connection to a per-user room.onDisconnect— when the socket closes; receives the closecode.onError— every error thrown in middleware/handlers, after the client has been replied to. Great as a test seam and for centralized reporting.
These hooks are also the recommended seams for testing — capture the server-side conn, assert connects/disconnects, observe thrown errors.
Next: Error handling.