Zagora
Type-safe functions with full inference, typed errors, and zero async overhead -- just pure TypeScript. Skip the complexity of tRPC or Effect.ts and build robust libraries and APIs, the Robust Way.
Quick Example
import { z } from 'zod';
import { zagora } from 'zagora';
const getUser = zagora()
.input(z.tuple([z.string(), z.number().default(18), z.string().optional()]))
.handler((_, name, age, country) => {
// name: string
// age: number <-- because there is a default value in schema!
// country: string | undefined <-- because it's marked as optional in schema!
return `${name} is ${age}, from ${country || 'unknown'}`
})
.callable();
getUser('John', 30);
// => John is 30
// @ts-expect-error -- reported at compile-time AND runtime, invalid second argument
getUser('John', 'foo');
// @ts-expect-error -- reported at compile-time AND runtime, missing required argument
getUser();
// NOTE: fine, because second and third arguments are optional (default or optional)
getUser('Barry') // => Barry is 18, from unknown
getUser('Barry', 25) // => Barry is 25, from unknown
getUser('Barry', 33, 'USA') // => Barry is 33, from USA
const result = await getUser('Alice');
if (result.ok) {
console.log(result.data); // "Alice is 18, from unknown"
} else {
console.error(result.error.kind);
console.error(result.error);
// ^ { kind: 'UNKNOWN_ERROR', message, cause }
// or
// ^ { kind: 'VALIDATION_ERROR', message, issues: Schema.Issue[] }
}Features
Minimal & Standards-Based
Zagora is lightweight with zero bloat, built entirely on StandardSchema for universal validation. This means you can use Zod, Valibot, ArkType, or any compliant validator. No lock-in, just the tools you already know and love.
Never-Throwing Execution
Every function returns a predictable { ok, data, error } result—exceptions are eliminated completely. Your process never crashes from unhandled errors, similar to Effect.ts or neverthrow. This gives you total control and deterministic error handling across your entire codebase.
Typed Error System
Define error schemas upfront and get strongly-typed error helpers inside your handlers. Each error kind is validated at runtime and fully typed at compile-time. You'll never see try/catch blocks or guess error shapes again.
Full Type Inference
Complete TypeScript inference across inputs, outputs, errors, context, defaults, and optionals. Even JavaScript consumers get full autocomplete and IntelliSense support. The type system has been battle-tested with dedicated type-level tests.
Tuple Arguments Support
Define multiple function arguments using schema tuples with per-argument validation and defaults. Call your functions naturally like fn('Alice', 25) instead of fn({ name: 'Alice', age: 25 }). This creates a familiar API that feels like native TypeScript functions.
Sync & Async Awareness
Zagora dynamically infers whether procedures are sync or async based on handler and schema behavior. Sync handlers return Result, async handlers return Promise<Result>—no forced async everywhere. This is impossible with oRPC/tRPC where everything is always async.
Built-in Caching
Add memoization to any procedure with a simple cache adapter. Cache keys include input, schemas, and handler body for intelligent invalidation. Works with both sync and async cache implementations seamlessly.
Just Pure Functions
Zagora produces regular TypeScript functions—no special clients, routers, or network glue required. Export your procedures directly and call them like any other function. Perfect for building type-safe libraries, SDKs, and internal tooling.
Env Vars Validation
Validate environment variables with the same schema system used for inputs and outputs. Get type-safe access to process.env or import.meta.env inside handlers. Coercion, defaults, and optionals work exactly as expected.
No Unwrapping Required
Unlike neverthrow or similar libraries, you directly access result.data or result.error. No .unwrap(), .map(), or monadic chains needed. The discriminated union type guides you naturally with TypeScript's narrowing.
Learn More
Curious how Zagora compares to other solutions? Check out our detailed comparisons:
- vs oRPC / tRPC - When to use Zagora vs RPC frameworks
- vs neverthrow / Effect.ts - Error handling approaches compared
- vs Plain TypeScript - Why add Zagora to your stack
- vs Standalone Validators - Beyond just Zod/Valibot
Need routers, middleware, or HTTP integration? Zagora makes it easy - see Building Routers for patterns.