Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

API reference

wasmkeep:actor@0.2.0, the interface between WasmKeep and every actor, in any language. Each entry gives the WIT definition, then its name in each SDK (Rust, TypeScript, Python, C, Zig). The SDKs add conveniences (typed rows, JSON, broadcast); see each SDK’s README.

The wasmkeep:actor package: everything an actor can call on the host, and everything the host calls on an actor.

An actor is addressed as tenant/class/name. One component implements one class; each distinct name is an independent instance with its own SQLite database. The host delivers events (requests, alarms) to an instance one at a time, in arrival order: an actor never runs concurrently with itself.

Versioning: 0.x minor versions may break guests; every change comes with a migration note (docs/adr). See docs/adr/0002-wasmkeep-actor-wit-v0.1.md and docs/adr/0003-websockets-wit-v0.2.md.

What an actor implements

HTTP requests arrive through the standard wasi:http/incoming-handler export (handle), which the TypeScript, Python, C and Zig SDKs expose as their fetch handler and the Rust SDK as http::Guest::handle. The world adds:

on-init

on-init: func() -> result<_, string>;

Runs before the first event an instance handles with this component version: once for a new actor, and again after each deploy. Use it to create or migrate tables. An error fails the event; it is retried with the next event.

RustTypeScriptPythonCZig
Actor::on_initinit()Actor.initwk_initpub fn init

on-alarm

on-alarm: func() -> result<_, string>;

Runs when the pending alarm is due. An error or trap schedules a retry.

RustTypeScriptPythonCZig
Actor::on_alarmalarm()Actor.alarmwk_alarmpub fn alarm

on-websocket-open

on-websocket-open: func(conn: u64, path: string) -> result<_, string>;

A client upgraded a GET to path (the part after the actor’s name, with its query). Return an error to refuse it: the connection is then closed with code 1008 and the error as reason. It runs before the host answers the upgrade (101), so the client is connected only once the actor has accepted it. What the actor sends meanwhile waits in the socket’s queue, with its usual cap (backpressure past 4 MiB). A client that leaves before the 101 (or a node relaying for it, which gives up after 30 s) still gets this event if it was already queued, followed by on-websocket-close with code 1006.

RustTypeScriptPythonCZig
Actor::on_websocket_openwebsocket.open()Actor.websocket_openwk_ws_openpub fn wsOpen

on-websocket-message

on-websocket-message: func(conn: u64, msg: message);

A message arrived on one of this actor’s connections.

RustTypeScriptPythonCZig
Actor::on_websocket_messagewebsocket.message()Actor.websocket_messagewk_ws_messagepub fn wsMessage

on-websocket-close

on-websocket-close: func(conn: u64, code: u16, reason: string);

A connection closed (by the client, by close, or by the host).

RustTypeScriptPythonCZig
Actor::on_websocket_closewebsocket.close()Actor.websocket_closewk_ws_closepub fn wsClose

Interface storage

The actor’s own SQLite database. Nothing else can read or write it.

Each call runs one statement. Outside an explicit transaction every statement commits on its own. BEGIN … COMMIT across several calls within one event is allowed; a transaction still open when the event ends (or traps) is rolled back. A statement that fails in a way that rolls the open transaction back (it ran past its deadline or the event’s SQL CPU budget, say) ends that transaction: nothing of it is kept, and every later storage statement of the event is refused with not-allowed (but a ROLLBACK, which is what already happened, succeeds).

exec

exec: func(sql: string, params: list<value>) -> result<u64, sql-error>;

Runs a statement that returns no rows; returns the number of rows changed. Returns once the write is committed to the local database.

RustTypeScriptPythonCZig
storage::execstorage.execstorage.execwk_execwk.exec

exec-durable

exec-durable: func(sql: string, params: list<value>) -> result<u64, sql-error>;

Like exec, but returns only once the write is durable: replicated to the bucket when the node has one, fsynced locally when it does not. Inside a transaction it takes only COMMIT (or END), and makes the whole transaction durable; any other statement there is refused with not-allowed before it runs. Outside one it does not take a statement that opens or controls a transaction (BEGIN, SAVEPOINT): run those with exec.

RustTypeScriptPythonCZig
storage::exec_durablestorage.execDurablestorage.exec_durablewk_exec_durablewk.execDurable

query

query: func(sql: string, params: list<value>) -> result<rows, sql-error>;

Runs a statement and returns its rows.

RustTypeScriptPythonCZig
storage::querystorage.querystorage.querywk_querywk.query

delete-self

delete-self: func();

Deletes this actor: its database is removed once the current event ends, and its pending alarm is cleared. The next event to this address starts a fresh actor.

RustTypeScriptPythonCZig
storage::delete_selfstorage.deleteSelfstorage.delete_selfwk_delete_selfwk.deleteSelf

Interface alarms

One pending alarm per actor. Alarms survive restarts and fire at least once: an on-alarm that fails or traps is retried, so handlers must be idempotent.

set-alarm

set-alarm: func(at: u64);

Schedules on-alarm at at (milliseconds since the Unix epoch), replacing any pending alarm. A time in the past fires as soon as possible.

RustTypeScriptPythonCZig
alarms::set_alarmalarms.setalarms.setwk_alarm_setwk.setAlarm

get-alarm

get-alarm: func() -> option<u64>;

The pending alarm time, if any.

RustTypeScriptPythonCZig
alarms::get_alarmalarms.getalarms.getwk_alarm_getwk.getAlarm

clear-alarm

clear-alarm: func();

Cancels the pending alarm, if any.

RustTypeScriptPythonCZig
alarms::clear_alarmalarms.clearalarms.clearwk_alarm_clearwk.clearAlarm

Interface actors

Calls between actors of the same tenant.

request (record)

A fully buffered request, delivered to the callee’s wasi:http/incoming-handler like any other request.

Field
method: string
path: stringPath and query, starting with /.
headers: list<tuple<string, string>>
body: list<u8>

response (record)

Field
status: u16
headers: list<tuple<string, string>>
body: list<u8>

call-error (variant)

Case
invalid-address(string)The address is not class/name, or the class does not exist.
reentrantThe call would re-enter an actor already waiting in this call chain (including the caller itself), which would deadlock.
too-largeThe request or response exceeded the body limit (16 MB).
timeoutThe callee did not answer within the event’s wall-clock limit.
failed(string)The callee trapped, or the host could not deliver the call.

actor-address (record)

An actor’s address. Within a tenant, class/name is what call takes; the name is decoded (any text without / or control characters).

Field
tenant: string
class: string
name: string

call

call: func(address: string, request: request) -> result<response, call-error>;

Sends request to the actor at address (class/name, within the caller’s tenant) and waits for its response. The caller’s own event stays in progress meanwhile: other events to the caller wait.

The callee sees x-wasmkeep-caller: <tenant>/<class>/<name>, the name percent-encoded (every byte but ASCII letters, digits and -._~), so room 1 arrives as room%201. The host removes that header from requests coming from outside, so the callee can trust it.

RustTypeScriptPythonCZig
actors::callactors.callactors.callwk_callwk.call

self-address

self-address: func() -> actor-address;

This actor’s own address: an actor does not otherwise know its name or class (requests arrive with /actor/<class>/<name> removed, and alarms carry nothing). For links to itself, for telling other actors who it is, and for anything keyed by its name.

RustTypeScriptPythonCZig
actors::self_addressactors.self()actors.self_addresswk_self_addresswk.selfAddress

Interface websockets

WebSocket connections to this actor.

The host accepts upgrades on the actor’s path and owns the sockets, so a connection stays open while the actor hibernates; the next message wakes it. A connection id is only valid in the actor it was opened on.

message (variant)

A WebSocket message.

Case
text(string)
binary(list<u8>)

send-error (variant)

Case
unknown-connectionThe connection is closed, or belongs to another actor.
backpressureThe client is not reading fast enough; the message was dropped.

send

send: func(conn: u64, msg: message) -> result<_, send-error>;

Queues msg on the connection.

RustTypeScriptPythonCZig
websockets::sendsockets.sendsockets.sendwk_send, wk_send_binarywk.send, wk.sendBinary

close

close: func(conn: u64, code: u16, reason: string);

Closes the connection with a close code (1000 = normal) and reason.

RustTypeScriptPythonCZig
websockets::closesockets.closesockets.closewk_closewk.close

connections

connections: func() -> list<u64>;

This actor’s open connections, oldest first.

RustTypeScriptPythonCZig
websockets::connectionssockets.connectionssockets.connectionswk_connectionswk.connections

Interface env

Configuration and secrets the operator gives an actor class: an API key, a webhook URL, a feature flag. Values come, by name, last wins, from the deployed version’s plain configuration (wasmkeep deploy --config CLASS=FILE), the app’s secrets sealed in the bucket (wasmkeep secrets set), and files on the node (wasmkeep serve --env CLASS=FILE), never from the component. They are the same for every actor of the class and read-only, and change only between events, never during one: when a deploy or a secrets change lands (the next event runs in a new instance) or the node restarts.

get

get: func(name: string) -> option<string>;

The value of name, or none if it is not set.

RustTypeScriptPythonCZig
env::getenv.getenv.getwk_envwk.env

Interface types

Types shared by several interfaces.

value (variant)

A SQL value, mapping one to one onto SQLite’s storage classes.

Case
null
integer(s64)
real(f64)
text(string)
blob(list<u8>)

rows (record)

The result set of a query: column names, then one list of values per row, in column order.

Field
columns: list<string>
values: list<list<value>>

sql-error-kind (enum)

Why a statement failed.

Case
syntaxThe SQL did not parse, or referred to a missing table or column.
constraintA constraint failed (UNIQUE, NOT NULL, CHECK, FOREIGN KEY).
bindParameter count or types do not match the statement.
multiple-statementsThe text held more than one statement; send one at a time.
quota-exceededThe database reached its size cap (default 1 GB); reads still work.
not-allowedThe statement tried something actors may not do (ATTACH, a PRAGMA outside the allowed set, loading extensions).
internalThe host failed (disk, I/O). The event should fail.

sql-error (record)

Field
kind: sql-error-kind
message: stringHuman-readable detail, usually SQLite’s own message.

Also available

The actor world includes the standard wasi:http/proxy world: outbound HTTP (wasi:http/outgoing-handler), clocks, randomness and stdio (what an actor writes to stdout and stderr goes to the node’s log, line by line, within --guest-log-rate). The wasi:cli interfaces most standard libraries import are present but empty: no arguments, environment or terminal. wasi:filesystem and wasi:sockets, which many toolchains import whether or not the code uses them, are linked but reach nothing: no directory is opened to the guest, and every socket and name lookup is refused. A component importing an interface the host does not provide is refused at deploy time.