Documentation

Route kinds and compute classes

Every file in src/api/ has two independent concerns:

New HTTP endpoints and tasks default to function with 1gb of memory.

Canonical compute contract

export const route = {
  kind: 'task',
  compute: {
    class: 'function',
    memory: '4gb', // 1gb | 4gb | 6gb | 8gb | 12gb
  },
  timeout: 60_000,
};
Class Use it for Memory contract
function (default) Stateless HTTP endpoints, native modules, CPU work, media/doc processing, AI payloads, and ordinary tasks 1gb default; 4gb, 6gb, 8gb, or 12gb selectable
dedicated WebSockets, listening sockets, process-local state, background loops, and always-on work Machine profile; optional memory/CPU are preserved in the manifest

On managed Vura, server and hybrid pages currently use the persistent Dedicated project runtime because their request-time SSR handler needs the full server build context. They are not placed in an individual Function endpoint.

Dedicated manifests also carry the legacy machine.memoryMb / machine.cpus shape so current platform hot-task sizing honors canonical memory and CPU requests during the compatibility window.

Use vura routes inspect --json or vura runtime advise --json to see the effective class, memory, CPU, timeout, provider recommendation, confidence, and reasons before deployment.

Deployment-relevant route config is a static literal contract. Strings, finite numbers, numeric separators, booleans, null, arrays, nested objects, and as const are supported. Calls, referenced identifiers, spreads, computed properties, and template literals are rejected with a line/column error; scanning never imports or evaluates application code. Deployment-affecting page fields such as mode, revalidate, and tags must use static literals, and modes must be quoted strings. Explicit presentation references such as styles: [baseStyles] are omitted from the manifest and evaluated later by the renderer.

Annotation

// shorthand — kind only
export const kind = 'hot';

// route object — kind + config fields
export const route = {
  kind: 'task',
  compute: { class: 'function', memory: '1gb' },
  retries: 2,
  timeout: 60_000,
};

When both kind and route are exported from the same file, route wins. Use the shorthand when you have no additional config.


serverless (default)

Stateless, per-request. Runs inside the unified Node server (persistent hosts) or as an individual Lambda/Worker function (serverless adapters). No state persists between requests. No timeout beyond the adapter limit.

Exports recognized:

Export Description
GET(req, reply) Handle HTTP GET
POST(req, reply) Handle HTTP POST
PUT(req, reply) Handle HTTP PUT
DELETE(req, reply) Handle HTTP DELETE
PATCH(req, reply) Handle HTTP PATCH
HEAD(req, reply) Handle HTTP HEAD
OPTIONS(req, reply) Handle HTTP OPTIONS

Any subset of these. req is the Celsian request object; reply has .json(), .text(), .html(), .status(), .redirect().

Lifecycle: request comes in → handler runs → response sent → handler scope is discarded.


hot

Persistent process. The server holds a WebSocket open for each connection. In-memory state (sets, maps, objects) lives for the lifetime of the server process, not the request. There is no timeout.

Exports recognized:

Export Description
websocket(peer, req) Called once per connection on open.
GET / POST / ... Plain HTTP methods work alongside WebSocket on the same route file.

websocket(peer, req) contract:

peerHotPeer:

reqHotRequest:

route config fields:

Field Type Default Effect
origins string[] unset Origin allowlist for the WebSocket handshake. When set, upgrade requests whose Origin header is not on the list are rejected with 403 before the handshake. Entries are compared as URL origins, case-insensitively (trailing slashes/paths/default ports are normalized away). Requests with no Origin header always pass — browsers always send one, and non-browser clients can forge any value anyway. Default is open: without origins, any site can open a WebSocket to this route — set it for any cookie-authenticated app. Entries must be inline string literals; unsafe dynamic forms fail the build instead of silently losing the allowlist. origins: [] is preserved and denies all browser origins.
export const route = { kind: 'hot', origins: ['https://app.example.com'] };

Known limitation — backpressure: peer.send() is fire-and-forget with no bufferedAmount cap. A slow consumer can buffer unbounded data in the socket write queue. Implement your own flow-control in the message handler for high-throughput binary streams.

Lifecycle: server boots → per-connection websocket(peer, req) called on upgrade → peer.on('message', ...) fires per frame → peer.on('close', ...) fires on disconnect. State in module scope persists across all connections for the lifetime of the process.


task

Off the request path. Not exposed as a regular HTTP endpoint — calls go through /__tasks. The handler is the POST export and receives a context object.

Exports recognized:

Export Description
POST(ctx) Handler. ctx.attempt (1-based retry count), ctx.input (parsed JSON body or { _cron: true } for scheduled runs).
route Object with kind: 'task' plus optional retries (default 0) and timeout in ms (default 30 000).
schedule Top-level cron expression sugar (standard five-field). Registers a cron trigger automatically on server start. Can be exported separately from route; does not need to be inside route.

route config fields:

Field Type Default Effect
retries number 0 Number of retry attempts after a failure.
timeout number (ms) 30000 Milliseconds before the run is considered timed-out and retried (if retries remain).

/__tasks admin interface:

Request Effect
GET /__tasks List all registered task routes.
POST /__tasks/<name> Trigger task <name> immediately with an optional JSON body as ctx.input.

vura tasks CLI:

vura tasks list                       # list task routes and their schedules
vura tasks run <name>                 # trigger immediately
vura tasks run <name> --input '{"k":1}' # with input

Lifecycle: POST to /__tasks/<name> → handler runs → retried up to retries times on throw → result returned as JSON. Scheduled tasks fire via the cron engine inside the running server.


Per-adapter support matrix

Route kind Node / VPS Docker Fly.io Railway CF Workers Lambda
Serverless yes yes yes yes yes yes
Hot (WebSocket) yes yes yes yes no no
Task / cron yes yes yes yes via scheduled event yes (EventBridge)

Hot routes require a persistent process that can hold a WebSocket open. Cloudflare Workers and Lambda terminate the process between invocations and cannot fulfill this contract. vura build emits a warning at build time when a hot route is detected and the selected adapter cannot support it:

[vura] N hot route(s) cannot run on <adapter> and were not bundled: /api/live/room — deploy them to a persistent host (see /self-host/)

The hot routes are not silently omitted — they are excluded with an explicit named warning.

See the Deploy rung and Adapters reference for the full per-adapter picture.