useMutation
Call a server-side mutation or action function. Returns both a synchronous (mutate) and an async (mutateAsync) interface.
Signature
const { mutate, mutateAsync, data, isLoading, error } = useMutation<T>(functionName: string);
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
functionName | string | Yes | Name of the exported server mutation or action function |
Return value
| Property | Type | Description |
|---|---|---|
mutate | (args?: Record<string, unknown>) => void | Fire-and-forget - does not return a Promise |
mutateAsync | (args?: Record<string, unknown>) => Promise<T> | Async variant - returns a Promise that resolves with the result |
data | T | null | Result of the last successful call |
isLoading | boolean | true while the mutation is in flight |
error | string | null | Error message if the last call failed |
Example: Simple fire-and-forget
import { Button, useMutation } from "@zaflun/lumio-sdk";
function RevealButton({ ruleId }: { ruleId: string }) {
const { mutate: reveal, isLoading } = useMutation("revealRule");
return (
<Button
label={isLoading ? "Revealing..." : "Reveal"}
onClick={() => reveal({ id: ruleId })}
disabled={isLoading}
/>
);
}
Example: Async with refetch
import { Button, useMutation, useQuery } from "@zaflun/lumio-sdk";
function AddRuleButton() {
const { data: rules, refetch } = useQuery("getRules");
const { mutateAsync: addRule, isLoading, error } = useMutation("addRule");
const handleAdd = async () => {
try {
await addRule({ text: "New challenge rule" });
refetch(); // refresh list after successful add
} catch (e) {
// error is also available as useMutation().error
console.error("Failed to add rule:", e);
}
};
return (
<>
{error && <Text content={error} variant="muted" />}
<Button
label={isLoading ? "Adding..." : "Add rule"}
onClick={handleAdd}
disabled={isLoading}
/>
</>
);
}
Example: Handler action
Use action() for server-side work with the installation's data. Handler-based server functions cannot make outbound requests; see External APIs.
// server/functions.ts
import { action, v } from "@zaflun/lumio-sdk/server";
export const clearVotes = action({
args: { pollId: v.string() },
handler: async (ctx, args) => {
const votes = await ctx.db.list("votes", [
{ field: "pollId", op: "eq", value: args.pollId },
]);
for (const vote of votes) await ctx.db.delete("votes", vote._id);
return { cleared: votes.length };
},
});
const { mutateAsync: clearVotes, isLoading } = useMutation("clearVotes");
const resetPoll = async () => {
const result = await clearVotes({ pollId: "current" });
// use result.cleared...
};
Result
For a declarative function, mutateAsync resolves with the written row - the full row as JSON including _id, _created_at and _updated_at (insertRow / patchRow), or { _id } of the deleted row (deleteRow) - and with null when no row matched (for example a patchRow on an _id that does not exist).
editorOnly mutations
Mutations declared with editorOnly: true in the server function can only be called from the editor surface (the Lumio dashboard). Calling them from your extension running in an OBS overlay or widget rejects with Editor-only function called outside editor. Lumio takes the surface from the host's credential, so your extension cannot claim to be the editor:
export const deleteRule = deleteRow("rules", arg("id"), { editorOnly: true });
Errors
A failed call rejects mutateAsync (and sets error) with the API's message. The underlying JSON-RPC error carries one of the SERVER_FUNCTION_ERRORS codes from @zaflun/lumio-protocol:
| Code | Name | When |
|---|---|---|
-32602 | INVALID_REQUEST | the function name is not an export name, or the arguments are not an object |
-32001 | UNAVAILABLE | no installation (designer preview), or a credential that cannot call server functions |
-32002 | DENIED | an editorOnly function outside the editor, or the signed-in user lacks permission |
-32003 | UNKNOWN_FUNCTION | the installed version exports no function of that name, or it is a query |
-32004 | INVALID_PARAMS | a missing or mistyped argument, an invalid row id or column value |
-32005 | FAILED | rate limit, no data schema, storage quota or timeout |
Handler-based functions (mutation(), action()) run on the Bot Module Worker: the params become the handler's args, and data is the handler's return value. See Handler-based Functions. Declarative functions (insertRow, patchRow, deleteRow) run on the API. Both work on every host.
Rate limits
| Function type | Limit |
|---|---|
insertRow / patchRow / deleteRow | 200 calls/minute per installation |
mutation() | 50 calls/minute per installation |
action() | 20 calls/minute per installation |
Notes
mutate()andmutateAsync()both setisLoadinganderroron the returned object- Only one mutation of the same name can be in flight at a time - subsequent calls queue until the previous completes
- If
mutateAsyncthrows, the error is also stored in theerrorproperty