Skip to content

Why super-line

super-line is a typesafe realtime data bus for TypeScript. You write one contract; the server implements it and the client calls it with full end-to-end type inference — and zero codegen — over the wire of your choice: WebSocket, HTTP (SSE / long-poll), or libp2p/WebRTC, swapped in one line.

The assembly tax

Realtime apps usually glue together hand-maintained event-name constants, untyped payloads, and ad-hoc validation. Every message pattern — a request, a push, a subscription — gets its own bespoke plumbing, and none of it is checked end to end. The types on the client and the types on the server are two hand-copied truths that drift the moment someone renames a field.

super-line replaces all of that with a single defineContract({...}) object that both sides import. From that one declaration you get:

  • Types on both ends — the server's handlers and the client's calls are inferred from the same source, so they can't drift.
  • Runtime validation — the same schemas that type your payloads also validate them. The server rejects malformed input automatically.
  • Interaction flavors over one connection — requests, events, topics, and rooms. A shared topic also doubles as a cluster-wide event bus (server.publish / server.subscribe) so nodes converge without a separate messaging API. See The cluster event bus.
  • Persisted state — the contract can also declare collections: typed rows you filter and subscribe to in subsets, and CRDT documents whose concurrent edits merge. Every write is schema-validated; a client reads and writes and the server — and every other node — sees it converge. See Collections.
  • Work that outlives the connection — declare a queue and its worker, and slow work becomes a durable, at-least-once job instead of a request you hope stays open: typed input/result, retries, leases, declarative concurrency, and cron — on the same collections, with no Redis and no second deployment. See Queues and workers.
  • Whole domains as plugins — a plugin merges its own collections, roles, and requests straight into your contract: first-party authentication (sessions, API keys, JWT), durable queues (jobs and cron), a chat backbone with streaming AI messages and shared channel resources, and a cluster inspector each drop in as one line, typed end to end like code you wrote. See Plugins.
  • Any wire — the same contract and the same code run over WebSocket, HTTP, or libp2p/WebRTC. The transport is one line; everything above it is identical. See Transports and adapters.

One contract collapses the assembly tax into a single typed surface. Nothing on the wire is untyped; nothing is validated twice by hand.

Two axes: direction and role

The contract is organized along two axes:

  • DirectionclientToServer (requests) and serverToClient (events & topics). Each is a named key on the contract, so there are no positional generics to get backwards.
  • Role — a shared base plus one block per client role (user, agent, …). A connection's role is fixed at connect (by authenticate) and decides which surface — and which ctx — it gets. A cross-role call is rejected with NOT_FOUND.

See The contract for the full model.

How it compares

super-lineSocket.IOtRPC
Typesafe contract⚠️ types-only
Runtime validation
Per-role contracts
Rooms & topics⚠️ rooms onlysubscriptions
Inter-server messaging
Durable background jobs & cron
Domain plugins on the contract (auth · queue · chat · inspector)⚠️ routers only
Pluggable wire (WS · HTTP · WebRTC)⚠️ WS + polling⚠️ link-dependent

Socket.IO splits its types into ClientToServerEvents / ServerToClientEvents / InterServerEvents interfaces you wire as positional generics (easy to swap) with no runtime validation. super-line keeps the directional split but in one shared object, validates inbound automatically, and adds per-role contracts. See the full comparison & FAQ.

Ready to write one? Start with Run a super-line server.

Released under the MIT License.