Skip to content

Handlers

Build local handlers that run actions in the current process.

A handler is the actual code that runs when an action is called. You write one, register it on the runtime, and it answers incoming calls. There are three ways to write one — use whichever reads best for you.

server.ts
import { createLocalHandler } from "@nice-code/action";
const userHandler = createLocalHandler().forDomainActionCases(act_user, {
getUser: async (action) => {
const user = await db.users.find(action.input.userId);
if (!user) throw err_user.fromId("not_found", { userId: action.input.userId });
return user;
},
updateName: async (action) => {
await db.users.update(action.input.userId, { name: action.input.name });
return { success: true };
},
});

Each handler gets the whole action: action.input is typed, and action.context carries routing details like originClient — who made the call (see Bi-directional).

const userHandler = createLocalHandler()
.forAction(act_user.action.getUser, async ({ input }) => db.users.find(input.userId));
const userHandler = act_user.wrapAsLocalHandler({
getUser: async ({ userId }) => { /* ... */ },
updateName: async ({ userId, name }) => { /* ... */ },
});

Here each handler gets the input directly (already destructured) instead of the full action object — the shortest form, for when you don’t need action.context.

wrapAsPartialLocalHandler works like wrapAsLocalHandler, but you only implement some of a domain’s actions. This is handy for a client that answers a few calls itself and lets the rest travel on to the server.

const partial = act_user.wrapAsPartialLocalHandler({
getUser: async ({ userId }) => cache.get(userId), // the rest are forwarded over the connection
});

Once you’ve written a handler, pass it to serveChannel on the listening side, or — to answer toConnector pushes on the dialing side — to connectChannel’s onPush.