Getting Started
Define a capability and run it through the Kaji executor.
Kaji is TypeScript-only in v0 and is not yet published to a package
registry. This tutorial defines one capability and executes it with the
in-memory execution store, the same shape as examples/refund in the
repository.
Install
From within the repository (or a project that depends on the @irogane/kaji
workspace package):
bun install
Kaji does not require or declare a schema library as a dependency. input
only needs a parse(input: unknown) method that returns validated input or
throws — this example uses Zod, but any library with that shape works.
bun add zod
First run
Define and execute a capability
import { capability, createKaji, memoryStore } from "@irogane/kaji"; import { z } from "zod"; const echo = capability({ name: "echo", input: z.object({ message: z.string() }), authorize: async () => true, execute: async (input) => ({ message: input.message }), }); const kaji = createKaji({ store: memoryStore() }); const result = await kaji.execute(echo, { input: { message: "Hello, Kaji." }, principalId: "local-user", idempotencyKey: "echo-1", }); console.log(result);Run it with
bun run kaji.ts(ornpx tsx kaji.ts). It prints asucceededresult carrying{ message: "Hello, Kaji." }and execution evidence (executionId,capability,principalId,idempotencyKey,inputFingerprint).Add approval for sensitive input
approvalmarks which requests need a decision beforeexecuteruns. Supplyapproveon the executor to decide them:const refund = capability({ name: "payments.refund", input: z.object({ paymentId: z.string(), amount: z.number() }), authorize: async ({ principalId, input }) => canRefund(principalId, input.paymentId), approval: ({ input }) => input.amount >= 500, execute: async (input, context) => refundPayment(input, context), }); const kaji = createKaji({ store: memoryStore(), approve: async (request) => requestHumanApproval(request), });An absent approver, an invalid decision shape, a rejected decision, or an approver error settles the execution as
rejectedinstead of runningexecute.Handle every outcome
execute()resolves with an explicitstatusinstead of throwing for expected outcomes:succeeded,denied,rejected,failed,cancelled, orunknown.unknownmeans a side effect may have committed but Kaji cannot prove it — do not blindly retry it.if (result.status === "succeeded") { console.log(result.result); } else if (result.status === "unknown") { // do not retry automatically; the side effect may have already run }
Further Reading
- Capabilities for the full
capability()contract - Execution for the exact order Kaji runs validation, authorization, approval, and execution
- Idempotency for how retries and duplicates resolve
docs/api.mdin the repository for the complete frozen contract, including the export boundary and known-failure semanticsexamples/refundin the repository for a runnable end-to-end proof