Creating Procedures
Procedures in Actyx RPC are constructed using a procedure builder created by createProcedure(). This allows you to encapsulate and reuse shared server concerns, such as authentication, logging, global context, and centralized error mapping.
createProcedure()
To get started, instantiate a base procedure builder:
import { createProcedure } from "@explita/actyx-rpc";
const procedure = createProcedure({
async createContext() {
const session = await getSession();
if (!session) {
return { ok: false, reason: "UNAUTHORIZED" };
}
return {
ok: true,
ctx: {
userId: session.user.id,
role: session.user.role,
},
};
},
onContextError({ reason }) {
if (reason === "UNAUTHORIZED") {
return {
_redirect: () => redirect("/login"),
message: "Session expired or invalid",
};
}
},
onSuccess({ ctx, input, output, duration }) {
console.log("Procedure completed successfully", {
handler: ctx.handlerName,
duration
});
},
inputMode: "strict", // "strict" | "form" | "partial"
});Configuration Options
| Option | Type | Description |
|---|---|---|
createContext | (prev) => Promise<ContextResult> | Runs at the start of the request lifecycle to generate a shared context. Can return { ok: false, reason: string } on failure. |
onContextError | (err) => any | Called if createContext fails. Useful for global redirects or constructing unified error payloads. |
enrichInput | (ctx) => Promise<TEnrich> | TEnrich | Runs after context creation to attach global context keys (e.g., userId) into all procedure input objects. |
onSuccess | (props) => void | Runs when a procedure finishes successfully. Receives context, input, output, and execution duration. |
onError | (props) => any | Runs when a procedure throws or returns an error. Returns mapped error format. |
inputMode | InputMode | The default caller input mode for procedures (strict, form, partial). |
middlewares | Middleware[] | Array of global middlewares applied to all procedures generated from this builder. |
plugins | Plugin[] | Array of global plugins applied to all procedures. |
cache | CacheAdapter | The global cache adapter used for caching queries and rate-limit tracking (defaults to MemoryCache). |
compression | Compressor | The global compressor instance used for response compression (defaults to Compressor). |
meta | TMeta | Default global metadata attached to all child procedures. |
Global Error Mapping
The onError hook is a powerful way to centralize error handling. Instead of polluting every query or mutation handler with identical try/catch blocks, let your handlers throw naturally.
When onError returns an object, that object becomes the official error response returned by the procedure to the client.
const procedure = createProcedure({
// ...
onError({ error, ctx }) {
// If the procedure has a .name("..."), it's available in ctx.handlerName
console.error(`Error in ${ctx.handlerName}:`, error);
// Map database unique constraint violations globally
if (error.code === "P2002") {
return {
success: false,
message: "Conflict detected",
reason: "VALIDATION_ERROR",
errors: {
[error.meta.target[0]]: "This value is already taken",
},
};
}
},
});Now your actual query and mutation handlers can be clean and free of boilerplate:
const createUser = procedure.input(userResolver).mutation(async ({ input }) => {
// Just throw! No try/catch needed here.
return await db.user.create({ data: input });
});Next.js Redirects in Context Failures
If you are using Next.js, you can return a _redirect callback in onContextError (or inside middlewares) to trigger a top-level redirect:
onContextError({ reason }) {
if (reason === "INVALID_SESSION") {
return {
_redirect: () => redirect("/login"),
message: "Session expired",
};
}
}This is executed at the very start of the procedure response before returning to the caller.