TypeScript-First DX
Types all the way down.
The SDK isn't typed as an afterthought — it's built TypeScript-first. Every face, method, parameter, and response is fully typed with generics, branded types, and discriminated unions.
Why it matters
Your editor becomes your documentation.
Autocomplete
Every face, method, parameter, and response field appears in your editor. No docs needed — your IDE is the documentation.
Compile-time safety
Catch typos, wrong parameter types, and missing fields before you run a single line. Refactor with confidence.
Refactoring confidence
Rename a face method and TypeScript finds every call site. Change a response shape and the compiler shows every affected handler.
Generic Responses
Pass your schema, get typed data.
Generic type parameters flow through every face method. Pass your interface to ee.ai.generate<T>() and the response is typed to your schema — no runtime assertions needed.
// Generic response types — pass your schema, get typed data
interface RevenueReport {
quarter: string
revenue: number
growth: number
highlights: string[]
}
const report = await ee.ai.generate<RevenueReport>({
model: 'claude-sonnet-4-20250514',
prompt: 'Analyse Q3 revenue data',
context: await ee.storage.get('data/q3.csv'),
responseFormat: 'json',
})
// report.data is fully typed:
report.data.quarter // string
report.data.revenue // number
report.data.growth // number
report.data.highlights // string[]Discriminated Unions
Events narrowed by type.
Webhook events and async responses use discriminated unions. Switch on the type field and TypeScript narrows the data type automatically — no casting, no guards.
// Discriminated unions for events
type WebhookEvent =
| { type: 'user.created'; data: { userId: UserId; email: string } }
| { type: 'user.deleted'; data: { userId: UserId; reason: string } }
| { type: 'invoice.paid'; data: { invoiceId: InvoiceId; amount: number } }
| { type: 'invoice.failed'; data: { invoiceId: InvoiceId; error: string } }
// TypeScript narrows the type automatically
function handleEvent(event: WebhookEvent) {
switch (event.type) {
case 'user.created':
// event.data is { userId: UserId; email: string }
console.log(event.data.email)
break
case 'invoice.paid':
// event.data is { invoiceId: InvoiceId; amount: number }
console.log(event.data.amount)
break
}
}Branded Types
No more mixed-up IDs.
Every ID in the SDK is a branded type. A UserId is not assignable to an EntityId — the compiler catches the mistake before it reaches production.
// Branded types for IDs — no mixing up user IDs and invoice IDs
type UserId = string & { readonly __brand: 'UserId' }
type EntityId = string & { readonly __brand: 'EntityId' }
type InvoiceId = string & { readonly __brand: 'InvoiceId' }
// The SDK returns branded types:
const user = await ee.auth.getUser({ id: userId })
user.id // UserId (not just string)
user.entityId // EntityId (not just string)
// This is a compile error — you can't pass an EntityId as a UserId:
// await ee.auth.getUser({ id: user.entityId })
// ^^^ Type 'EntityId' is not assignable to 'UserId'Typed Errors
Exhaustive error handling.
Every error code is a typed literal. The switch statement covers every possible error — TypeScript tells you if you miss one. Each error code carries its own typed details object.
// Typed error codes with exhaustive switch
import { EEError, isEEError } from '@evileye/sdk'
try {
await ee.billing.charge({ amount: 100, currency: 'usd' })
} catch (err) {
if (isEEError(err)) {
switch (err.code) {
case 'INSUFFICIENT_FUNDS':
// err.details is { balance: number; required: number }
notifyUser(`Need ${err.details.required - err.details.balance} more`)
break
case 'CARD_DECLINED':
// err.details is { reason: string; retryable: boolean }
if (err.details.retryable) retry()
break
case 'RATE_LIMITED':
// err.details is { retryAfter: number }
await sleep(err.details.retryAfter)
retry()
break
// TypeScript ensures you handle every case
}
}
}Client Config
Every option documented in your editor.
// Full client options interface — every option typed
interface EEClientOptions {
/** API token (root or scoped) */
token: string
/** Entity identifier */
entity: string
/** Base URL override (default: https://api.evileye.dev) */
baseUrl?: string
/** Request timeout in ms (default: 30_000) */
timeout?: number
/** Retry configuration */
retry?: {
maxRetries?: number // default: 3
backoffMs?: number // default: 1000
backoffMultiplier?: number // default: 2
retryableErrors?: ErrorCode[]
}
/** Custom fetch implementation */
fetch?: typeof globalThis.fetch
/** Logger instance */
logger?: EELogger
/** Enable debug mode */
debug?: boolean
}Experience the types yourself.
Install the SDK and let your editor show you what's possible.