Skip to content

Calling Actions

Run an action and read what it returns. It works the same on either side of the connection.

Once your runtime is set up, calling an action looks the same on client or server. It doesn’t matter whether the action runs right here or on a peer.

The everyday case is to run the action and get its output back. It throws if the action fails, whether that’s a declared error or a connection problem.

const output = await act_user.action.getUser
.request({ userId: "u_123" })
.runToOutput();
console.log(output); // { id: "u_123", name: "Alice" }

If you want to track progress or be able to cancel, hold onto the RunningAction and wait for its full result:

const running = await act_user.runAction(
act_user.action.getUser.request({ userId: "u_123" }),
);
const result = await running.waitForResultPayload();
console.log(result.output);

Any error you listed with .throws(domain, ids?) comes back fully typed. Use castNiceError and the domain’s checks to narrow down which one it is:

import { castNiceError } from "@nice-code/error";
try {
const output = await act_user.action.getUser.request({ userId }).runToOutput();
} catch (e) {
const error = castNiceError(e);
if (err_user.isExact(error) && error.hasId("not_found")) {
console.log("User not found:", error.getContext("not_found").userId);
}
}

This request(...).runToOutput() shape is the same whether the action runs locally, in a handler on this runtime, or remotely over a connection to a peer. The runtime works out where it needs to go.

runToResult() hands you the outcome as a value instead of throwing. Rather than wrapping every call in try/catch, you check expected, meaning this action said it could throw this error:

const result = await act_user.action.getUser.request({ userId }).runToResult();
if (result.ok) use(result.output);
else if (result.expected) handleDeclared(result.error);
else report(result.error); // an error this action didn't declare, or an unexpected crash

Use this form when declared failures are part of ordinary control flow; runToOutput() remains the shortest everyday form when failure should throw. See Error Handling → for the full model: expected vs isUnhandled, the typed isExpectedError guard, and how it all shows up in devtools.

Actions defined with .reliable() are called exactly the same way. Two things differ. The promise settles later, since it retries across reconnects instead of failing fast. And .run() / .runToOutput() take an optional per-call streamKey, which keeps independent streams, such as one per room or per entity, separately ordered.