Native API options & contracts
Function arguments, complete native option tables, defaults, return values, callbacks and failure behavior.
Select an operation first
(ns application.transports
(:require [cljs.core :refer [await]]
[fast-twitch.client.core :as client]
[fast-twitch.server.core :as server]
[fast-twitch.server.sse :as sse]))
These are ordinary functions with map arguments. The tables below describe the pinned implementation's options; they are not arbitrary JavaScript constructor passthroughs. Unknown keys and invalid combinations are rejected through Malli before the corresponding native operation. A function that starts asynchronous I/O can reject its Promise; validate and catch at the application boundary.
| Operation | Arguments | Result and ownership |
|---|---|---|
client/request! |
[request] or [request options] |
HTTP: Promise of response map. SSE: Promise completing after the stream ends |
client/connect! |
[endpoint options] |
TCP/WebSocket: Promise of connection. EventSource: immediate source handle; close it explicitly |
client/send! |
[handle data] or [handle data options] |
TCP: await local write completion. WebSocket/port: native send; no peer acknowledgement |
client/receive! |
[tcp-connection] |
Promise of the entire peer stream through EOF, using connection settings |
client/half-close! |
[tcp-connection] |
Ends writes and retains reads; await before the next lifecycle step |
client/close! |
[handle] or [handle options] |
Closes the selected connection/port/source; close semantics follow the native handle |
client/message-channel! |
[] or [options] |
Map with configured :port1 and :port2; close both |
client/read-body! |
[message] or [message options] |
Promise of collected (:body message); bytes by default |
client/read-stream! |
[stream] or [stream options] |
Promise of bounded collected stream data |
client/reduce-stream! |
[stream reducer initial] or [stream reducer initial options] |
Promise of final accumulator; awaits the reducer between reads |
server/listen! |
[handler options] |
HTTP: native runtime server; TCP: listener map, Promise on Node. Await readiness explicitly |
server/response |
[body] or [body options] |
Ring response map |
server/close! |
[handle] or [handle options] |
Native server/peer shutdown; await asynchronous completion |
sse/response |
[next-event] or [next-event options] |
Ring response containing a pull-based SSE stream |
HTTP request options
The request map supplies :url, :request-method (usually :get / :post), :headers and optional :body. A Ring-style map can instead supply :scheme, :server-name, :server-port, :uri and :query-string. Headers and bodies retain Fetch constraints: GET/HEAD do not accept a body, and an already-consumed body cannot be sent again.
| Option | Accepted value | Default / behavior |
|---|---|---|
:transport |
:http |
HTTP is the default for request! |
:codec |
:native, :bytes, :text, :json |
:native; preserves the native response-body stream |
:streaming |
Collection reader options below | Only allowed with a decoded codec; collection defaults to 1 MiB |
:request-init |
Validated native Fetch settings below | Native defaults unless supplied/inherited from a request carrier |
:request-middleware |
Sequence of functions, or nil |
Each receives and returns a request map, possibly through a Promise |
:response-middleware |
Sequence of functions, or nil |
Each receives and returns a response map, possibly through a Promise |
JSON encodes a supplied request body before request middleware. Response middleware runs before response-body decoding. Preserve the response map rather than discarding its status and headers. HTTP errors such as 404 remain response statuses: they are not transport rejections. Check :status yourself. Fetch/network errors, invalid JSON and reader failures reject.
(defn ^:async create-message! [url]
(let [response (await (client/request!
{:url url :request-method :post :headers {}
:body {"message" "hello"}}
{:codec :json
:streaming {:max-bytes 65536}
:request-middleware
[(fn [request]
(assoc-in request [:headers "x-client"] "example"))]}))]
(if (= 201 (:status response))
(:body response)
(throw (ex-info "Unexpected HTTP status" {:status (:status response)})))))
This is a function to call against your JSON endpoint, not a self-starting program. Run the complete HTTP exchange.
Native Fetch settings
Keys below belong inside :request-init, not at the options map's top level. They use native string values, not Clojure keywords.
| Key | Allowed values |
|---|---|
:signal |
Native AbortSignal or nil |
:credentials |
"omit", "same-origin", "include" |
:cache |
"default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" |
:mode |
"same-origin", "no-cors", "cors" |
:redirect |
"follow", "error", "manual" |
:referrer |
String |
:referrerPolicy |
"", "no-referrer", "no-referrer-when-downgrade", "origin", "origin-when-cross-origin", "same-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url" |
:integrity |
String |
:keepalive |
Boolean |
:duplex |
"half" |
The native constructor may reject otherwise valid option combinations for the selected runtime or URL. Supplying :signal directly to an HTTP options map is not an alias for :request-init {:signal ...}. For decoded body reading, :streaming {:signal ...} controls the reader separately from Fetch.
HTTP listener and response options
server/listen! receives a Ring handler, so do not wrap it in ft-handler first. The alternative existing API, routing/start-server!, receives an adapted Fetch handler.
| Listener option | Accepted value / default |
|---|---|
:transport |
:http (default) |
:hostname |
String; defaults to "127.0.0.1" |
:port |
Integer 0–65535; defaults to 6464. Zero requests an ephemeral port |
:on-listen |
Function receiving the native address object; use its hostname / port fields for readiness |
:reuse-port |
Boolean; delegated to the selected runtime's capabilities |
:codec |
:native (default), :bytes, :text, :json |
:streaming |
Collection reader settings for decoded request bodies; forbidden with :native |
:async? |
Boolean; selects the callback handler contract when true |
:protocol, :remote-addr |
Request-map strings; default "HTTP/1.1" and "unknown" |
A nonnative listener codec decodes present request bodies. :json also encodes HTTP response bodies, except bodyless status codes 204, 205 and 304 and WebSocket listener responses. :text / :bytes do not automatically serialize an application response map. The HTTP forwarding example demonstrates readiness without a fixed sleep.
server/response options are only :status (integer 100–599, default 200), :headers (map, default {}) and :codec (:native default or :json). The response helper's status schema accepts 100–599, but ordinary native Fetch Response construction restricts which statuses/bodies can be sent; a 1xx map is not an ordinary final Fetch response. Do not JSON-encode the same response twice: choose JSON at the listener boundary or for the individual response. server/close! accepts {:force true} for an HTTP server; a TCP handle does not accept that HTTP option.
TCP options
connect!'s endpoint requires string :hostname and integer :port from 1 to 65535. listen! passes each accepted connection to a one-argument handler. Listener defaults are loopback and port 0.
| Option | Connection | Listener |
|---|---|---|
:transport |
Required :tcp for client/connect! |
:tcp |
:runtime |
:auto default, :node, :bun, :deno |
Same |
:codec |
:native default, :bytes, :text |
Same; inherited by accepted peers |
:streaming |
Bounded collection reader settings; no BYOB or :chunk-bytes |
Same; inherited by accepted peers |
:tls? |
Boolean, false when omitted; selects native client TLS connection | Not accepted by this listener API |
:signal |
Connection-attempt AbortSignal | Not accepted by this listener API |
The handle carries readable/writable native streams and close/half-close operations. Use send! and receive! directly with it. :text sends strings as UTF-8 and returns text; :native / :bytes send binary inputs and receive! returns a Uint8Array. Receive collects through EOF rather than waiting for a discrete message. Half-close writes before awaiting a peer's EOF-based reply. Close accepted peers as well as the listener. Limits and signals for subsequent receiving belong in :streaming; the connection-attempt signal does not remain a general session deadline.
TCP has no built-in framing, automatic retry or acknowledgement. TLS exists as a native client option, but the pinned runtime qualification does not establish portable TLS behavior. Complete three-runtime TCP exchange.
WebSocket options
The endpoint requires :url with ws: or wss: and no fragment. connect! resolves on native open; connection-attempt failure/abort rejects and cleans up the attempted socket.
| Option | Accepted value / behavior |
|---|---|
:transport |
:websocket |
:codec |
Only :native; no JSON/text codec or streaming options |
:protocols |
Vector of unique protocol token strings, or nil; omitted means none |
:listener |
Callback map below, a supported listener implementation, or nil |
:on-event |
Function receiving event maps, or nil |
:signal |
Cancels the connection attempt; removed when opening settles |
| Listener callback | Arguments |
|---|---|
:on-open |
[connection] |
:on-message |
[connection message] |
:on-error |
[connection error] |
:on-close |
[connection code reason] |
:on-ping, :on-pong |
[connection payload] where the native adapter exposes these capabilities |
The browser-style native client attaches open/message/error/close events; defining a ping callback does not create browser ping support. Callback functions may be asynchronous. Listener errors are forwarded to the error callback when available. Event maps include :type and :connection; messages use :message, close maps include code/reason/clean status, and errors use :error.
send! accepts native message payloads; the connection must be open. close! supports :code (default 1000) and :reason (default empty string); native close-code and reason-length restrictions still apply. Other connection types do not accept these close options. A Ring WebSocket route uses :ring.websocket/listener; Bun and Deno implement native server upgrade, Node's adapter answers 501. Complete WebSocket echo.
SSE options
For Fetch consumption, the request map has an HTTP URL, method and headers. The response must be status 200 and Content-Type: text/event-stream; invalid responses reject and their bodies are disposed.
| Fetch SSE option | Accepted value / default |
|---|---|
:transport |
:sse |
:on-event |
Required function, receiving the decoded SSE map; its Promise is awaited |
:codec |
Only :native event-map codec |
:signal |
AbortSignal; applies to Fetch and consumption |
:streaming |
:max-line-bytes default 65536 and :max-event-bytes default 1048576; each positive integer |
Events have string :data, :event (default "message"), :id and optional numeric :retry. Multiline data is joined by newlines. The Fetch consumer parses these records but does not reconnect or implement a retry policy from :retry.
For native EventSource, use an endpoint {:url ...} and options {:transport :event-source :on-event ...}. Optional :event-types is a vector of named event strings (default []); :with-credentials? is a Boolean (default false). It has no arbitrary header, codec, streaming or signal option. The returned handle contains :state and :close!; use client/close!. Callbacks receive :open, :error or :message type maps. Message maps include data, event name, id and origin. Native reconnect semantics apply until explicitly closed.
The producer sse/response takes a zero-argument function returning an event map or Promise; nil ends the stream. Its options are :headers (map or nil) and :on-cancel (function or nil). The event requires string :data, with optional single-line :event, single-line/NUL-free :id, and nonnegative safe integer :retry. Headers default to SSE UTF-8 and no-cache. :on-cancel receives the cancellation/failure reason, not normal EOF. Complete SSE exchange · Producer lifecycle.
MessageChannel options
message-channel! creates a native channel and configured handles for both ports. Options: :codec (:native default or :json), optional :on-event for port2, and :owned? (default true). Ownership applies to both ports: closing removes attached listeners; an owned handle also closes the native port.
Native structured clone expects native JavaScript data, not a ClojureScript persistent map. JSON accepts string-keyed maps, vectors and scalar JSON values. A message event map contains :type :message, :data, :ports, and native port/event carriers. JSON decoding failure emits an error map with :code :fast-twitch.codec/invalid-json and :error. Native message errors emit :type :messageerror.
send! accepts {:transfer [buffer-or-port ...]} for a port, with unique ArrayBuffers or MessagePorts. Transfer is not an option for TCP or WebSocket sends. Sending through a closed configured port fails. Close both sides of channels you own. Complete JSON exchange.
JSON value contract
JSON codec inputs use string-keyed maps, vectors, strings, Booleans, nil and finite numbers. Integer numbers must be safe JavaScript integers. Keywords, sets, native objects and keyword-keyed maps do not silently stringify into accepted application JSON. Decoding retains string keys. Depth is limited to 64; decode input text is bounded to 1048576 characters in addition to any configured byte collection limit.
Failures and cancellation
(defn ^:async guarded-request! [request]
(try
(await (client/request! request {:codec :text
:streaming {:max-bytes 65536}}))
(catch :default error
(let [details (ex-data error)]
;; Application policy decides whether to log, respond or retry.
(throw (ex-info "Outbound request failed"
(select-keys details [:code :operation :path :errors])
error))))))
Explicit capability/option errors expose code, operation, path and expected value. Malli instrumentation errors expose the operation, schema and error locations; they are not guaranteed to have the same shape as capability errors. Native network/parse errors and AbortSignal reasons are distinct. An abort may reject with the original reason, which need not be a JavaScript Error; do not assume .message always exists.
Common codes include :fast-twitch.contract/unsupported-transport, :fast-twitch.contract/unsupported-capability, :fast-twitch.stream/limit-exceeded, :fast-twitch.sse/invalid-response, :fast-twitch.sse/unsupported-event-source, :fast-twitch.websocket/not-open, :fast-twitch.messaging/closed and HTTP carrier/body errors. There is no automatic application-level HTTP error mapping or retry loop.
Stream reader settings · All public APIs · Pinned contracts source.