Skip to content

Standard Library

Kumiki's standard library is designed with the goal of being "minimal and complete". It does not provide multiple functions for the same purpose (so as not to make the AI's choice ambiguous).

2.1 Built-in Types

2.1.1 Primitives

TypeRepresentationLiteral example
TextUTF-8 string"hello"
Int64-bit integer42, -7
Float64-bit floating point3.14, -0.5
Boolbooleantrue, false
Unitsingle value()
Bytesbyte sequenceno literal; created with Bytes.from-text()
TimeUNIX nanosecondsno literal; now or Time.parse()

2.1.2 Generic Types

TypeUse
Map(K, V)keys are Eq, values are arbitrary
Set(T)T is Eq
List(T)ordered, index-accessible
Option(T)None or Some(T)
Result(T, E)Ok(T) or Err(E)
Tuple(T1, ..., Tn)fixed length

2.1.3 Domain Types (provided by the standard library)

TypeDefinition
HttpStatusnominal Int where between(100, 599)
HttpError{status: HttpStatus, message: Text, body: Option(Text)}
Urlnominal Text where url
Emailnominal Text where email
Uuidnominal Text where uuid
Durationnominal Int (nanoseconds)
Route{path: Text, params: Map(Text, Text), query: Map(Text, Text)}
FormDataMap(Text, FormValue)
FormValueTextV(Text) | NumberV(Float) | BoolV(Bool) | FileV(File)
File{name: Text, size: Int, type: Text, content: Bytes}

2.2 Collection Methods

2.2.1 Map(K, V)

keys                        : List(K)
values                      : List(V)
entries                     : List(Tuple(K, V))  ; an array of [[k, v], ...] in the implementation
size                        : Int
is-empty                    : Bool
has(k)                      : Bool
get(k)                      : Option(V)
get-or(k, default)          : V
insert(k, v)                : Map(K, V)        ; pure. Returns a new Map
remove(k)                   : Map(K, V)
update(k, expr)             : Map(K, V)        ; within expr, $1 is the current value
merge(other)                : Map(K, V)
filter(pred)                : Map(K, V)        ; within pred, $1=key, $2=value
map(expr)                   : Map(K, V')       ; within expr, $1=key, $2=value

.entries returns a sequence of 2-element arrays as List(Tuple(K, V)). A subsequent map / sort-by / filter lambda can handle them as $1=key, $2=value via runtime destructuring:

kumiki
fn sortedByCreatedAt(m: Map(Id, Item)) -> List(Id)
   = m.entries.sort-by($2.createdAt).map($1)

get-or is a polymorphic method that can also be used for Option:

kumiki
m.get-or(k, default)         ; Map: default if there is no value
opt.get-or(default)          ; Option: default if None, v if Some(v)

.filter can be used on both List and Map, and the runtime dispatches automatically by looking at the receiver's type (polymorphic dispatch):

  • Receiver is List → evaluate pred($1) for each element, keep only elements that are true
  • Receiver is Map → evaluate pred($1, $2) (key, value) for each entry, keep only entries that are true

For example, when you chain like m.keys.filter(...), m.keys returns List(K), so filter runs with the List signature. Even if you write a mixed chain, the behavior follows the type.

2.2.2 Set(T)

size                        : Int
has(x)                      : Bool
add(x)                      : Set(T)
remove(x)                   : Set(T)
toggle(x)                   : Set(T)
union(other)                : Set(T)
intersect(other)            : Set(T)
diff(other)                 : Set(T)
to-list                     : List(T)

2.2.3 List(T)

length                      : Int
is-empty                    : Bool
get(i)                      : Option(T)
head                        : Option(T)
tail                        : List(T)
last                        : Option(T)
push(x)                     : List(T)
prepend(x)                  : List(T)
concat(other)               : List(T)
slice(start, end)           : List(T)
reverse                     : List(T)
sort                        : List(T)          ; T is Ord
sort-by(expr)               : List(T)
unique                      : List(T)
map(expr)                   : List(T')
filter(pred)                : List(T)
contains(x)                 : Bool
find(pred)                  : Option(T)
fold(init, expr)            : Acc              ; within expr, $1=acc, $2=elem
join(sep)                   : Text             ; T is Text
chunk(n)                    : List(List(T))
zip(other)                  : List(Tuple(T, U))

Parenthesis-free shortcut: argument-less methods (is-empty / length / reverse / sort / unique / head / tail / last) can omit () and be written like a field:

kumiki
slot todos : List(Todo) = []
fn count() -> Int = todos.length              ; parenthesis-free OK
fn empty?() -> Bool = todos.is-empty          ; same as above
fn norm() -> List(Todo) = todos.reverse       ; same as above

Dispatch rule (v0.3, ADR-002). recv.m is dispatched by the inferred type of recv, not by name: if recv is a record with a field m, it reads the field; if recv is a stdlib type with method m, it uses the shortcut. So a record field literally named like a method (node.head on {head, …}) is read as the field — not shadowed. When the receiver type is known and m is neither a field nor a member, it is a compile error (errors.md E0108). When the receiver type can't be inferred (e.g. an untyped reducer payload), the name-based dispatch is used unchanged.

The lambda arguments of map / filter / sort-by:

  • For a List element, $1 is bound; for the [k, v] pair after .entries, $1=key, $2=value are bound (the runtime destructures automatically)
  • Example: m.entries.sort-by($2.createdAt).map($1) with $1=key, $2=value

2.2.4 Option(T)

is-some                     : Bool
is-none                     : Bool
get                         : T               ; panics if None (allowed only inside a reducer)
get-or(default)             : T
map(expr)                   : Option(T')
flat-map(expr)              : Option(T')
filter(pred)                : Option(T)
or(other)                   : Option(T)
to-list                     : List(T)

2.2.5 Result(T, E)

is-ok                       : Bool
is-err                      : Bool
get                         : T               ; panics if Err
get-err                     : E               ; panics if Ok
get-or(default)             : T
map(expr)                   : Result(T', E)
map-err(expr)               : Result(T, E')
flat-map(expr)              : Result(T', E)
or(other)                   : Result(T, E)
to-option                   : Option(T)

Panic semantics (v0.3). Option.get / Result.get (the polymorphic unwrap, also written paren-free as value.get) panic on the empty case (None / Err); Result.get-err panics on Ok. All raise the one controlled panic signal handled by the live runtime — see lifecycle.md §7.2. Prefer get-or(default) outside a reducer.

2.2.6 Text

length                      : Int
is-empty                    : Bool
upper                       : Text
lower                       : Text
trim                        : Text
starts-with(s)              : Bool
ends-with(s)                : Bool
contains(s)                 : Bool
split(sep)                  : List(Text)
replace(from, to)           : Text
slice(start, end)           : Text
parse-int                   : Option(Int)
parse-float                 : Option(Float)

2.2.7 Int / Float

abs, neg, min(b), max(b), clamp(lo, hi)
show, to-float (Int), to-int (Float, truncated)

x.show is the common-to-all-types stringification method. Int / Float / Bool / variant / nominal all return .show : Text. Kumiki has no name called to-text.

2.2.8 Time

Time.now                    : Time
Time.parse(text)            : Option(Time)    ; ISO8601
plus(duration)              : Time
minus(duration)             : Time
diff(other)                 : Duration
format(pattern)             : Text            ; "yyyy-MM-dd HH:mm"

2.2.9 Duration

Duration.ms(n)              : Duration
Duration.s(n)               : Duration
Duration.m(n)               : Duration   ; min is also valid
Duration.h(n)               : Duration
Duration.d(n)               : Duration   ; days is also valid
to-ms                       : Int

Time / Duration are represented at runtime as a raw number of milliseconds. An operation like time.plus(Duration.h(72)) is expanded into a simple ms addition.

kumiki
fn isSoon(due: Time) -> Bool = due < now.plus(Duration.h(72))
fn elapsed(start: Time) -> Duration = now.diff(start)

2.3 Tile Primitive Elements

Kumiki's built-in tiles. They are semantic tags and are not literal translations of HTML tags.

2.3.1 Structural Elements

ElementRoleMain props
pagethe app's root screentitle, class
regiona named sectionaria-label, class
rowhorizontal layoutgap, align, justify
columnvertical layoutgap, align, justify
stackoverlapping placementalign
gridgridcols, gap
boxgeneric containerclass, style
cardcardclass
panelpanelclass
dividerdividerorientation
scrollscroll containerdirection, max-height

2.3.2 Text Elements

ElementRoleMain props
texttext displaystrike, bold, italic, size, color
headingheadinglevel (1-6)
linklinkto, external
codecodelang
markdownMarkdown rendering(content is the argument)

2.3.3 Media Elements

ElementRoleMain props
imageimagesrc, alt, width, height, loading
iconiconname, size
videovideosrc, controls, autoplay

2.3.4 Input Elements

ElementRoleMain props
buttonbuttontext, onClick, variant, disabled, loading
inputtext inputbind, placeholder, type (text/email/password/...), disabled
textareamulti-line inputbind, rows, placeholder
checkcheckboxvalue, onClick, label
radioradio buttonname, value, selected, onClick
selectselectbind, options (List of {label, value}), placeholder
slidersliderbind, min, max, step
switchtogglevalue, onClick

2.3.5 Forms

ElementRoleMain props
formform (delivered to the tile wrapping the form via ui.submit(WrapperTile))id, auto-complete, novalidate
labellabelfor
fieldsetfield setlegend
errorvalidation error displayfield

2.3.6 Lists / Tables

ElementRoleMain props
listlistordered
list-itemlist item
tabletable
table-headtable header
table-bodytable body
table-rowtable row
table-celltable cellcolspan, rowspan

2.3.7 Overlays

ElementRoleMain props
modalmodalopen, onClose, title
drawerdraweropen, onClose, side
tooltiptooltiptext, placement
popoverpopoveropen, onClose, placement
toasttoast notificationkind (info/success/warn/error), text

2.3.8 Feedback

ElementRoleMain props
spinnerspinnersize
progressprogress barvalue, max
skeletonskeletonkind (text/box/circle)

2.3.9 Control Elements

ElementRole
when(cond, tile)display tile if cond is true
if cond then tA else tBconditional branch
for x in coll tileiteration
route-outletoutput position for nested routes
link(to=...)route navigation link

2.3.10 Common Specification of props

Every tile accepts the following common props (built-in):

propTypeMeaning
classTextstyle class name
styleMap(Text, Text)inline style (minimal use recommended)
ariaMap(Text, Text)ARIA attributes
keyTextuniquely identifies an element within a for
test-idTextID for testing

2.4 Built-in Functions

2.4.1 ID Generation

TypeName.fresh()           : T            ; a new ID for a nominal type (UUIDv7)

2.4.2 Time

now                        : Time          ; the current time

2.4.3 Type Conversion

TypeName.parse(text)       : Option(T)    ; string parsing of a nominal type
TypeName.show(value)       : Text         ; the string representation of a value

2.4.4 Math

math.abs, math.min, math.max, math.clamp
math.floor, math.ceil, math.round
math.sqrt, math.pow, math.log, math.exp
math.random                : Float        ; callable only inside a reducer (treated as an effect)

2.4.5 String Formatting

fmt(template, ...args)     : Text         ; "Hello {0}, you have {1}"

When you concatenate Text with another type using +, the equivalent of show is called automatically.

2.4.6 Debugging Aids

trace(label, value)        : T            ; records to the episode log with a label, returns the value as is
panic(message)             : never        ; stops the program (inside a reducer only)

2.5 Standard Capabilities

The standard set of capabilities that can be declared in app.caps:

capabilityUse
http.get, http.post, http.put, http.patch, http.deleteHTTP requests
storage.read, storage.writelocalStorage
session.read, session.writesessionStorage
indexed.read, indexed.writeIndexedDB
nav.push, nav.replace, nav.backroute navigation
clipboard.read, clipboard.writeclipboard
notification.showdesktop notifications
analytics.sendsending measurement events
log.writelog output
crypto.random, crypto.hashcryptography
media.camera, media.microphonemedia devices
geo.readlocation information
socket.connect, socket.sendWebSocket

Writing a capability in app.caps that is neither standard nor registered is a compile error (E0302).

Registering custom capabilities (kumiki.caps.json)

A project can extend the accepted set with a kumiki.caps.json manifest placed in the same directory as the .kumiki file:

json
{
  "capabilities": [
    { "name": "telemetry.track", "description": "..." }
  ]
}

Each entry is a capability name in group.action form (lowercase, dot-separated) — either a bare string or an object with a description. A registered name is then accepted in app.caps, and an effect bound to it (effect track cap=telemetry.track …) becomes emittable and is dispatched at the capability boundary — and is mockable in scenarios exactly like a standard effect. A name already in the standard set must not be re-declared.

This is a capability-boundary registration: a declarative manifest, not new syntax or arbitrary code — consistent with Kumiki's non-goal of macro/DSL extension. Working example: packages/examples/features/27-custom-capability.kumiki (+ its kumiki.caps.json).

Supplying the implementation (host capability providers)

A custom capability has no built-in implementation — the manifest only makes the name declarable. The host that mounts the app supplies the implementation at the capability boundary, via mount's options:

ts
import { mount } from "@kumikijs/runtime";
import { stripe } from "./stripe.ts"; // any npm library lives here, host-side

mount(App, root, {
  providers: {
    // keyed by capability name; receives the effect's (map-request-mapped) input
    "payments.charge": async (input) => {
      try {
        const r = await stripe.charges.create(input as ChargeReq);
        return { kind: "ok", value: { id: r.id } };
      } catch (e) {
        return { kind: "err", value: { message: String(e) } };
      }
    },
  },
});

A provider returns an EffectResult ({kind:"ok"|"err", value}), sync or async; a thrown error is normalized to err. This is Kumiki's inbound ecosystem seam: arbitrary JS / npm libraries are confined to the provider, behind a typed, mockable, episode-tracked boundary, so the language core needs no FFI. If an effect on a custom capability fires with no provider registered, it resolves to err {message: "Capability <name> has no provider"}. In a kumiki run scenario, the scripted effect outcome overrides the provider (the runner mocks at the same boundary), so tests stay hermetic.

The compiled bundle auto-mounts to #root; a host embedding the bundle can register providers by assigning globalThis.__kumikiProviders before the module loads.

Overriding a standard capability. A provider may also be registered for a standard capability (http.*, storage.*, nav.*, notification.show, log.write, …) — every effect invoke consults caps.provider(cap) before its built-in implementation. This lets a host swap the HTTP transport (axios / ofetch), inject auth headers, plug in a framework router, or replace the toast UI, without changing the Kumiki source. The provider receives the effect's (already map-request-mapped) request; when none is registered the built-in behavior runs unchanged.

Unhandled effect errors are surfaced, never silent. An effect that resolves to err is delivered to every matching .err reducer. If a program wires no .err reducer for that effect, the dropped error is reported via console.error ([kumiki] effect "<name>" returned an error with no .err reducer: …), so the verification tiers (smoke / runScenario, which capture console.error) flag it — consistent with the live-panic model (lifecycle.md §7.2). A failed capability must never look like a no-op: the storage-unavailable case (opaque-origin sandbox, private mode) is exactly this — storage.read / storage.write return err, and an app handling only .ok would otherwise silently do nothing. The default contract is therefore err plus a surfaced report; a program opts into handling (or deliberately ignoring) the error by wiring an .err reducer — even an empty one. An in-memory storage fallback is not the default behavior (it would paper over this contract); a host that wants one supplies it explicitly via a storage.* provider.


2.6 Standard Effects

The standard effect corresponding to each capability. If the capability is in app.caps, it is automatically usable.

→ For the detailed specification, see HTTP / Storage.

2.6.1 Navigation

kumiki
effect navigate    cap=nav.push     in={path: Text, params: Map(Text, Text)}  out=Unit
effect navigate-replace cap=nav.replace in={path: Text, params: Map(Text, Text)} out=Unit
effect navigate-back   cap=nav.back  in=Unit  out=Unit

2.6.2 Toast

kumiki
effect toast       cap=notification.show  in={kind: Text, text: Text}  out=Unit

2.6.3 Log

kumiki
effect log         cap=log.write    in={level: Text, message: Text, data: Map(Text, Text)}  out=Unit

2.7 Frequently Wanted Types Such as Numeric/Currency Are Intentionally Not Provided

Types such as Money, Percent, and Decimal are defined on the application side using nominal. Kumiki is unopinionated.

kumiki
type Cents = nominal Int where positive
type Yen   = nominal Int where positive