ts-micro-result
v3.3.0
Published
Lightweight Result type for TypeScript - API contract for BE ↔ FE
Maintainers
Readme
ts-micro-result
Lightweight Result type for TypeScript
API contract for BE ↔ FE. Not a FP monad.
TL;DR
ts-micro-result is a JSON-safe Result type for API boundaries.
- Designed for BE ↔ FE contracts
- Works great with Edge / Serverless
- No classes, no exceptions, no FP chains
- Errors are data, not thrown
import { ok, err } from 'ts-micro-result'
import type { Result } from 'ts-micro-result'
// Backend returns Result
function getUser(id: string): Result<User> {
if (!user) return err({ code: 'NOT_FOUND', message: 'User not found' })
return ok(user)
}
// Frontend receives predictable JSON
const res = await fetch('/api/users/123')
const result = await res.json() // { ok: true, data: {...} } or { ok: false, errors: [...] }Should I Use This?
Use ts-micro-result if:
- You design APIs with explicit error contracts
- You want predictable BE ↔ FE JSON responses
- You run on Edge / Serverless (Cloudflare, Vercel, Workers)
- You need errors as data (loggable, cacheable, replayable)
Do NOT use if:
- You want
map/flatMap/unwrap→ use neverthrow - You prefer throwing exceptions → use try/catch
- You need internal-only error handling → use custom patterns
Install
npm install ts-micro-resultQuick Example
import { ok, err } from 'ts-micro-result'
import type { Result } from 'ts-micro-result'
function getUser(id: string): Result<User> {
const user = db.find(id)
if (!user) {
return err({ code: 'NOT_FOUND', message: 'User not found' })
}
return ok(user)
}
const result = getUser('123')
if (result.ok) {
console.log(result.data.name) // TypeScript knows data is User
} else {
console.log(result.errors[0].message)
}Entries
| Entry | Use Case | Size |
|-------|----------|------|
| ts-micro-result | Full API | ~2.5 kB |
| ts-micro-result/lite | Edge Workers, ultra-small bundles | < 1 kB |
| ts-micro-result/http | HTTP helpers | ~0.5 kB |
| ts-micro-result/types | FE import type only | 0 kB |
API Overview
Factories
ok() // Result<undefined> - void operations
ok(data) // Result<T>
ok(data, meta) // Result<T> with meta
okJson(data) // Result<T> with strict JsonValue check (optional)
err(error) // Err
err([error1, error2]) // Err with multiple errors
okPage(items, pagination) // Result<T[]> with paginationNote:
ok()accepts any type - JSON-safety is by convention. UseokJson()if you want compile-time enforcement that data extendsJsonValue.
ok() vs ok(null):
ok()→ void operations (DELETE, UPDATE) →Result<void>ok(null)→ null is valid data →Result<T | null>
Utilities
match(result, { ok, err }) // Pattern matching
isResult(value) // Runtime type guard
isOk(result) / isErr(result) // Type guards
combine([r1, r2, r3]) // Combine Results into array
combineObject({ a: r1 }) // Combine Results into object
serialize(result) // Deep JSON-safetyHTTP (ts-micro-result/http)
toHttpResponse(result) // ok(data) → 200, ok() → 204, err() → 400
sendResponse(res, response) // Express/Fastify helper
created(result) // 201
accepted(result) // 202
// Adapter for automatic error-to-status mapping
const toHttp = withFallbackStatus(createHttpResultAdapter(errorMap), 400)| Result | Status | Body |
|--------|--------|------|
| ok(data) | 200 | JSON |
| ok() | 204 | null |
| err() | 400 | JSON |
Types
type Result<T = undefined> = Ok<T> | Err
interface Ok<T> {
readonly ok: true
readonly data: T
readonly meta?: ResultMeta
}
interface Err {
readonly ok: false
readonly errors: readonly ErrorDetail[]
readonly meta?: ResultMeta
}
interface ErrorDetail {
readonly code: string // Machine-readable
readonly message: string // Human-readable
readonly field?: string // For validation
}
interface ResultMeta {
readonly pagination?: Pagination
readonly traceId?: string
readonly params?: Record<string, unknown> // Opaque metadata passthrough
}About params
params is opaque metadata passed through the Result.
The library never interprets or mutates it.
Use it for domain-specific data that doesn't fit standard fields:
// Rate limiting
return err(
{ code: 'RATE_LIMIT_EXCEEDED', message: 'Too many requests' },
{
traceId,
params: {
retryAfter: 60,
timestamp: Date.now(),
limit: 'user_login',
},
}
)
// Cache hints
return ok(user, {
params: { cacheKey: `user:${user.id}`, ttl: 3600 },
})✔ Typed
✔ Keeps message clean
✔ Doesn't leak HTTP concerns
✔ Doesn't break Result shape
Documentation
- docs/http.md — HTTP integration, adapters, policy wrappers
- docs/examples.md — React Query, SWR, Express, Hono, pagination, error factories
- docs/MIGRATION.md — v2 → v3 migration guide
- docs/CHANGELOG.md — Version history
Comparison
| Library | Purpose |
|---------|---------|
| ts-micro-result | API boundaries, Edge/Serverless, JSON contracts |
| neverthrow | Internal logic, FP patterns, map/flatMap |
| fp-ts | Pure FP, effect systems |
| Zod / Valibot | Input validation |
| RFC 7807 | HTTP-level error format (can use inside err()) |
License
MIT
