API

Reference for createClient, the client methods, VercubeClientError, RouteDef and typegen

createClient()

createClient<Routes>(options?) returns a client typed by Routes, usually the generated VercubeRoutes. Without a type argument every path and option is accepted and responses are any.

OptionTypeDescription
baseURLstringPrepended to every path: 'http://localhost:3000', '/api'. Defaults to ''.
fetch(request: Request) => Response | Promise<Response>Replaces the global fetch.
headersHeadersInit | () => HeadersInit | Promise<HeadersInit>Sent with every request. A function is called per request.
onRequest({ request }) => void | RequestRuns before each request. Returning a Request replaces it. May be async.
onResponse({ request, response }) => void | ResponseRuns after each response, error responses included. Returning a Response replaces it. May be async.

Client methods

get, post, put, patch, delete, head and options take (path, init?). request takes (method, path, init?). The path is a route template from the route map, and init is required as soon as the route requires something.

init fieldDescription
paramsValues for the :segments of the path.
queryQuery string fields. Arrays repeat the key, null and undefined are left out.
headersHeaders for this call, added on top of the client's.
bodyThe JSON body, or a Blob, ReadableStream, ArrayBuffer, typed array, FormData or URLSearchParams sent as is.
formThe fields of a @MultipartFormData() route, as FormData or a plain object.
anything elsePassed to fetch: signal, credentials, cache, keepalive...

A call resolves to the response body (see Responses), or to an SseStream for a text/event-stream response.

VercubeClientError

Thrown for every status outside 200 to 299.

PropertyTypeDescription
statusnumberThe HTTP status.
bodyunknownThe parsed body: JSON when it is JSON, the text otherwise.
responseResponseThe response, its body already read.
messagestringbody.message when there is one, '<status> <statusText>' otherwise.

SseEvent and SseStream

SseStream<E> is an AsyncGenerator<E>; calling return() (or leaving a for await loop) closes the connection. Each SseEvent<D, E> has data: D, event: E, id: string | undefined and retry: number | undefined.

parseSse(body, signal?) is the parser on its own, for a ReadableStream you got some other way.

RouteDef

RouteDef<Controller, 'method', Args> turns a controller method into a route. Args holds the argument index of each parameter decorator, which is what the generator writes:

KeyDecoratorExample
param@Param(name), or null for a segment no argument reads{ param: { id: 0, slug: null } }
query@QueryParam(name){ query: { page: 1, sort: 2 } }
queries@QueryParams(){ queries: 1 }
header@Header(name){ header: { 'x-tenant': 2 } }
body@Body(){ body: 1 }
form@MultipartFormData(){ form: 1 }
sse@Sse(){ sse: true }

@Request(), @Response(), @Headers() and custom decorators are not part of the client's input and are not listed.

A route map can be written by hand, for instance for a service you do not build yourself, by giving each key a Route with params, query, headers, body, form and response.

Typegen

SettingWhereDescription
build.typegenvercube.config.tsfalse turns it off, true or { output } turns it on. Unset: on when package.json lists @vercube/client.
typegenvercube({ ... }) in vite.config.tsThe same, for the Vite plugin.
outputeitherPath relative to the project root. Defaults to .vercube/routes.d.ts.

vercube typegen builds the application and writes the file, whether or not the project lists @vercube/client; only typegen: false stops it. Controller files come from the Vite plugin's discovery manifest when there is one, and from scanning the project for @Controller() classes otherwise; build output, node_modules and test files are not scanned.

A controller's class name must be unique in the project, and the class exported (named, default, or through export { ... }). @Controller() may be imported under another name or used through a namespace (@core.Controller()). Routes on a controller that breaks one of these are skipped, with one warning per cause.

Controllers are imported with a .js extension ('../src/Controllers/UserController.js'), which resolves to the .ts source under both bundler and node16/nodenext module resolution.

The cost on the type checker grows linearly: about 420 type instantiations per route that is called, or roughly 0.12 s of check time for 500 routes with one call each.