Capabilities
capability() declares one named application action — its input contract, authorization and approval rules, and the function Kaji executes.
A capability declares one application action: its validated input contract,
authorization and approval rules, and the ordinary application function
Kaji will eventually execute. A capability never executes itself — only
kaji.execute() calls its execute function.
import { capability } from "@irogane/kaji";
import { z } from "zod";
const refund = capability({
name: "payments.refund",
input: z.object({ paymentId: z.string(), amount: z.number() }),
authorize: async ({ principalId, input }) => {
return canRefund(principalId, input.paymentId);
},
approval: ({ input }) => input.amount >= 500,
execute: async (input, context) => {
return refundPayment({
...input,
idempotencyKey: context.idempotencyKey,
signal: context.signal,
});
},
});
Fields
nameis a stable, non-empty, application-defined identifier. It’s the only field the publicCapabilitytype exposes.inputneeds only aparse(input: unknown)method that returns validated input or throws. Kaji does not select or export a schema library — Zod, Valibot, or a hand-written parser all satisfy this shape.authorizereceives validated input and returns whether the principal may execute the capability. Returningfalsedenies execution. Throwing also denies it, before any side effect runs.approvalis optional. Returningtruerequires an approval decision beforeexecuteruns; returningfalse(or omittingapproval) does not. The capability decides when approval is required — the executor enforces it. See Approval.executeis ordinary application code. It receives validated input and the per-executionExecutionContext(principalId,idempotencyKey,signal), and must use the suppliedAbortSignalcooperatively when the underlying operation supports it.
Non-Goals
capability() only declares the action — it validates its own declaration
(a non-empty name, an authorize function) at construction time, but
never calls authorize, approval, or execute itself. Business logic
stays in your application: Kaji must not embed domain-specific behavior
such as a refund policy, and a capability’s execute function must remain
ordinary application code that Kaji only calls through its executor.