Using the library
Vela's library is a set of modules written in Vela that come with the
compiler. A program brings one in the way it brings in a
module of its own, with
include and a path that starts with std:
include std/files {File} ; File, by its own name include std/files ; or through the module: files.File include std/files => disk ; or under a name of the file's choosing: disk.File
Nothing is read from the disk to find it: the library is part of the
compiler, so the command, Vela Studio, and the web editor all have the
same library, and a program needs nothing installed beside the
compiler. Everything the
module rules say holds for it too:
priv keeps what a module keeps to itself, names brought
in may be renamed with =>, and a file includes each
module once.
-
stdis not a word. It is only the first part of a path on anincludeline, the waystd::starts a name in C++. A variable, a task, or a field may still be calledstd. -
A module is named.
include stdalone is refused: 'std' is Vela's library, not one of its modules; include a module of it, as in 'include std/files'. So is a module the library doesn't have, and the message lists the ones it does. -
A program's folder can't be called
std. A module in a folder namedstdbeside the main file would have a name that reads as the library's, so it is refused: '../std/helpers' is in a folder named 'std' beside the main file, which would read as Vela's library; give the folder another name.
std/collections
Lazy iterators and eager tasks over
the items of a slice. The pattern
Iterator<T> is one next() after
another; a slice has its chain members (map,
filter, take, skip,
zip, enumerate, and the consumers
collect, fold, find,
all, any) in a file that includes the
module, each step reading the one before it and gathering nothing
until a consumer asks. zip gives Pair shapes
(.first, .second) and
enumerate gives
Indexed (.index, .item); either
side of zip may be a slice, read as its own iterator. The
shapes Iterator, Pair, Indexed,
and SliceIter are brought in by name; the chain members
need no import, and each x in it goes through any
iterator.
The eager tasks are written to be read in a
pipe: each takes the slice first, then
what it does with an item (a task value, or a
one-line lambda, whose types
the slice tells), then, for those that make a new slice, the region it
goes in, last, so at r gives it. Brought in with
include std/collections (map, filter), or named
collections.map(…) after
include std/collections.
include std/collections (map, filter, fold, take) task main() region r fixed nums = new [int]([1, 2, 3, 4, 5]) at r show(nums |> filter(n -> n > 2) at r |> map(n -> n * 10) at r) ; [30, 40, 50] show(nums |> fold(0, (sum, n) -> sum + n)) ; 15 show(nums |> take(2)) ; [1, 2], a view of nums fi
| Task | What it gives |
|---|---|
map(xs, f, r) -> [R]@r |
a new slice in r of what f gives for
each item, in order
|
filter(xs, keep, r) -> [T]@r |
a new slice in r of the items keep holds
for, in order
|
fold(xs, initial, step) -> A |
initial, then step of it and each item
in turn: fold(xs, 0, (sum, n) -> sum + n) adds
them up; makes no memory
|
zip(xs, ys, r) -> [(A, B)]@r |
a new slice in r of pairs of the items at the same
place, as long as the shorter slice
|
enumerate(xs, r) -> [(int, T)]@r |
a new slice in r of each item with its place, from 0
|
take(xs, n) -> [T]@a |
the first n items, a view of the same slice; all of
them when it has fewer, none when n is below 1
|
skip(xs, n) -> [T]@a |
the items after the first n, a view of the same
slice; none when it has n or fewer
|
find(xs, test) -> maybe T |
the first item test holds for, or none
|
all(xs, test) -> bool |
whether test holds for every item; true for none
|
any(xs, test) -> bool |
whether test holds for some item; false for none
|
-
Eager. Each task goes through all the items when it
is called, and
map,filter,zipandenumeratemake their slice at once, so a pipe of two of them makes one slice in between. Lazy chains that make nothing until collected are planned (see Iterators & Collection Pipelines). -
Slices only. An array never becomes a slice, so
[1, 2, 3] |> map(…)is refused with a message saying so: write the values as a slice,[1, 2, 3] at r, or copy them into one. -
zipandenumeratemake tuples, whose items never change, so their items arefrozentypes: numbers, bools, text, and tuples and shapes of those, not slices or pointers. -
A lambda takes nothing from the code around it, so
a one-line lambda given here reads only its item and what the file
declares, and makes no memory in a region of the caller; one that
keeps what it uses (
[@r]) goes only where the task's type says[@r], which these don't.
std/files
Files on the disk. A File is made from its path, and
making one opens nothing: it is a value that names a file, which its
methods ask about, open, read, write, rename, and delete. A file is
opened in a region, which closes it when
its block ends, as it frees its memory (a
block … fi makes a block
anywhere, for just that); close() closes it sooner.
Everything that touches the disk can fail, with a
FileError that a
plan B handles.
include std/files {File, FileMode} task main() region r flex notes = File { path: "notes.txt" } notes.open(FileMode.Write) at r { failure e show("could not open it") } notes.write("first line\n") { failure e show("could not write") } notes.close() { failure e show("could not close") } notes.open(FileMode.Read) at r { failure e show("could not open it") } loop notes.line() at r { none } is {line} show(line) ; first line fi fi ; r's block ends, and closes the file
File
Its fields are
read fields: any
code reads them, and only File's own alters methods
change them. path is given when a File is made; the
others start at their own values, which no literal gives.
| Member | What it is |
|---|---|
path: text |
The path the File names. |
state: FileState |
What its methods left it: Idle until opened, then
Open, Closed, or
Gone once deleted.
|
mode: maybe FileMode |
How it was opened last; none before then. |
alters open(mode) at r |
Opens the file as mode says, until
close() or the end of r's block. Fails
with AlreadyOpen on a file open already, and
NotFound or NotAllowed when the system
says so.
|
alters close() |
Closes the file. On one that isn't open, fails with
NotOpen.
|
read() at r -> text@r |
The rest of the file, from where reading is. |
line() at r -> maybe text@r |
The next line without its line break (a Windows
\r\n as well); none at the end. A last
line without a line break is still a line.
|
lines() at r -> [text@r]@r |
The rest of the file, line by line. |
write(t) |
Writes t, of any region, where writing is. |
ended() -> bool |
Whether nothing more can be read: at the end, or not open for reading. |
size() -> int |
How many bytes the file has; an open one with what was written so far. |
exists() -> bool |
Whether a file that can be read is at the path. A folder, or a file the program may not read, is not one, and neither is a path holding a zero byte. |
alters rename(to) |
Gives the file another path, on the disk and in
path.
|
alters delete() |
Deletes the file, closing it first when it is open; its state is
then Gone.
|
str() at r -> text@r |
The path and the state: notes.txt (open). |
Each of read, line, lines,
write, size, rename, and
delete can fail, and their results are
~ FileError. The methods marked
alters change the File, so they are called on a
flex variable (or through a pointer). The ones that take
a region take it last, so it is written after the call:
notes.read() at r, as
any such call may.
FileMode
| Variant | Opens the file to |
|---|---|
Read |
read it from its start; it must be there. |
Write |
write it from its start, emptied first, and made when it isn't there. |
Append |
write at its end, and made when it isn't there. |
ReadWrite |
read and write it; it must be there. Reading and writing go on from the same place. |
FileError
| Variant | When |
|---|---|
NotFound |
There is no file at the path, or none that can be read. |
NotAllowed |
The system doesn't let the program open it so. |
AlreadyOpen |
Opened again before it was closed. |
NotOpen |
Not open: never opened, closed, deleted, or closed by its region or by a copy. |
WrongMode |
Read when opened only to write, or written when opened only to read. |
Failed |
Anything else the system reports. |
NotFound and NotAllowed are told apart where
the system's C library says which; where it doesn't, a failure to open
is Failed.
Regions and copies
The region a file is opened in owns it: when the region's block ends,
the file is closed, even when a File naming it lives on outside the
block. A File is a value, and a copy of it names the same open file,
so closing either closes it for both. Neither can tell the File they
didn't close, so its
state still says Open until one of its own
methods finds out: every method then finds the file closed, and fails
with NotOpen, never reaching a file opened since (a
close() that finds it so sets state to
Closed). The state is what the File's methods left it,
and what the file is, its methods tell.
region r flex kept = File { path: "notes.txt" } each pass in 0..1 region round kept.open(FileMode.Read) at round { failure e show("could not open it") } fi ; round's block ends, and closes the file show(kept.str() at r) ; notes.txt (open): what open left it show(kept.line() at r { failure e "closed" ; NotOpen, so the plan B runs }) ; { "closed" }: line() gives a maybe text, none at the end
examples/files.vela is a whole program: a list written to
a file, added to, read back line by line, and deleted.
std/gui
Foundational 2D software canvas rasterization, native X11 window
presentation, styling themes, interactive widgets, and per-frame
region discipline. Brought in with
include std/gui {Window, Canvas, GuiEvent, Point, Size, Rect,
Color, Style, Button, Label, TextBox, ListBox, CheckBox,
ProgressBar, ImageView, MessageBox, InputBox, VBox, HBox,
is_interactive}
or include std/gui => gui. See also the comprehensive
guide in docs/gui.html.
include std/gui {Window, Canvas, GuiEvent, Point, Rect, Color, Style, Button, ProgressBar} task main() flex win = Window.open("GUI Application", 340, 220) { failure _ <- } fixed btn = Button { rect: Rect { x: 30, y: 120, width: 90, height: 36 }, label: "Action", style: Style.primary() } fixed canvas = win.canvas() loop win.is_open region frame default fixed ev = win.poll_event() match ev case GuiEvent.Redraw canvas.clear(Color.hex(0x0F172A)) btn.draw(canvas, Point { x: 0, y: 0 }, false) win.present() case GuiEvent.CloseRequested win.close() break case _ fi fi fi
| Name | Role |
|---|---|
Point |
2D coordinate: Point { x: int, y: int } |
Size |
Dimensions: Size { width: int, height: int } |
Rect |
2D bounding box: Rect { x, y, width, height }, with
.contains(point) and
.intersects(other_rect)
|
Color |
RGBA color: Color { r, g, b, a },
Color.rgb(r, g, b),
Color.rgba(r, g, b, a),
Color.hex(0xRRGGBB), and built-in palettes
(.black(), .white(),
.red(), .green(), .blue(),
.gray(), .transparent())
|
Style |
Unified styling theme shape: presets Style.default(),
.primary(), .secondary(),
.success(), .danger(),
.dark(), .light(), and fluent modifiers
.with_bg(c), .with_fg(c),
.with_border(c, width), .with_padding(p)
|
Window |
Window context: Window.open(title, width, height),
.canvas(), .poll_event(),
.push_event(type, a1, a2, a3),
.present(), .close(),
.is_open
|
Canvas |
Software rasterization drawing context:
.clear(color), .draw_pixel(x, y, color),
.pixel_at(x, y),
.fill_rect(rect, color),
.draw_rect(rect, color),
.draw_line(x1, y1, x2, y2, color),
.draw_text(msg, pos, color),
.export_ppm(r)
|
GuiEvent |
Event kind: MouseDown(x, y, btn),
MouseUp(x, y, btn), MouseMove(x, y),
KeyDown(key, mods), KeyUp(key, mods),
Resize(w, h), CloseRequested,
Redraw, None
|
Button |
Interactive widget: Button { rect, label, style },
.draw(canvas, mouse_pos, is_down),
.clicked(pos)
|
Label |
Text widget: Label { pos, text, color, bg },
.draw(canvas)
|
TextBox |
Single-line input widget:
TextBox { rect, text, placeholder, style },
.set_text(t), .clear(),
.handle_click(pos),
.handle_key(key, mods), .draw(canvas)
|
ListBox |
Selectable list:
ListBox { rect, item_height, style },
.add_item(t), .clear(),
.select(idx), .selected_item(),
.handle_click(pos),
.draw(canvas, mouse_pos)
|
CheckBox |
Toggle box: CheckBox { rect, label, checked, style },
.toggle(), .handle_click(pos),
.clicked(pos), .draw(canvas, mouse_pos)
|
ProgressBar |
Progress indicator:
ProgressBar { rect, value, fill_color, style },
.set_value(v) (0..100), .draw(canvas)
|
ImageView |
Graphic frame: ImageView { rect, border_color },
.draw_checkerboard(canvas, cell_size),
.draw_frame(canvas, bg)
|
MessageBox |
Modal alert overlay: MessageBox { style },
.show_modal(title, message), .dismiss(),
.handle_click(pos),
.draw(canvas, win_w, win_h, mouse_pos, is_down)
|
InputBox |
Modal prompt overlay: InputBox { style },
.show_modal(title, prompt, default),
.dismiss(), .value(),
.handle_click(pos),
.handle_key(key, mods), .draw(...)
|
VBox, HBox |
Linear layout flow calculators:
VBox.new(x, y, width, spacing),
HBox.new(x, y, height, spacing),
.next(size), .reset()
|
-
Per-frame arena discipline. Interactive frame loops
allocate all temporary drawing buffers, glyph records, and event
handlers inside a dedicated per-frame region (
region frame default). At the end of every frame iteration, all frame data is instantly reclaimed in $O(1)$ time with zero garbage collection overhead. -
Embedded 8x8 bitmap font.
canvas.draw_textrenders ASCII text directly into the frame buffer without requiring external font files or system rasterization dependencies. -
Portable testing and PPM export.
canvas.export_ppm(r)outputs standard binary PPM images, andwin.push_event(...)enables 100% deterministic, headless GUI testing in CI environments. - Desktop window presentation. Automatically presents native desktop windows using dynamic X11 linking when a display server is present, falling back cleanly to headless mode when running in headless CI or without a display.
std/int512
512-bit signed integers in two's complement, Int512.
Brought in with
include std/int512 {Int512, int512, zero512}. A value is
held across eight 64-bit unsigned limbs, lowest limb first. Every
arithmetic operation strictly follows Vela's checked-arithmetic
safety: any overflow past 512 bits or division by zero immediately
stops the program with an informative runtime trap.
Int512 follows the module's
Numeric<T> pattern, so it also answers the
operators the pattern names:
a + b, a * b, -a, and
a < b read as the methods plus, times, neg, and cmp,
both operands Int512.
include std/int512 {Int512, int512, int512_parse} task main() region r fixed a = int512(1000) fixed b = int512_parse("98765432101234567890") { Int512.zero() } fixed c = a.times(b) show(c.str(r)) ; 98765432101234567890000 show("{a} / 3 = {a.div(int512(3))}" at r) fi
| Operation / Member | What it does |
|---|---|
plus(other), minus(other),
times(other)
|
addition, subtraction, and multiplication; traps on signed overflow |
div(other), rem(other) |
quotient (truncated toward zero) and remainder; traps on division
by zero or MIN / -1
|
neg(), abs() |
negation and absolute value; traps on MIN |
cmp(other), less(other) |
compares values: cmp gives -1, 0, or 1;
less gives bool
|
to_int64(), to_uint64() |
converts to primitive 64-bit integer; traps if the value does not fit |
str(r: region) |
formats the integer as decimal text into region r
|
Int512.zero(), Int512.one() |
shared constants for 0 and 1 |
Int512.min_value(), Int512.max_value()
|
the smallest (−2511) and largest (2511 − 1) 512-bit signed integers |
Int512.from_int(n), int512(n) |
creates an Int512 from an int64 |
int512_from_uint64(n) |
creates an Int512 from a uint64 |
int512_parse(s), Int512.parse(s) |
parses text (decimal, or base prefix 0x,
0o, 0b); gives
Int512 ~ ParseError
|
std/json
JSON parsing, serialization, and tree manipulation. Brought in with
include std/json {Json, JsonError, parse, stringify}.
include std/json {Json, parse, stringify} task main() region r default fixed raw = "\{"name": "Vela", "stars": 100, "active": true\}" fixed doc = parse(raw, r) { Json.Null } fixed name = doc.get("name") { Json.Null }.as_text() { "" } fixed stars = doc.get("stars") { Json.Null }.as_int() { 0 } show(name, " has ", stars, " stars") show("json: ", doc.str(r)) fi
| Name | Role |
|---|---|
Json |
Kind representing JSON values: Null,
Bool(b), Int(n), Float(f),
Str(s), Array(items),
Object(props)
|
Json.is_null() |
Returns true if the value is Null |
Json.as_bool(), as_int(),
as_float(), as_text()
|
Unwraps primitive values into maybe T |
Json.get(key) |
Looks up a property in an object, returning
maybe Json@this
|
Json.item(idx) |
Retrieves an element from an array by index, returning
maybe Json@this
|
Json.str(r) |
Serializes the JSON tree to text in region r |
parse(raw, r) |
Parses a JSON string into Json@r ~ JsonError@r,
strictly: an unknown escape such as \q fails with
InvalidEscape, and a control character written raw in
a string with UnexpectedToken;
\uXXXX escapes, pairs included, are decoded
|
stringify(val, r) |
Serializes a Json@p value into JSON formatted text
|
std/loops
What a
loop that gives a value fails
with, LoopResult<B, M>: a kind that takes types,
written in Vela. A loop gives a value in any file, with or without
this module; a file that writes LoopResult itself, in a
type or a variant, brings it in by name (unknown type 'LoopResult'; it is in Vela's library, and 'include
std/loops {LoopResult}' brings it in). A case Broke b on a loop's failure names no kind, and
needs no include.
include std/loops {LoopResult} task main() flex last = 0 fixed found: int ~ LoopResult = each x in [2, 7, 4] when x > 5 break = x fi last = x finally when last == 4 fail LoopResult.Manual("four") else last fi fi show(found { 0 }) ; 0: a break stopped it at 7 fi
| Variant | Why the loop has no value |
|---|---|
Skipped |
its body never ran: the condition failed at once, or there was nothing to go through |
Broke(value: maybe B) |
a break stopped it, with what
break = v gave, or none
|
EndedByContinue |
its final pass ended with continue, before the last
line
|
Manual(value: M) |
the last line of its final pass failed with
fail LoopResult.Manual(m)
|
B is what the loop's breaks give and
M what its last lines fail with, each taken from the
place the value goes where the loop gives none, and
bool where no place names it either. On a loop's own
variable, a bare LoopResult takes the loop's types, as
found does above; anywhere else they are written,
LoopResult<int, text>.
std/math
Numbers beyond arithmetic: the macros
P (pi) and e, and tasks for powers,
trigonometry, exponentials, and logarithms. Every task takes and gives
floats, so an int is written as a float
(math.sin(1.0)) or turned into one
(math.sin(float(n))). Angles are in radians.
include std/math (P, e, sin) task main() show(sin(P / 2.0)) ; 1 show(math.cos(P)) ; -1 show(e ** 2.0) ; 7.38906, computed as math.exp(2.0) show(math.log(1000.0)) ; 3, base 10 when none is given show(math.log(8.0, 2.0)) ; 3 show(math.log(e, e)) ; 1, the natural logarithm fi
| Name | What it is |
|---|---|
P |
pi, 3.141592653589793, a macro |
e |
Euler's number, 2.718281828459045, a macro;
math.e ** x is computed as math.exp(x),
which C works out more closely than a power
|
pow(x, y) |
x raised to the power y, as
x ** y is for floats
|
sin(x), cos(x), tan(x)
|
the sine, cosine, and tangent of an angle |
asin(x), acos(x), atan(x)
|
the angle whose sine, cosine, or tangent is x |
sinh(x), cosh(x), tanh(x)
|
the hyperbolic sine, cosine, and tangent |
exp(x) |
e raised to the power x |
log(x, base = 10.0) |
the power base is raised to for x, base
10 when the call leaves it out (see
optional arguments);
with e it is the natural logarithm. Bases 2, 10, and
e are C's own log2, log10,
and log, which are exact where a quotient of
logarithms may not be.
|
A value outside what a task takes gives what C's gives, as float
arithmetic does: math.log(-1.0) and
math.asin(2.0) are nan, and math.log(0.0) is
-inf. Nothing stops the program. math.P and
math.e are macros, so nothing can change them ('P' is a macro, a value that never changes, not a variable), and a file that brings in e by name can't name a
variable e; one that writes math.e can.
std/web
Non-blocking networking, HTTP/1.1 request/response shapes,
pattern-matching routers, decorator-based route registration, and
composable call-time middleware. Brought in with
include std/web {Socket, Request, Response, Method, WebError, get,
post, serve}
or include std/web => web. See also the architectural
guide in docs/web.html.
include std/web {Socket, Request, Response, WebError, get, post, serve} task auth(next: (Request@r, @r) -> Response@r ~ WebError@r, req: Request@r, r: region default) -> Response@r ~ WebError@r when req.header("authorization") is {token} when token == "Bearer secret" <- next(req, r) fi fi <- fail WebError.Unauthorized("invalid token") fi $get("/greeting/\{name\}") task greet(req: Request@r, r: region default) -> Response@r ~ WebError@r fixed name = req.param("name") { "friend" } <- Response.text("Hello, {name}!\n" at r, r) fi $get("/secret") $(auth) task secret_panel(req: Request@r, r: region default) -> Response@r ~ WebError@r <- Response.text("Confidential data\n", r) fi task main() serve("127.0.0.1:8080") { failure err show("server stopped: ", err) } fi
| Name | Role |
|---|---|
Socket |
Non-blocking TCP socket handle (open,
bind_to, listen, accept,
connect, read, write,
close, poll)
|
Request@r |
HTTP request: .method, .path,
.raw_query, .headers,
.body_data, .header(name),
.query(name, r), .param(name),
.body(); .path is decoded from
%XX escapes, and .query decodes them and
+ in its key and value
|
Response@r |
HTTP response: Response.text(body, r),
Response.html(body, r),
Response.json(body, r),
Response.with_status(code, r),
Response.redirect(to, r), .header(k, v),
.status_code(code), .set_body(v)
|
WebError |
Failure kind: ListenFailed,
BadRequest(why), PayloadTooLarge,
RouteNotFound(where),
MethodNotAllowed(method, path),
Unauthorized(why), ServerFailed(why),
BadResponse(why) (a reply that isn't HTTP)
|
$get, $post, $put, $patch, $delete, $route |
Startup decorators that register handlers to the default router
before main
|
$on_error, on_error(handler) |
Custom error handler decorator for DefaultRouter or
programmatic router.on_error(handler) /
server.on_error(handler) accepting
(err: WebError@r, req: Request@r, r: region default) ->
Response@r
|
Router |
Programmatic router shape: Router.new(),
.get(pattern, h), .post(pattern, h),
.on_error(h),
.enroll(method_code, pattern, h),
.route(req, r)
|
Server |
Configurable server shape: Server.new(addr),
.timeouts(read_ms, idle_ms),
.body_limit(bytes), .keep_alive(bool),
.on_error(h), .serve(handler),
Server.stop()
|
Poll |
Descriptor readiness table: Poll.clear(),
Poll.watch(sock, events),
Poll.unwatch(sock),
Poll.wait(timeout_ms),
Poll.is_readable(sock),
Poll.is_writable(sock)
|
serve(addr, handler) |
Listens on TCP address and drives the non-blocking event loop dispatching incoming requests |
Client |
Configurable HTTP client: Client.new(timeout_ms),
.timeout(ms), .get(url, r),
.post(url, body, ct, r),
.post_json(url, body, r)
|
fetch(url, r) |
Performs an HTTP GET request and parses response into
Response@r ~ WebError@r
|
fetch_post(url, body, ct, r),
fetch_json(url, body, r)
|
Performs an HTTP POST request and parses response into
Response@r ~ WebError@r
|
FileServer |
Configurable static file server shape:
FileServer.new(folder, prefix),
.serve(req, r)
|
serve_files(pattern, folder, prefix),
files(folder, req, prefix, r)
|
Serves static files with path confinement discipline (rejecting
.., backslashes, colons, control chars, and empty
segments) and MIME mapping
|
file_response(path, r) |
Reads a single file into Response@r with automatic
MIME detection; the path is relative, with no
.. part, so it can't leave the folder the program
runs in, and an absolute one fails with BadRequest
|
parse_response(raw, r) |
Parses an HTTP reply into Response@r ~ WebError@r,
failing with BadResponse when the status line is
missing, doesn't start with HTTP/, or has no status
from 100 to 599, or the headers never end
|
Response.stream(chunks, r),
Response.sse(chunks, r)
|
Constructs HTTP/1.1 chunked transfer responses or Server-Sent Events (SSE) |
sse_event(data, event, id, r),
sse_msg(event, data, r)
|
Formats compliant SSE stream messages (event: ...\ndata: ...\n\n)
|
req.body_lines(r) |
Parses request body line-by-line into a region-scoped slice
[text]@r
|
WebSocket |
RFC 6455 WebSocket handle: .send_text(msg, r),
.send_binary(data, r), .ping(data, r),
.pong(data, r), .close(code, reason, r),
.recv(into), .is_open()
|
WebSocketMessage |
Typed message kind: Text(payload),
Binary(payload), Close(code, reason),
Ping(payload), Pong(payload),
None
|
ws_upgrade(client_key, r),
ws_accept(req, client, r)
|
Performs RFC 6455 WebSocket handshake and produces HTTP 101 Switching Protocols response |
ws_connect(url, r) |
Connects to a WebSocket server URL
(ws://host:port/path) and completes client handshake
|
router.ws(pattern, handler),
ws(pattern, handler)
|
Enrolls a persistent WebSocket route handler
(ws: WebSocket@r, req: Request@r, r: region default) ->
nothing
|
-
Path parameters and wildcards. Patterns like
/users/\{id\}capture dynamic path segments accessible viareq.param("id"). Wildcards like/assets/*capture remaining path text viareq.param("*"). -
Method-aware routing. If a route's path matches but
the request method does not, the router automatically fails with
WebError.MethodNotAllowed, yielding HTTP 405. If no route matches, it fails withWebError.RouteNotFound, yielding HTTP 404. -
Composable middleware. Decorators like
$(auth, logged)wrap handlers at call time, intercepting requests and short-circuiting on failure with typed errors such asWebError.Unauthorized. -
Custom error handling. Decorator
$on_errororrouter.on_error(handler)/server.on_error(handler)intercepts route errors and connection failures, receiving both the typedWebError@randRequest@rcontext to return structured JSON or custom HTML error pages (e.g. 404, 401, 405, 500). -
Event loop, keep-alive, and timeouts. The
single-threaded event loop multiplexes connections using
non-blocking I/O. HTTP/1.1 connections remain persistent by default;
idle sockets past
idle_timeout_msare swept, stalled partial reads pastread_timeout_msare closed with 408 Request Timeout, andServer.stop()shuts down gracefully. The loop never waits on one client: a request still arriving waits in its socket's own buffer, and a response the client can't take yet waits in the socket's queue and goes out as the client reads, so a client that stops reading holds up nobody and is dropped afteridle_timeout_ms. Both buffers live outside any region and go with the socket, so a long-running server holds only what its open connections still need.send_responseandsend_error_response, for a socket of your own, wait until the response is out, up to 5 seconds without progress. -
HTTP client and URL parsing.
fetch(url)andClientparse URLs (http://host:port/path), establish non-blocking TCP connections, issue compliant HTTP/1.1 requests, and parse response headers and bodies intoResponse@r. -
Static files and path confinement.
serve_files(pattern, folder, prefix)andFileServerserve directory contents with automatic MIME type mapping (.html,.css,.js,.json,.txt,.png,.svg, etc.). Request paths are decoded from%XXescapes first, and a%that starts no escape, or escapes that don't decode to UTF-8, make a 400. Traversal attacks (..,%2e%2e,a/../../b), absolute paths, backslashes, colons, and null bytes are rejected upfront with 400 Bad Request, confining file reads strictly within the target folder. -
Chunked streaming and Server-Sent Events.
Response.stream(chunks)streams response chunks via HTTP/1.1Transfer-Encoding: chunked.Response.sse(events)formats Server-Sent Events withContent-Type: text/event-stream.parse_responseandClientautomatically decode chunked streams. -
Request body lines.
req.body_lines(r)parses request payload lines into a slice without manual line-splitting logic.
How the library is made
The library is Vela source in the repository's
std/ folder, which the compiler holds as it is built.
Most of it is Vela like any program's. What Vela itself can't do, such
as asking the system about a file, is a native task: its
header is Vela, and its body is a function of the runtime that the
generated C includes.
; std/files.vela priv native task path_exists(path: text@p) -> bool
A native task has no body and no fi, and the compiler
writes its body as a call of the runtime function
src/library.rs names for it. Only the library has them:
in a program, native task is refused ('native' marks a task whose body the compiler writes in C, which
only Vela's library has), so the library is not a way to call any C a program likes. Like
read, native is a word only there, so a
program may still name anything native. The runtime's
functions use standard C11 alone, so a program that uses the library
builds wherever any other does. std/math's tasks are
native ones whose bodies are C's own functions of math.h,
and its P and e are macros like any
program's.