Skip to content

Calling Actions

Run actions and read their output — the same on any side of the connection.

Once your runtime is set up, calling an action looks the same on any side — client or server — and it doesn’t matter whether the action runs right here or over on a peer. You call it the same way either way.

The everyday case: 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 (a handler in this runtime) or remotely (over a connection to a peer) — the runtime figures out where it needs to go.

runToResult() hands you the outcome as a value instead of throwing. You check expected (did this action say it could throw this error?) rather than wrapping every call in try/catch:

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

This is the recommended path — see Error Handling → for the full model (expected vs isUnhandled, the typed isExpectedError guard, and how it surfaces in devtools).

Actions defined with .reliable() call exactly the same way — the only differences are when the promise settles (it retries across reconnects instead of failing fast) and an optional per-call streamKey on .run() / .runToOutput() to keep independent streams (one per room, per entity) separately ordered.