Skip to main content

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.

Availability

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:

ScopeIsolationCapacity
"global"Shared across all installs100 MB
"install"Per overlay install10 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.

PropertyAvailable
ctx.db.getYes
ctx.db.listYes
ctx.db.setNo - throws
ctx.fetchNo - throws
ctx.secretsYes (read-only)
ctx.realtimeNo - throws
ctx.identityYes

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__triggerSync/AsyncTimeout
command(name, handler)"command"Command nameSync500ms
keyword(word, handler)"keyword"Keyword stringAsync10s
pattern(regex, handler)"pattern"Regex stringAsync10s
event(type, handler)"event"Event typeAsync10s
timer(name, handler)"timer"Timer nameAsync10s
moderate(handler)"moderate"N/ASync500ms

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;
}