Skip to main content

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​

ParameterTypeRequiredDescription
functionNamestringYesName of the exported server mutation or action function

Return value​

PropertyTypeDescription
mutate(args?: Record<string, unknown>) => voidFire-and-forget - does not return a Promise
mutateAsync(args?: Record<string, unknown>) => Promise<T>Async variant - returns a Promise that resolves with the result
dataT | nullResult of the last successful call
isLoadingbooleantrue while the mutation is in flight
errorstring | nullError 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:

CodeNameWhen
-32602INVALID_REQUESTthe function name is not an export name, or the arguments are not an object
-32001UNAVAILABLEno installation (designer preview), or a credential that cannot call server functions
-32002DENIEDan editorOnly function outside the editor, or the signed-in user lacks permission
-32003UNKNOWN_FUNCTIONthe installed version exports no function of that name, or it is a query
-32004INVALID_PARAMSa missing or mistyped argument, an invalid row id or column value
-32005FAILEDrate 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 typeLimit
insertRow / patchRow / deleteRow200 calls/minute per installation
mutation()50 calls/minute per installation
action()20 calls/minute per installation

Notes​

  • mutate() and mutateAsync() both set isLoading and error on 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 mutateAsync throws, the error is also stored in the error property