API
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.
| Option | Type | Description |
|---|---|---|
baseURL | string | Prepended to every path: 'http://localhost:3000', '/api'. Defaults to ''. |
fetch | (request: Request) => Response | Promise<Response> | Replaces the global fetch. |
headers | HeadersInit | () => HeadersInit | Promise<HeadersInit> | Sent with every request. A function is called per request. |
onRequest | ({ request }) => void | Request | Runs before each request. Returning a Request replaces it. May be async. |
onResponse | ({ request, response }) => void | Response | Runs 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 field | Description |
|---|---|
params | Values for the :segments of the path. |
query | Query string fields. Arrays repeat the key, null and undefined are left out. |
headers | Headers for this call, added on top of the client's. |
body | The JSON body, or a Blob, ReadableStream, ArrayBuffer, typed array, FormData or URLSearchParams sent as is. |
form | The fields of a @MultipartFormData() route, as FormData or a plain object. |
| anything else | Passed 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.
| Property | Type | Description |
|---|---|---|
status | number | The HTTP status. |
body | unknown | The parsed body: JSON when it is JSON, the text otherwise. |
response | Response | The response, its body already read. |
message | string | body.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:
| Key | Decorator | Example |
|---|---|---|
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
| Setting | Where | Description |
|---|---|---|
build.typegen | vercube.config.ts | false turns it off, true or { output } turns it on. Unset: on when package.json lists @vercube/client. |
typegen | vercube({ ... }) in vite.config.ts | The same, for the Vite plugin. |
output | either | Path 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.