Built-in reference¶
Kiroha 0.8.0 has 30 global functions, 19 collection methods, one .size property and 5 schema
atoms. Globals are ordinary bindings and may be shadowed. Members are selected
by receiver kind; they are not global names. Normative text:
spec.md §7–§13.
Notation. f(a, b=default){cb}: = marks a builtin default (applied only
when the argument or option is omitted — explicit null never requests a
default). {cb} is a required trailing callback, [{cb}] an optional one.
"Accepts n" means callable with n ordinary arguments and no callback.
Collection members use a receiver: xs.map{f}, xs.fold(0){f}, xs.sum().
Zero ordinary arguments may omit () before a trailing callback. .size
returns a number directly. A saved method retains its receiver:
m=[1,2].map; m(){x=>x+1}. Registered record members take precedence over
fields; use r["size"] or r["keys"] to read colliding data. Other fields keep
ordinary dot access. A member type error reports the receiver at position 0,
then explicit arguments starting at 1.
General validation order (§7.2). Explicit arguments left to right, then
option fields in the order listed in each table, then unknown option names
(argument), then the callback's signature, then — for constrained
collections — every element, all before any callback runs or effect starts.
A wrong kind is type; a right kind with a bad value is argument. A
non-record options value is type. Callback signature mismatches are arity
with reason:"callback_signature". User callback failures propagate
unchanged.
- Data: size, range, map, filter, fold, each, flat, take, sort, group, unique, sum
- Records: keys, has, pick, merge, one
- Text and JSON: split, join, replace, text, number, parse, dump, encode, decode
- Schemas: str, num, int, bool, json,
.as, maybe, optional, open - Runtime: parallel, sleep, retry, pages, fail
- HTTP client: http, get, post, url
- Database: db, sql, tx
- Server: serve, input, reply
- Local: env, read, write, now, uuid, log
Data¶
| Function | Result | Errors / notes |
|---|---|---|
x.size |
Integer length: array items, record fields, string scalars, bytes octets | type for other kinds. "😀".size is 1 |
range(start, stop, step=1) |
Integers start + k*step before the exclusive stop |
All integers (type if not number, argument if fractional); step=0 → argument. Wrong direction or equal endpoints → [] |
xs.map{f} |
[f(x) ...] in order |
f accepts 1 |
xs.filter{f} |
Items where f(x) is true |
f must return bool (type) |
xs.fold(init){f} |
Left fold f(acc, x); init when empty |
f accepts 2 |
xs.each{f} |
null; runs f(x) for effects |
|
xs.flat() |
One-level concatenation | Every item must be an array (type), checked first |
xs.take(n) |
First min(n, len) items |
n nonnegative integer |
xs.sort() / xs.sort{f} |
Stable ascending sort by key (identity when omitted) | Keys all numbers or all strings (type otherwise, including a single invalid key). Each key computed once, in order |
xs.group{f} |
[{key, items}] in first-seen order |
Keys must be JSON (type); equality is JSON equality (1 == 1.0, record order ignored); first key value kept |
xs.unique() / xs.unique{f} |
First item per JSON-equal key | As group |
xs.sum() |
Exact sum; [] → 0 |
All items numbers (type) |
[range(5, 0, -2), [{n:2},{n:1}].sort{x => x.n}, [1, 1.0, {a:1,b:2}, {b:2,a:1}].unique()]
// [[5,3,1],[{"n":1},{"n":2}],[1,{"a":1,"b":2}]]
Records¶
| Function | Result | Errors / notes |
|---|---|---|
r.keys() |
Keys in insertion order | |
r.has(k) |
Own-key presence (null values count as present) |
k string |
r.pick(ks) |
New record with keys in ks order |
All ks strings and unique (checked first, argument for duplicates); absent key → missing {key}; never inserts null |
a.merge(b) |
Shallow merge; b wins; existing keys keep position; new keys append |
Both records |
xs.one() |
The only item | Other lengths → cardinality {expected:1, actual} |
Text and JSON¶
All text operations are literal scalar sequences — no regex, no locale.
| Function | Result | Errors / notes |
|---|---|---|
s.split(sep) |
Parts, keeping empty leading/trailing parts. "".split(",") → [""] |
sep nonempty (argument) |
xs.join(sep) |
Strings joined; empty sep allowed |
Items strings (type) |
s.replace(old, new) |
All non-overlapping occurrences left to right; replacement not rescanned | old nonempty |
text(x) |
String unchanged; number/bool/null canonical spelling | Other kinds type |
number(s) |
Exact number from a complete signed JSON number | Non-string type; bad spelling (spaces, +1, 01, NaN) → number {reason:"invalid_syntax"} |
parse(s) |
Strict JSON with exact numbers and ordered records | json {reason, offset}; reasons syntax, duplicate_key, surrogate, bom, trailing |
dump(v) |
Canonical compact JSON (see values) | Any non-JSON value anywhere → type |
encode(s) |
UTF-8 bytes | |
decode(b) |
Strict UTF-8 string; a leading U+FEFF is kept | utf8 {offset}; non-bytes type |
Schemas¶
| Name | Meaning |
|---|---|
str, num, int, bool, json |
Atoms: string, number, integer-valued number, bool, any JSON |
[S] (array literal) |
Array of any length whose items match S; must have exactly one element |
{k: S, ...} (record literal) |
Closed record with exactly these required fields |
maybe(S) |
null or S; repeated maybe collapses; rejects optional operand |
optional(S) |
Field may be absent (not null). Only valid directly as a record field |
open(R) |
Record schema allowing extra JSON fields (preserved). Idempotent; only records |
(v).as(S) |
Normalizes S fully, then validates v; returns v unchanged |
v.as[T] |
Validates v against reifiable type T; returns it with type T |
Errors: malformed schema → schema {path, reason} with reasons
invalid_description, array_arity, optional_position, nested_optional,
record_required (path relative to the schema). Mismatch →
validation {path, expected, actual} where expected is one of
str num int bool json array record absent and actual a kind name or
"missing". Record fields are checked in schema order, then extra fields in
value order. Only the first failure is reported; no input values are copied
into the error.
Runtime¶
xs.parallel(n){f}¶
Runs f(item) with at most n (positive integer) callbacks active, starting
in index order; returns results in input order. Empty xs returns []
without starting anything (but f is still validated). On the first observed
failure no further items start, running siblings are cancelled and joined, and
that error is raised. Collect per-item failures with try inside f.
sleep(ms)¶
Waits at least ms (nonnegative integer) milliseconds, returns null.
Cancellable; sleep(0) yields to other tasks.
retry({attempts, when, delay}){f}¶
All three options are required: attempts integer ≥ 1 (counts the first
attempt), when a function accepting one error, delay integer ms ≥ 0. f
accepts 0. After a failed attempt i with attempts remaining, when(e) must
return a bool; true waits delay * 2^(i-1) ms and retries, false re-raises
e. The final failure is raised without calling when. A failing or
non-bool when replaces the error. Aborts are never retried.
pages(seed, o={}){step}¶
| Option | Default | Domain |
|---|---|---|
max_pages |
100 | integer ≥ 1 |
max_items |
10000 | integer ≥ 0 |
seed must be JSON. step(cursor) must return exactly {items:[...], next:JSON}
(type/argument otherwise). Items are concatenated; only next == null
ends (0, false, "", [] do not). Checks per page, first match wins: too many
items → pagination max_items; next null → return; repeated non-null cursor
(JSON equality, seed included) → pagination repeated_cursor; page count
reached → pagination max_pages. Error data {reason, pages, items}. No
partial result on failure.
fail(err)¶
Raises err unchanged. err must be exactly {code, message, data}: code
a nonempty string, message a string, data a JSON record. Extra/missing
fields → argument; wrong field kinds → type.
HTTP client¶
http(method, u, o={})¶
method is an uppercase HTTP token (not CONNECT); u a strict absolute
http/https URL (no userinfo, fragment, spaces or bad escapes). Returns
{status, headers, body} for every final status 200–599. Response headers are
a record of lowercase names to arrays of values in wire order (not
comma-split; set-cookie not merged).
| Option | Default | Meaning |
|---|---|---|
headers |
{} |
Record of string values. Names are tokens, case-insensitively unique. Values: tab, space, visible ASCII. Runtime-owned names (Host, Content-Length, Transfer-Encoding, Connection, Keep-Alive, Proxy-Connection, Upgrade, TE, Trailer, Expect) → argument. Accept-Encoding may request only gzip/identity |
query |
{} |
Record appended as encoded pairs; values string/number/bool/null or one-level arrays; null omitted |
body |
absent | Bytes; forbidden for GET and HEAD |
timeout |
10000 | Positive ms covering connect, TLS, send, read and decode |
max_bytes |
8388608 | Limit on decoded body octets → limit |
Responses with Content-Encoding: gzip are decoded (checksums verified); any
other coding → network unsupported_encoding; corrupt gzip →
network decode. HEAD, 204 and 304 return empty bodies; a 205 with content is
a network protocol error. No redirects, retries, cookies or proxy
discovery; HTTPS always verifies chain and hostname. Network failures report
outcome: "not_sent" only when no request bytes were sent, otherwise
"unknown".
get(u, o={}) and post(u, v, o={})¶
Same options except body. post serializes JSON v with dump and sends
application/json (an explicit Content-Type must be application/json,
optionally charset=utf-8; Content-Encoding must be identity). Both require
status 2xx (else http {status}, without parsing), then strictly decode UTF-8
and parse one JSON value. An empty body is a json error.
url(base, segments, query={})¶
Appends percent-encoded path segments (unreserved characters kept, / → %2F,
space → %20, + → %2B) after removing at most one trailing slash from the
base path, then appends query pairs. "."/".." segments → argument.
Origin and existing query are preserved.
url("https://api.example/base?old=1", ["a/b","x y"], {q:["a b",null,true], n:0})
// "https://api.example/base/a%2Fb/x%20y?old=1&q=a%20b&q=true&n=0"
Database¶
The db/sql/tx API is shared by PostgreSQL, MySQL and MariaDB. Select
backend in the host pool configuration; SQL dialect and native result types
follow that backend. See the database guide.
db(name)¶
Returns the configured pool handle for alias name (needs the
databases capability). Repeated calls reuse the pool; there is no close.
sql(d, Q, o={})¶
d is a database handle or a live transaction handle owned by the current
task; Q must be a query literal (a string is type).
Kiroha also provides SQL blocks: sql[T](d){...} validates
each returned row as T and returns [T]; sql(d){...} returns the usual
{rows, count}. Either form accepts options after d. Validation occurs after
SQL execution. See the database guide
for details.
| Option | Default | Domain |
|---|---|---|
timeout |
10000 | positive ms |
max_rows |
10000 | nonnegative; exceeding → limit (never truncates) |
The following type mapping describes PostgreSQL; the database guide lists MySQL/MariaDB mappings (BOOL → integer, binary → Bytes, MariaDB JSON → string, large/fractional numeric parameters → exact text requiring a DECIMAL cast).
Statement validation (before any connection is used) rejects with
db {phase:"prepare", outcome:"not_sent", reason}: statement (not a single
SELECT/INSERT/UPDATE/DELETE (PostgreSQL also MERGE), optionally with WITH; includes DDL, SELECT
INTO, transaction control), multiple_statements, hole_context (hole inside
quotes, comments or dollar-quoted text), literal_parameter (a written $1 on PostgreSQL or ? on MySQL/MariaDB).
Parameters bind as: null → untyped NULL, bool → boolean, string → text (no
U+0000), number → numeric (exact), bytes → bytea, JSON record/array → jsonb.
Other kinds → db {phase:"bind"}. Result columns: int2/int4/int8/numeric →
exact numbers (NaN/Infinity rejected), text/varchar/bpchar/uuid → strings,
bool, bytea → bytes, json/jsonb → parsed JSON, NULL → null. Any other column
type or duplicate column name → db {phase:"decode"} even for zero rows.
Returns {rows:[record], count:int}. Database errors carry phase,
outcome (not_sent, rolled_back, committed, unknown) and sqlstate
when the server supplied one.
tx(d, o={timeout:10000}){f}¶
Runs f(t) inside a read-committed transaction on one connection in the same
task. Normal return commits and returns the value; failure rolls back.
Precedence of outcomes: ancestor abort > transaction timeout (ordinary
timeout {operation:"tx"}, catchable outside) > first failed sql(t)
(latched even if caught) > other callback error. t expires when f
finishes; other tasks (including parallel children) cannot use it; nested
tx in the same task → db {phase:"ownership"}. An unconfirmed COMMIT is
db {phase:"commit", outcome:"unknown"} and is never retried.
Server¶
serve(routes, o={})¶
routes maps "METHOD /path" to one-argument handlers. Path segments starting
with : are parameters. All routes, handlers and options are validated
before listening; ambiguous routes (same method, same length, same number of
literals, compatible at every position) → argument.
| Option | Default | Domain |
|---|---|---|
host |
"127.0.0.1" |
numeric IP |
port |
8080 | 1–65535 |
timeout |
30000 | positive ms per request |
max_body |
1048576 | nonnegative bytes |
max_inflight |
128 | positive |
The endpoint must be in capabilities.listen. The most specific matching
route (most literal segments) wins. Request record:
{method, path, params, query, headers, body} — path is the raw encoded
path, params are decoded once (so %2F stays inside one segment), query
values are arrays with + decoded as space. Response mapping and error codes:
see the language guide and spec §12.4.
input(r, S=json)¶
Normalizes S (malformed → schema), checks the six-field request shape,
requires identity content coding and exactly one
Content-Type: application/json (optional charset=utf-8) (else media),
then decodes, parses and validates; failures → input {reason} with reason
utf8, json or validation (plus path/expected/actual).
reply(status, v, headers={})¶
Status 200–599. v is JSON (sent as application/json) or bytes (default
application/octet-stream). 204/205/304 require null and forbid a
Content-Type. Header rules as for http. Returns an opaque reply value; a
plain record like {status:404} is just JSON.
Local¶
| Function | Capability | Result / errors |
|---|---|---|
env(name) |
env lists name |
The configured string; absent → env {name} |
read(path) |
read directory |
Whole regular file as bytes; missing → io not_found; over max_file_bytes → resource abort |
write(path, b) |
write directory |
Atomic replacement, returns null. Keeps existing permission bits, new files 0600, no parent creation, symlinks refused |
now() |
clock |
UTC YYYY-MM-DDTHH:mm:ss.sssZ (truncated ms) |
uuid() |
random |
Lowercase random UUIDv4 |
log(v) |
log |
Writes dump(v) + LF to stderr; non-JSON → type before output |
Paths: relative paths resolve against filesystem_root; .. components and
NUL are rejected; . and repeated slashes are ignored; a trailing slash is
not a file. Every component is opened without following symlinks.