Guides

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.

Source revision a4eb73f96836d034c7a24fe09dc7d51e4c581f64

Built with Fast-Twitch · Integrant · Datastar

Search documentation

Search pages and API symbols

Start hereWelcome to Fast-TwitchRing HTTP applications and configured native clients and transports in ClojureScript.Start hereGetting startedCompile and run your first Fast-Twitch server.Core conceptsRequest & responseUnderstand the maps at the center of a Fast-Twitch application.Core conceptsRoutingMatch URL patterns and compose handlers.Core conceptsMiddlewareWrap handlers with focused request and response behavior.GuidesHTML & IntegrantCompose a UI system with Integrant and render Hiccup.