Clients & native transports
Configure HTTP, TCP, WebSocket, SSE, stream readers and MessageChannel with ordinary maps.
Ordinary maps, native APIs
fast-twitch.client.core provides configured clients; fast-twitch.server.core provides Ring response helpers and native listeners. Existing routing/routes, routing/ft-handler, middleware and serve remain available. Choose a transport and codec in an options map; handlers and payloads remain ordinary functions and data.
HTTP and JSON
client/request! accepts an outbound request map with :url, :request-method, :headers and optional :body. server/listen! accepts a Ring handler directly:
(ns application.exchange
(:require [cljs.core :refer [await]]
[fast-twitch.client.core :as client]
[fast-twitch.server.core :as server]))
(def http-server
(server/listen!
(fn [request] (server/response (:body request)))
{:transport :http :hostname "127.0.0.1" :port 6464
:codec :json :streaming {:max-bytes 65536}}))
(defn ^:async exchange! []
(let [response
(await (client/request!
{:url "http://127.0.0.1:6464/"
:request-method :post :headers {}
:body {"message" "hello"}}
{:codec :json :streaming {:max-bytes 65536}}))]
(:body response)))
;; Call exchange! after the listener is ready. At shutdown, await:
;; (server/close! http-server {:force true})
The JSON codec encodes supplied request/response data and decodes bodies to string-keyed maps. :text and :bytes collect a body within the configured byte limit; the default limit is 1 MiB. :native preserves native bodies and does not accept :streaming. Response maps retain status and headers. HTTP request middleware and response middleware run in sequence and may return Promises.
Native Fetch settings go under :request-init, for example {:request-init {:signal (.-signal controller)}} for cancellation. Plain HTTP cancellation uses that native signal; Fetch SSE uses a top-level :signal in its transport options. No retry policy is added automatically.
Read streams explicitly
client/read-body! collects a request/response body's stream; client/read-stream! collects a native stream. Choose :codec :bytes, :text or :json and :max-bytes explicitly when buffering.
client/reduce-stream! consumes chunks without collecting the whole body. It awaits each reducer result before reading again. Use {:codec :text} for incremental strict UTF-8 decoding or :bytes for byte chunks. Readers own their stream by default, cancel on failure or early termination, and release their lock. With :owned? false, borrowed readers release their lock without cancellation. A reducer can return reduced to stop. An abort signal rejects with its original reason.
BYOB uses :mode :byob on a native byte stream. Its :chunk-bytes defaults to 65536 and must be between 1 and 1048576. TCP readers do not accept BYOB or :chunk-bytes. Reader API.
TCP connections
Use server/listen! with :transport :tcp and client/connect! with a {:hostname ... :port ...} endpoint. Select :codec :native, :bytes or :text; :streaming carries bounded reader options. :runtime :node, :bun or :deno selects a native adapter; omitting it selects the current runtime.
Both the client and the server's accepted connection work with client/send!, client/receive!, client/half-close! and client/close!. Await sends and half-close before collecting a peer's EOF reply. receive! reads through EOF, so a peer must finish its writes; Fast-Twitch adds no message framing. send! completion does not mean the peer received the data. Close listener and connection resources at the end of their lifetimes.
WebSocket connections
client/connect! with {:url "wss://example.test/socket"} and {:transport :websocket} resolves when the native socket opens. Configure :protocols, :listener, :on-event or a connection-attempt :signal as needed. Use client/send! and client/close! with the returned connection; optional close settings are :code and :reason. WebSocket retains native message payloads and rejects streaming settings or other codecs.
On Bun and Deno, a Ring handler can return {:ring.websocket/listener {:on-message handler}}; the callback receives [connection message]. This works with ordinary routes. Node has a native WebSocket client but this framework's native Node HTTP adapter returns 501 for server upgrades.
SSE and EventSource
Use client/request! with :transport :sse, :on-event, optional :signal and :streaming {:max-line-bytes ... :max-event-bytes ...} to consume a Fetch SSE stream. Defaults are 64 KiB per line and 1 MiB per event. Events use the native event-map codec, and this path does not reconnect automatically.
client/connect! with :transport :event-source uses the runtime's native EventSource. It accepts :event-types, :with-credentials? and :on-event, follows native reconnect behavior and has no arbitrary request-header option. Close its returned handle when finished. The server.sse/response helper supplies pull-based generic SSE; Datastar & SSE explains response-local producers and SDK patches.
MessageChannel
client/message-channel! returns :port1 and :port2. Its default :native codec retains native structured-clone payloads; :json uses ordinary JSON data. An optional :on-event receives port2 events, with the payload under :data. Send on a port using client/send!; {:transfer [buffer]} transfers supported native objects for port messages. Close both ports through client/close! when finished.
Runtime capabilities and validation
The upstream runtime profiles record Node 25.8.1, Bun 1.4.2 and Deno 2.9.5. Capabilities depend on native APIs rather than a universal emulation layer:
| Capability | Node | Bun | Deno |
|---|---|---|---|
| HTTP server, Fetch, TCP, Fetch SSE, MessageChannel | Supported | Supported | Supported |
| Native WebSocket client | Supported | Supported | Supported |
| Native WebSocket server upgrade | Unavailable (501) | Supported | Supported |
| Native EventSource | Unavailable | Unavailable | Supported |
Browser clients use the corresponding native browser APIs where available; server TCP and HTTP listeners require a server runtime. Celld and workerd expose the HTTP Fetch entry, which does not imply every Node transport API is available there. The upstream capability profiles leave TLS qualification incomplete.
Fast-Twitch validates native option combinations through Malli before I/O. Unknown options, incompatible codecs and unsupported capabilities reject rather than silently changing behavior. The serve macro also rejects unknown literal option names and invalid numeric literal ports during expansion. Dynamic values are validated at their native boundaries.
Run the upstream native transport examples.
Learn each transport with a complete program
The descriptions above give the overall map. Use complete function/option tables for defaults, callback signatures, return values and rejected combinations. The stream reader guide covers ownership, bounds, cancellation and BYOB.
| Transport | Complete walkthrough |
|---|---|
| HTTP | Native forwarding, response edits and text collection |
| TCP | Node/Bun/Deno byte exchange, EOF and half-close |
| WebSocket | Ring server upgrade and native client echo |
| SSE/EventSource | Response-local producer and both consumers |
| MessageChannel | JSON port messages and ownership |
| Readable streams | Owned abort and split UTF-8 reduction |
Each page includes the exact namespace and build files from the displayed framework revision, not only a link to an external repository or a command with no explanation. Public API index.