SDK Server Reference
Complete reference for @zaflun/lumio-sdk/server - the server-side library used in server/functions.ts.
Import
import { action, query, v } from "@zaflun/lumio-sdk/server";
Defining functions
action
Defines a server function that can be called by the client via useLumioAction() or useMutation(). Actions have full access to storage, secrets, egress, and real-time push.
function action<TArgs, TReturn>(definition: {
args: ArgSchema<TArgs>;
handler: (ctx: ActionContext, args: TArgs) => Promise<TReturn>;
}): Action<TArgs, TReturn>;
export const incrementScore = action({
args: { amount: v.number() },
handler: async (ctx, args) => {
const current = (await ctx.db.get<number>("install", "score")) ?? 0;
const next = current + args.amount;
await ctx.db.set("install", "score", next);
return { score: next };
},
});
query
Defines a read-only server function. Queries cannot write to storage, push real-time events, or call external APIs. They are intended for fetching state and may be cached by the platform.
function query<TArgs, TReturn>(definition: {
args: ArgSchema<TArgs>;
handler: (ctx: QueryContext, args: TArgs) => Promise<TReturn>;
}): Query<TArgs, TReturn>;
export const getScore = query({
args: { userId: v.string() },
handler: async (ctx, args) => {
return ctx.db.get<number>("user", args.userId, "score") ?? 0;
},
});
ActionContext
The ctx parameter in action() handlers.
Server-function handlers do not have working ctx.db, ctx.fetch, ctx.secrets, ctx.cache or ctx.defer() yet: in this path the calls return empty results (ctx.secrets.get returns null) or throw fetch not available. Bot-module handlers have working ctx.cache, ctx.secrets, ctx.fetch and ctx.defer() (see Bot Module Context); ctx.db is not available anywhere yet.
ctx.db - Storage
Reads and writes extension storage, namespaced by scope.
// Read a value
const value = await ctx.db.get<T>(scope, ...keys);
// Write a value
await ctx.db.set(scope, ...keys, value);
// Delete a value
await ctx.db.delete(scope, ...keys);
// List all keys in a scope (with optional prefix)
const keys = await ctx.db.list(scope, prefix?);
Scopes:
| Scope | Isolation | Capacity |
|---|---|---|
"global" | Shared across all installs | 100 MB |
"install" | Per overlay install | 10 MB |
ctx.fetch - External HTTP
Calls an external HTTPS endpoint. Subject to the egress.allowHosts allowlist declared in lumio.config.json.
const res = await ctx.fetch("https://api.example.com/data");
const json = await res.json();
Has the same interface as the standard fetch() API. Restrictions:
- HTTPS only
- No private IP ranges or localhost
- Maximum 10 calls per action invocation
- 30-second timeout per request
ctx.secrets - Secret access
Reads secrets stored via lumio env set or the dashboard. Secrets belong to the extension; every install reads the same values.
const apiKey = await ctx.secrets.get("MY_API_KEY"); // string, or null if not set
Today only bot-module handlers read real values (see Bot Module Context); in server functions get returns null. Secret values are never logged or returned to the client.
ctx.realtime - Push real-time events
Pushes a message to every rendered surface of this install (editor and layer).
ctx.realtime.push("score:updated", { score: 42 });
Surfaces receive it with useRealtime:
useRealtime<{ score: number }>("score:updated", (payload) => setScore(payload.score));
Topic: 1-64 characters of A-Z a-z 0-9 _ . : -, starting with a letter or digit. Payload: at most 16 KiB of JSON. At most 32 pushes per handler run, and per install and minute at most 300 pushes and 1 MiB of published messages (topic plus payload, serialized); pushes over either cap are dropped. Pushes are sent only when the handler does not throw.
ctx.cache - Scoped cache
Scoped Redis key-value cache. Faster than ctx.db for temporary data that can be lost.
await ctx.cache.set("counter", 42, 3600); // set with TTL in seconds
const val = await ctx.cache.get("counter"); // get value
await ctx.cache.increment("counter", 1); // atomic increment
await ctx.cache.delete("counter"); // delete key
Keys are scoped per install (one Redis hash, lumio:ext:cache:\{install_id\}). TTL range 1s–24h, default 1h; at most 256 keys of 4 KB per install. Works in bot-module handlers only. See Cache & Background Jobs.
ctx.defer - Background work
Queue a function to run after the handler returns - useful for background DB writes after a fast cache read.
ctx.defer(async () => {
await ctx.db.set("install", "last_run", Date.now());
});
Max 5 deferred calls per handler invocation, 10 seconds for all of them together. Works in bot-module handlers only. See Cache & Background Jobs.
QueryContext
The ctx parameter in query() handlers. A subset of ActionContext - read-only access only.
| Property | Available |
|---|---|
ctx.db.get | Yes |
ctx.db.list | Yes |
ctx.db.set | No - throws |
ctx.fetch | No - throws |
ctx.secrets | Yes (read-only) |
ctx.realtime | No - throws |
ctx.identity | Yes |
Validation - v
The v object provides runtime argument validators. All args must be declared with v - undeclared fields are stripped before the handler runs.
// Primitives
v.string()
v.number()
v.boolean()
v.null_()
// Modifiers
v.optional(v.string()) // string | undefined — field may be absent
v.nullable(v.string()) // string | null
// Collections
v.array(v.string())
v.object({ name: v.string(), count: v.number() })
// Union
v.union([v.string(), v.number()])
// Literal
v.literal("active")
Validation happens before the handler is called. Invalid args return a structured error to the caller without invoking the handler.
Exports from server/functions.ts
Every named export from server/functions.ts that is an action() or query() result is automatically registered with the platform at deploy time. No additional registration step is needed.
// server/functions.ts
export const getScore = query({ ... }); // registered as "getScore"
export const setScore = action({ ... }); // registered as "setScore"
// Not registered — not an action or query
export const MULTIPLIER = 2;
The exported name becomes the actionName / queryName used on the client.
Bot module handlers
Bot module extensions (category: "bot_module") use a separate set of handler wrappers for chat interaction. Each wrapper sets a __type discriminant on the exported function, following the same pattern as query() and action().
Handler type reference
| Wrapper | __type | __trigger | Sync/Async | Timeout |
|---|---|---|---|---|
command(name, handler) | "command" | Command name | Sync | 500ms |
keyword(word, handler) | "keyword" | Keyword string | Async | 10s |
pattern(regex, handler) | "pattern" | Regex string | Async | 10s |
event(type, handler) | "event" | Event type | Async | 10s |
timer(name, handler) | "timer" | Timer name | Async | 10s |
moderate(handler) | "moderate" | N/A | Sync | 500ms |
Signatures
function command(
name: string,
handler: (ctx: BotModuleContext, args: string[]) => Promise<HandlerResponse | null>
): CommandDefinition;
function keyword(
word: string,
handler: (ctx: BotModuleContext, message: ChatMessage) => Promise<HandlerResponse | null>
): KeywordDefinition;
function pattern(
regex: string,
handler: (ctx: BotModuleContext, message: ChatMessage, matched: string) => Promise<HandlerResponse | null>
): PatternDefinition;
function event(
eventType: string,
handler: (ctx: BotModuleContext, evt: Record<string, unknown>) => Promise<HandlerResponse | null>
): EventDefinition;
function timer(
name: string,
handler: (ctx: BotModuleContext) => Promise<HandlerResponse | null>
): TimerDefinition;
function moderate(
handler: (ctx: BotModuleContext, message: ChatMessage) => Promise<ModerationResponse>
): ModerateDefinition;
BotModuleContext
See Bot Module Context for the full API reference. In bot-module handlers ctx.cache, ctx.secrets, ctx.fetch and ctx.defer() work; ctx.db throws "not available yet".
HandlerResponse
interface HandlerResponse {
reply?: string;
actions?: ActionRequest[];
}
interface ActionRequest {
action: string;
params: Record<string, unknown>;
}
ModerationResponse
interface ModerationResponse {
block: boolean;
action?: "delete" | "timeout" | "ban";
duration?: number;
reason?: string;
}
// defineModerate(handler) return value
interface ModerateResult {
action: "allow" | "delete" | "timeout" | "warn" | "ban";
duration?: number;
reason?: string;
message?: string;
}
Lumio acts on the action field only. allow (or no action) lets the message through; delete, timeout, ban and warn become the chat actions chat:delete, chat:timeout (duration seconds, default 600), chat:ban and chat:send (message, else reason). The manifest must declare that action in permissions, or it is refused. block is not read: { block: true } without an action does nothing. The handler has a 500ms budget; a timeout or error lets the message through. See Bot Modules → Moderation.
ChatMessage
interface ChatMessage {
text: string;
platformMessageId: string;
}