Response API
primate/response exports server response helpers and the types used by custom
response functions and frontend integrations.
import response from "primate/response";
import type {
Deferred,
ResponseFunction,
ServeApp,
ViewResponse,
} from "primate/response";Helpers return ResponseFunction values that Primate
executes with the current serving app and request. See the
Responses guide for implicit response conversion and
walkthroughs. The signatures below describe the JS/TS module. Equivalent Go,
Python, Ruby, and Kotlin route examples use their backend SDKs and are linked
under each supported helper.
response.text()
text(body: string, init?: ResponseInit): ResponseFunction<never>;Returns a plain-text response function. The default status is 200, and
Content-Type is set to text/plain.
import http from "@rcompat/http";
import response from "primate/response";
import route from "primate/route";
export default route({
post(request) {
return response.text("Hello TypeScript!", { status: http.Status.CREATED });
},
});import http from "@rcompat/http";
import response from "primate/response";
import route from "primate/route";
export default route({
post(request) {
return response.text("Hello JavaScript!", { status: http.Status.CREATED });
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Post(func(request route.Request) any {
return response.Text("Hello from Go!", map[string]any{"status": 201})
})package routes.text
import run.primate.Response
import run.primate.Route
fun main() {
Route.post { Response.Text("Hello from Kotlin!", mapOf("status" to 201)) }
}from primate import Response, Route
@Route.post
def post(request):
return Response.text("Hello from Python!", {"status": 201})require 'primate/route'
require 'primate/response'
Route.post do |request|
Response.text("Hello from Ruby!", status: 201)
end
response.json()
json<T>(body: T, init?: ResponseInit): ResponseFunction<never, T>;Serializes body with JSON.stringify when the response function executes.
The default status is 200, and Content-Type is set to
application/json. Serialization errors propagate through request error
handling. The second type parameter retains T for route-client inference.
import response from "primate/response";
import http from "@rcompat/http";
import route from "primate/route";
export default route({
get() {
return response.json({ ok: true }, { status: http.Status.CREATED });
},
});import response from "primate/response";
import http from "@rcompat/http";
import route from "primate/route";
export default route({
get() {
return response.json({ ok: true }, { status: http.Status.CREATED });
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.JSON(map[string]any{"ok": true}, map[string]any{"status": 201})
})package routes.json
import run.primate.Response
import run.primate.Route
fun main() {
Route.get { Response.Json(mapOf("ok" to true), mapOf("status" to 201)) }
}from primate import Response, Route
@Route.get
def get(request):
return Response.json({"ok": True}, {"status": 201})require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.json({ ok: true }, { status: 201 })
end
response.binary()
binary(
source: Streamable,
init?: ResponseInit,
): ResponseFunction<never>;Streams a Blob, readable stream, or @rcompat/fs streamable source. It defaults
to application/octet-stream and an attachment filename of default.bin.
Named sources use their sanitized, length-limited name in Content-Disposition.
Blobs also set Content-Length and use their non-empty MIME type.
The Wasm backend SDKs accept bytes rather than the JS/TS streamable sources.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.binary(new Blob(["data"]), {
// set filename manually
headers: { "Content-Disposition": "attachment; filename=data.bin" },
});
},
});import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.binary(new Blob(["data"]), {
// set filename manually
headers: { "Content-Disposition": "attachment; filename=data.bin" },
});
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.Binary([]byte{0, 127, 128, 255})
})package routes.binary
import run.primate.Response
import run.primate.Route
fun main() {
Route.get { Response.Binary(byteArrayOf(0, 127, -128, -1)) }
}from primate import Response, Route
@Route.get
def get(request):
return Response.binary(bytes([0, 127, 128, 255]))require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.binary([0, 127, 128, 255])
end
response.redirect()
redirect(
target: RedirectTarget,
statusOrInit?: Redirection | RedirectInit,
): ResponseFunction<never>;Redirects to a string or structured same-origin target. The status defaults to
302; the second argument may be a redirect status number or options object.
The response has an empty body, Content-Length: 0, and
Cache-Control: no-cache.
During client navigation, Primate returns 204 and carries the destination and
original redirect status in Primate headers. Ordinary requests receive the
configured status and Location header.
import http from "@rcompat/http";
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.redirect("/login", http.Status.SEE_OTHER);
},
post() {
return response.redirect("/login");
},
});import http from "@rcompat/http";
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.redirect("/login", http.Status.SEE_OTHER);
},
post() {
return response.redirect("/login");
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(_ route.Request) any {
return response.Redirect("/login", 303)
})
var _ = route.Post(func(_ route.Request) any {
return response.Redirect("/login")
})package routes.redirect
import run.primate.Response
import run.primate.Route
fun main() {
Route.get { Response.Redirect("/login", 303) }
}from primate import Response, Route
@Route.get
def get(request):
return Response.redirect("/login", 303)
@Route.post
def post(request):
return Response.redirect("/login")require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.redirect('/login', 303)
end
Route.post do |request|
Response.redirect('/login')
endThrows: For invalid origin-form paths, control characters, backslashes, non-HTTP(S) protocols, unapproved external origins, invalid byte limits, or a location larger than the configured limit.
RedirectTarget
type RedirectTarget = string | {
pathname: string;
query?: Record<
string,
boolean | null | number | string | undefined
>;
};Structured pathname must start with one /, cannot start with //, and
cannot contain a backslash. Path segments and query values are URL-encoded;
nullish query values are omitted.
RedirectInit
type RedirectInit = Omit<ResponseInit, "headers" | "status"> & {
headers?: HeadersInit;
status?: 300 | 301 | 302 | 303 | 304 | 307 | 308;
allowExternal?: boolean;
base?: string | URL;
maxLocationBytes?: number;
};allowExternal defaults to false, base defaults to the request URL, and
maxLocationBytes defaults to 2048. Generated redirect headers replace any
caller-provided values with the same names.
response.view()
function view(
component: abstract new (...args: any[]) => any,
props?: Dict,
options?: ViewOptions,
): ResponseFunction<Dict>;
function view<Props>(
component: unknown,
props: Props,
options?: ViewOptions,
): ResponseFunction<Props>;
function view(
component: unknown,
props?: Dict,
options?: ViewOptions,
): ResponseFunction<Dict>;
function view<Props extends Dict>(
name: string,
props?: Props,
options?: ViewOptions,
): ResponseFunction<Props>;Renders a component reference or registered view name. Props default to an empty object. Primate selects the frontend integration from the component or filename extension and throws a missing-frontend diagnostic when none matches.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.view("Counter.jsx");
},
});import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.view("Counter.jsx");
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.View("Counter.jsx", nil)
})package routes.view
import run.primate.Response
import run.primate.Route
fun main() {
Route.get { Response.View("Counter.jsx") }
}from primate import Response, Route
@Route.get
def get(request):
return Response.view("Counter.jsx")require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.view('Counter.jsx')
end
response.page()
page<Props extends Dict>(
props?: Props,
options?: ViewOptions,
): ResponseFunction<Props>;Renders the component collocated with the current route or layout. Props default to an empty object. Calling the returned function outside a matched route/layout context throws a page-context diagnostic. Go, Python, Ruby, and Kotlin return their SDK's page descriptor with props and optional view options; Core still selects the current route's frontend page. Their props must be JSON-serializable.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.page({ message: "Hello" });
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.Page(map[string]any{"name": "Ada"})
})package routes.page
import run.primate.Response
import run.primate.Route
fun main() {
Route.get { Response.Page(mapOf("name" to "Ada")) }
}from primate import Response, Route
@Route.get
def get(request):
return Response.page({"name": "Ada"})require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.page({ name: 'Ada' })
end
ViewOptions
Supporting options accepted by response.view() and response.page(); this
type is not a named export from primate/response.
interface ViewOptions extends ResponseInit {
csp?: {
script_src?: string[];
style_src?: string[];
};
head?: string;
template?: string;
partial?: boolean;
placeholders?: Omit<Dict, "body" | "head">;
}template selects an HTML template, head adds markup to its head placeholder,
and partial: true omits the enclosing template. placeholders cannot replace
the reserved body and head entries.
response.error()
error(options?: {
body?: string;
template?: string;
status?: ValidStatus;
}): ResponseFunction<never>;Renders an error HTML page. Defaults are body "Not Found", template
"error.html", and status 404. Development responses also inject the live
reload listener. This is an ordinary response and does not throw.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.error({ body: "Not Found" });
},
});import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.error({ body: "Not Found" });
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.Error(map[string]any{
"body": "Not Found",
})
})package routes.error
import run.primate.Response
import run.primate.Route
fun main() {
Route.get { Response.Error("Not Found", 404) }
}from primate import Response, Route
@Route.get
def get(request):
return Response.error({"body": "Not Found"})require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.error(body: 'Not Found')
end
response.abort()
abort(response: Response): never;
abort(options: AbortOptions): never;
type AbortOptions = Omit<ResponseInit, "status"> & {
body?: BodyInit | null;
status: ValidStatus;
};Throws an internal control-flow value carrying a native response. body
defaults to null; status is required. Primate handles it without invoking
the error route, error logger, or module error observers. A HEAD request keeps
status and headers but strips the body. Call response.abort(...) directly;
do not prefix it with throw.
The four Wasm SDKs return an explicit abort descriptor rather than invoking
JS/TS control-flow abort from a nested helper.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
response.abort({ body: "Not Found", status: 404 });
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.Abort(map[string]any{"body": "Not Found", "status": 404})
})package routes.abort
import run.primate.Response
import run.primate.Route
fun main() {
Route.get { Response.Abort(404, "Not Found") }
}from primate import Response, Route
@Route.get
def get(request):
return Response.abort({"body": "Not Found", "status": 404})require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.abort(body: 'Not Found', status: 404)
end
response.null()
null(): ResponseFunction<never>;Returns a response function producing an empty 204 response.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.null();
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.Null()
})package routes.null_response
import run.primate.Response
import run.primate.Route
fun main() {
Route.get { Response.Null }
}from primate import Response, Route
@Route.get
def get(request):
return Response.null()require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.null
end
response.defer()
defer<T>(value: T | PromiseLike<T>): Deferred<Awaited<T>>;Wraps the input in a branded promise used for direct page and layout props. Initial document rendering awaits it. Client navigation sends immediate props first and resolves or rejects the deferred prop through the streamed navigation response. The wrapper observes rejection early without changing the promise consumed by rendering. Use this helper with JS/TS page and layout props.
Deferred
Named type export representing the server-rendered or client-navigation form of a deferred value.
type Deferred<T> = T | Promise<T>;
response.sse()
sse(
body: (source: SSESource) => void | (() => void),
init?: ResponseInit,
): ResponseFunction<never>;Creates a text/event-stream response. The setup callback runs when the stream
starts and may return cleanup, which runs once on server close or client
disconnect.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.sse(source => {
// push event to client
source.send("open", "hi!");
});
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.SSE(func(source response.SSESource) func() {
source.SendEvent("open", "hi!")
return nil // Return a cleanup function when subscribing to external events.
})
})package routes.sse
import run.primate.Response
import run.primate.Route
fun main() {
Route.get {
Response.SSE({ source ->
source.send("open", "hi!")
null // Return a cleanup callback when subscribing to external events.
})
}
}from primate import Response, Route
@Route.get
def get(request):
def start(source):
source.send("open", "hi!")
return Response.sse(start)require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.sse do |source|
source.call('send', 'open', 'hi!')
end
end
SSESource
type SSESource = {
close(): void;
send(data: string): void;
send(name: string, data: unknown): void;
};send(data) emits the default event; send(name, data) emits a named event.
Data is JSON-serialized. Event names must be non-empty and contain no control
characters. Sends after close are ignored; close() is idempotent.
Go uses source.SendEvent(name, data) for named events. The foreign SDKs accept
JSON-serializable values for default events as well as named events.
response.ws()
ws(actions: Actions): ResponseFunction<never>;Upgrades the original request with the @rcompat/http WebSocket actions
object. Runtime support and action examples are in the
Responses guide.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.ws({
open(socket) {
socket.send("hello");
},
message(socket, message) {
// echo
socket.send(String(message));
},
close(socket) {
console.log("socket closed");
},
});
},
});package main
import (
"primate.run/go/response"
"primate.run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.WS(response.WebSocketActions{
Open: func(socket response.WebSocket) { socket.Send("hello") },
Message: func(socket response.WebSocket, message any) {
socket.Send(message)
},
})
})package routes.ws
import run.primate.Response
import run.primate.Route
fun main() {
Route.get {
Response.WS(
Response.WS.Actions(
open = { socket -> socket.send("hello") },
message = { socket, message ->
when (message) {
is Response.WS.Message.Text -> socket.send(message.value)
is Response.WS.Message.Binary -> socket.send(message.value)
}
},
),
)
}
}from primate import Response, Route
@Route.get
def get(request):
def on_open(socket):
socket.send("hello")
def on_message(socket, message):
socket.send(message)
return Response.ws({"open": on_open, "message": on_message})require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.ws(
open: proc { |socket| socket.call('send', 'hello') },
message: proc { |socket, message| socket.call('send', message) },
)
endKotlin callbacks use Response.WS.Actions with Response.WS.Message.Text and
.Binary; Go uses response.WebSocketActions. Python and Ruby pass callback
maps. The Ruby Wasm VM currently does not initialize under Bun 1.4.4; use Node
or Deno for Ruby routes.
ResponseFunction
Named type export implemented by every helper result.
type ResponseFunction<Props = never, Result = never> = {
(
app: ServeApp,
transfer: Dict,
request: RequestFacade,
): MaybePromise<View | null | Response | undefined>;
readonly _props?: Props;
readonly _result?: Result;
};_props and _result are optional inference properties and do not contain
runtime values. Custom response functions may use the serving context and
request to return a native response, rendered view, null, or undefined.
ServeApp
Named type export for the first ResponseFunction
argument. It is the full serving application class; response authors most
commonly use this response-related subset:
interface ServeApp {
respond(body: BodyInit | null, init?: ResponseInit): Response;
media(contentType: string, init?: ResponseInit): ResponseInit;
view(options: FullViewOptions): Response;
page(pathname: string): string;
upgrade(request: Request, actions: Actions): Response | null;
}The interface above is a focused view, not a separate exported declaration.
respond() applies response defaults and content length, while media() sets
the requested content type and defaults the status to 200.
ViewResponse
Named type export used by frontend modules to register view response factories.
type ViewResponse = (
name: string,
props?: Dict,
options?: ViewOptions,
) => ResponseFunction;
Browser availability
Response construction is server-side. Browser builds replace helpers with
throwing stubs; response.page is not present in the browser runtime object.
Do not call response helpers from frontend code.