The schema is the source of truth for everything callable in a Saikuro system. The runtime uses it to validate calls, enforce capabilities, and route correctly. The codegen tool uses it to emit typed client stubs.
Schema Structure
Schemas are JSON (or MessagePack). Here is a complete example:
{
"version": 1,
"namespaces": {
"math": {
"functions": {
"add": {
"args": [{ "name": "a", "type": { "kind": "primitive", "type": "i64" }, "optional": false }],
"returns": { "kind": "primitive", "type": "i64" },
"visibility": "public",
"capabilities": [],
"idempotent": true,
"doc": "Add two integers."
}
}
}
},
"types": {
"User": {
"fields": {
"id": "string",
"name": "string"
}
}
}
}
Namespaces
Functions live inside namespaces. Each namespace is owned by exactly one provider.
math.add
auth.validate_token
images.resize
Type Descriptors
The type field in arguments and return values uses Saikuro’s type descriptor system:
| Example | Meaning |
|---|---|
{ "kind": "primitive", "type": "i32" } |
32-bit signed integer |
{ "kind": "primitive", "type": "string" } |
UTF-8 string |
{ "kind": "list", "item": { ... } } |
List of items |
{ "kind": "map", "key": { ... }, "value": { ... } } |
Key-value map |
{ "kind": "optional", "inner": { ... } } |
Nullable value |
{ "kind": "named", "name": "User" } |
Reference to a custom type |
{ "kind": "stream", "item": { ... } } |
Stream of items |
{ "kind": "channel", "send": { ... }, "recv": { ... } } |
Bidirectional channel |
Primitive types bool, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, string, bytes, any, unit.
TypeScript Provider Type Builder
The TypeScript adapter provides a t builder for constructing type descriptors at registration:
import { SaikuroProvider, t } from "@nisoku/saikuro";
const provider = new SaikuroProvider("math");
provider.register("add", (a: number, b: number) => a + b, {
args: [
{ name: "a", type: t.i32() },
{ name: "b", type: t.i32() },
],
returns: t.i32(),
doc: "Add two integers.",
idempotent: true,
});
Available builders:
t.bool(),t.i32(),t.i64(),t.f32(),t.f64(),t.string(),t.bytes(),t.any(),t.unit()t.list(item),t.map(key, value),t.options(inner),t.named(name)t.stream(item),t.channel(send, recv)
Capabilities
Functions can require capability tokens:
"delete_user": {
"capabilities": ["admin.write"]
}
Callers present their token on connect. If a capability is missing, the call is rejected before it reaches the provider.
In dev mode you can configure the runtime to skip capability enforcement.
Visibility
Three levels:
| Level | Who can call |
|---|---|
public |
Any caller, any machine |
internal |
Callers on the same machine |
private |
Same process only |
Private functions are never exposed beyond the process boundary.
Schema Announcement (Dev Mode)
In development, providers announce their schema automatically when they call serve():
// Schema is announced automatically
await provider.serve("unix:///tmp/saikuro.sock");
The runtime stores the schema and shares it with all connected callers. No codegen step needed during development.
You can also extract a schema statically using your adapter’s CLI tool:
npx saikuro-schema my-namespace provider.ts # TypeScript
saikuro-schema --namespace my-namespace provider.py # Python
Schema Announcement (Production)
In production, pass a frozen schema to the runtime:
saikuro-runtime --schema ./schema.json
Dynamic announcement is disabled. Providers that announce a mismatched schema are rejected.