Requests
Declare, implement, and call a request — super-line's client→server call that awaits one typed reply. A request declaration carries both an input and an output schema; the server validates the input, runs your handler, and the client gets back the typed output. For the model behind requests and the other interaction flavors, see The contract.
Declare it on the contract
Put the request under a role's clientToServer (or shared.clientToServer — see below), with an input and output schema:
roles: {
user: {
clientToServer: {
send: { input: z.object({ room: z.string(), text: z.string() }), output: z.object({ id: z.string() }) },
},
},
}Implement the handler
Handlers live in implement, keyed by role (and shared). The handler receives the validated input, the connection's ctx, and the conn:
srv.implement({
user: {
send: async ({ room, text }, ctx, conn) => {
// input is already validated against the schema
return { id: crypto.randomUUID() } // typed to the output schema
},
},
})The server always validates inbound input before your handler runs — bad input rejects with a VALIDATION error and the handler never sees it.
Call it from the client
The client is a typed proxy; call requests as methods:
const out = await client.send({ room: 'lobby', text: 'hi' })
// ^? { id: string }Set a timeout or cancel
Each call accepts per-call options:
await client.send(input, { timeoutMs: 5000 }) // override the default 30s
await client.send(input, { signal: controller.signal }) // cancel via AbortControllerA timed-out call rejects with TIMEOUT; an aborted call rejects with BAD_REQUEST. Set timeoutMs: 0 to disable the timeout for a call.
Reject with a typed error
Throw a typed SuperLineError from a handler and the client's promise rejects with the same code:
import { SuperLineError } from '@super-line/core'
send: async ({ room }, ctx) => {
if (!ctx.canPost(room)) throw new SuperLineError('FORBIDDEN', 'not a member')
// ...
}Unknown throws become INTERNAL (your internals aren't leaked to the client).
Scope it: shared vs role
Put a request in shared.clientToServer to make it callable by every role; put it in a role block to scope it. A request a connection's role can't see is rejected with NOT_FOUND. See Roles & auth.
Next: Events & rooms.