defineRequest, request options, and HTTP request/response types.
HTTP
Declare a typed request, then call the operation with a client and its input.
defineRequest()
function defineRequest<
const M extends string,
const P extends string,
TInput extends AnyStruct | undefined = undefined,
TOutput extends AnyStruct | undefined = undefined,
TError extends AnyStruct | undefined = undefined,
>(
definition: ResponseDeclaration<TOutput, TError> & {
input?: TInput;
method: M;
operation?: string;
path: P;
} & BuilderField<NoInfer<TInput>, HttpRequestShape<NoInfer<P>, NoInfer<M>>>,
): RequestEndpoint<
EndpointArgs<TInput>,
RequestSuccessData<TOutput>,
RequestErrorData<TError>,
DeclaredResponse<TOutput>
>;- definition —
method,path, optionalinputstruct, optionaloutputanderrorstructs, optionaloperation. A declaredinputalways requires an explicitbuilder.inputonly defines the parameters; the builder alone decides where each value goes. Only definitions withoutinputmay omitbuilder. Thesedefinitionmembers are documented underRequestDefinition.builderis required exactly wheninputis declared. - Returns an operation. Call it as
getUser(client, input), orhealth(client)when no input is declared. When the whole input struct is optional, the input argument may be omitted. Calling it starts the request. There is no third options argument.
import { defineRequest, struct } from "@defjs/core";
const getUser = defineRequest({
method: "GET",
path: "/users/:id",
input: struct.object({ id: struct.number() }),
builder: (input) => ({ path: input }),
output: struct.object({ id: struct.number(), name: struct.string() }),
error: struct.object({ message: struct.string() }),
});output is one struct for the 2xx body; error is one struct for every non-2xx body. Neither is
keyed by status. Omitting one means that body is never read — see
Declaration is an assertion.
Calling the operation returns a promise of the tuple described under HttpAwaitResult. Pass per-call options by deriving a client from RequestOptions. HTTP reads signal and the progress callbacks and ignores the other fields.
A non-2xx status is HTTP_STATUS when no error struct is declared or the declared error body decodes. The fault's data is the decoded
error body when one was declared, and undefined otherwise. When a declared error body cannot be read (wrong media type, invalid JSON) or
does not match its struct, the fault is RES_MEDIA_TYPE_INVALID, RES_DECODE_FAILED or RES_STRUCT_MISMATCH instead, and the response status is still
reported in its metadata. On failure the tuple's third slot is undefined; the fault carries the response metadata.
fetchHandler()
function fetchHandler(
httpRequest: HttpRequest,
fetchImpl?: FetchHandle,
): Promise<HttpResponse<unknown>>;Default HTTP transport. fetchImpl defaults to the global fetch resolved at send time; withHTTPHandle supplies another. Untouched platform Fetch receives (url, init), while a replaced global or custom handle receives a Request. It rejects — rather than resolving
with a synthetic response — when no response reached the client or reading a declared body failed.
makeResponse()
function makeResponse<R>(
options?: MakeResponseOptions<R>,
): HttpResponse<R>;Build an HttpResponse without a network call (interceptors, tests). Default status is 0. ok is
true for 2xx. The value it returns goes through the same media-type check and the same declared
struct as one off the wire; there is no trusted short-circuit.
HttpExecuteOptions
type HttpExecuteOptions = UseRequestConfig;HttpExecuteOptions is the HTTP slice of RequestOptions: UseRequestConfig, which is signal plus onDownloadProgress and onUploadProgress. There is no timeout or abort field. A deadline is signal: AbortSignal.timeout(ms) and classifies as NET_TIMEOUT. A manual abort is NET_ABORTED. signal is the call's signal; combine signals with AbortSignal.any in application code. There is no client-level default deadline. Other request fields, such as heartbeat, are ignored.
Types
RequestDefinition
method, path, optional input, output, error, operation, and builder. There is no responseType: the struct kind selects how a declared body is read. A declared input always requires an explicit builder. input only defines the parameters; the builder alone decides where each value goes. Only definitions without input may omit builder.
HttpAwaitResult
type HttpAwaitResult<TData = undefined, TErrorData = undefined> =
| [
error: null,
result: TData,
response: [TData] extends [undefined] ? HttpMeta : DecodedResponse<TData>,
]
| [error: FaultOf<TErrorData>, result: undefined, response: undefined];On success the third slot carries body only when output was declared. On failure it is
undefined — the fault already holds whatever response metadata existed.
HttpRequest
Normalized outgoing request: method, endpoint, headers, body, signal, operation, withCredentials, xsrf, downloadProgress / uploadProgress, baseEndpoint, queryString, and queryParams. By the time interceptors see it, body is already the wire value — for JSON, a JSON text string — so sign HMACs and digests against that, not the struct input. Interceptors see queryParams as new URLSearchParams(queryString). A builder that returns URLSearchParams passes that instance through. responseType and errorResponseType are readonly: they are derived from the endpoint's output and error struct kinds and must not be changed.
HttpMeta
type HttpMeta = {
readonly headers: Headers;
readonly ok: boolean;
readonly status: number;
readonly statusText: string;
readonly url: string;
};Response metadata, available whenever a response reached the client. It carries no body: a body
only exists once a declared struct decoded one.
DecodedResponse
type DecodedResponse<TBody> = HttpMeta & { readonly body: TBody };A response whose body decoded against the declared struct. Holding this type is the proof that
decoding happened, which is why a decode failure reports HttpMeta alone.
HttpResponse
type HttpResponse<R> = {
readonly [HTTP_RESPONSE]: true;
readonly url: string;
readonly status: number;
readonly statusText: string;
readonly headers: Headers;
readonly body: R | null | undefined;
readonly ok: boolean;
};body is undefined when the response had none; a JSON null stays null. The type is branded, so build one with makeResponse rather than an object literal. The wire shape transports produce and interceptors see: body is text or already-parsed JSON, not a
decoded value. For a JSON representation, a string is a JSON string root and is not parsed again; bytes
and blobs are raw and are parsed once. DecodedResponse is what reaches the caller.
HttpProgressEvent
HttpProgressFn
loaded, total, lengthComputable. Callbacks may be async. A throwing onDownloadProgress or onUploadProgress fails the call with
EXT_OBSERVER_FAILED, with the thrown value as cause; cancellation still reports NET_ABORTED, or NET_TIMEOUT when the abort reason is a timeout.
Upload progress is reported only where the runtime can stream request bodies (Bun, Node); in browsers, Blob / File,
ArrayBuffer, and FormData bodies are sent unchanged and onUploadProgress is not called.
In server runtimes, FormData uses its serialized stream without whole-body Blob buffering and preserves the actual multipart boundary. Its total is unknown unless a valid explicit Content-Length supplies it. Explicit content types survive upload conversion, except that FormData must use its serialization's boundary. loaded counts stream bytes, not server acknowledgements. Downloads with a non-identity Content-Encoding report total: 0 and lengthComputable: false; their stream bytes may already be decompressed.
See HTTP guide and Client.
RequestEndpoint
Returned by defineRequest. The first argument is a Client. Further arguments are the input only.
BuilderInput
Parsed endpoint input seen by builder. Object nodes carry the struct that produced them, so alias, Date, and bigint encoding follow the value.
HttpRequestShape
type HttpRequestShape<P extends string, M extends string> = {
body?: Uppercase<M> extends "GET" | "HEAD" ? never : BodyValue;
headers?: HeadersInit | FlatRecord;
path?: PathValue<P>;
query?: URLSearchParams | QueryRecord;
};All four fields are optional. query is URLSearchParams or a record; a nested object needs withQuerySerializer at runtime. headers is HeadersInit or a record of scalars or arrays of scalars. path supplies one scalar per :name segment.
body is never for GET and HEAD. Otherwise it is a wire value (string, Blob, ArrayBuffer, a typed array or DataView, FormData, URLSearchParams, or ReadableStream<Uint8Array>) or JSON data (number, boolean, bigint, Date, null, an array, or an object with string keys).
UseRequestConfig
signal, plus optional onDownloadProgress and onUploadProgress. HttpExecuteOptions is this slice. There is no timeout or abort field.
RequestSuccessData
Inferred success body from the declared output struct, or undefined when none is declared.
RequestErrorData
Inferred error body from the declared error struct, or undefined when none is declared.
HttpResponseType
'arraybuffer' | 'blob' | 'json' | 'text'
Chosen from the declared struct kind (string → text, blob / file → blob, arrayBuffer → arraybuffer, everything else → json). Not a field on defineRequest.
MakeResponseOptions
Fields for makeResponse: status, statusText, url, headers, body, request.