
Lightweight TypeScript HTTP client for consuming third-party JSON APIs.
Built on the platform fetch API with strict Result<T, E> error
handling — zero legacy dependencies, no request, no Bluebird.
Named after the Carpathian trembita — a long signal horn. One small client, explicit success/failure at a distance.
Quick links: Install · Minimal example · Learning guide · Examples · Agent skill · Why trembita · Use cases · @trembita/openapi · Error handling · API reference · For AI agents · Migration from v1
npm install trembita
Optional OpenAPI helpers (path expansion, requestOpenapiPath, Standard Schema
re-exports) live in @trembita/openapi:
npm install trembita @trembita/openapi
See packages/openapi/README.md.
fetch and URL (same ESM entry).Uses throw on bad init so the same snippet works in Node and browsers (no
process.exit).
import { createTrembita, HTTP_OK } from 'trembita';
const api = createTrembita({ endpoint: 'https://api.example.com/v1' });
if (!api.ok) throw new Error('Invalid endpoint');
const json = await api.value.request({
path: '/resource',
expectedCodes: [HTTP_OK]
});
if (!json.ok) {
console.error(json.error.kind);
}
Choose your starting point based on available time:
QUICK_START.md — Install, 3-step example, common patterns, error types, FAQ.
LEARNING_GUIDE.md — Core concepts, building clients, error handling, testing, advanced features, patterns.
ARCHITECTURE.md — Visual diagrams, request pipeline, error flow, feature matrix, integration patterns.
DECISION_GUIDE.md — Compare with Axios/Got/Fetch, scenario-based recommendations, migration guides.
EXAMPLES.md — 7+ use cases: GitHub API, payments, microservices, health checks, webhooks, search, backups.
examples/ directory — Copy-paste ready:
rest-api-client.ts — Complete CRUD clienttesting-with-mock-fetch.test.ts — Unit test patternsresilience-patterns.ts — Retries, circuit breaker, failoverSee examples/README.md for navigation.
error.kind), not a thrown exception. TypeScript narrows the error for you.fetch and URL from the platform.
Works in Node >= 20 and browsers (via bundler).createTrembita() → { request, client }. No classes,
no middleware chains, no plugin system.fetchImpl to swap fetch in unit tests
without mocking globals..d.ts + source maps, tree-shakeable
with sideEffects: false.When your Node service calls a third-party REST API (payment provider, CRM,
shipping tracker), trembita gives you a typed client with predictable error
handling instead of scattered try/catch blocks around raw fetch.
import { createTrembita, HTTP_OK } from 'trembita';
const stripe = createTrembita({
endpoint: 'https://api.stripe.com/v1'
});
if (!stripe.ok) throw new Error('Bad Stripe config');
const charges = await stripe.value.request({
path: '/charges',
query: { limit: '10' },
headers: { Authorization: `Bearer ${process.env.STRIPE_KEY}` },
expectedCodes: [HTTP_OK]
});
if (!charges.ok) {
if (charges.error.kind === 'unexpected_status') {
console.error('Stripe returned', charges.error.statusCode);
}
}
Internal services often communicate over HTTP JSON. trembita standardizes how you send requests and handle non-200 responses across service boundaries.
const userService = createTrembita({
endpoint: 'http://user-service.internal:3000'
});
if (!userService.ok) throw new Error('Bad user-service config');
const user = await userService.value.request({
path: `/users/${userId}`,
expectedCodes: [HTTP_OK]
});
if (!user.ok && user.error.kind === 'unexpected_status') {
if (user.error.statusCode === 404) {
return null;
}
}
Use the lower-level client function when you need the raw status code and body
— useful for health checks, readiness probes, or status dashboards.
const raw = await api.value.client({ path: '/health' });
if (raw.ok && raw.value.statusCode === 200) {
console.log('Service healthy:', raw.value.body);
}
trembita works in the browser with any bundler (Vite, webpack, esbuild). The
same ESM entry point uses the global fetch and URL APIs.
import { createTrembita, HTTP_OK } from 'trembita';
const api = createTrembita({
endpoint: 'https://api.example.com/v1'
});
if (!api.ok) {
showError('Failed to initialize API client');
}
Use openapi-typescript paths for templates, then expandOpenapiPath
or requestOpenapiPath so path mistakes stay in Result before HTTP.
The add-on package re-exports validateStandardSchema,
requestWithStandardSchema, createRetryingFetch, and
traceContextHeaders for one import line. Full notes, bundle spike, and
openapi-fetch positioning:
packages/openapi/README.md.
Pass a mock fetchImpl to test your integration layer without touching the
network or patching globals.
import { createTrembita, HTTP_OK } from 'trembita';
import { vi } from 'vitest';
const fetchImpl = vi.fn(() =>
Promise.resolve(new Response(JSON.stringify({ id: 1 }), { status: 200 }))
);
const created = createTrembita({
endpoint: 'https://api.test.com',
fetchImpl
});
const result = await created.value.request({
path: '/items/1',
expectedCodes: [HTTP_OK]
});
expect(result.ok).toBe(true);
expect(fetchImpl).toHaveBeenCalledOnce();
The API is functional-first: createTrembita returns a Result, then
plain client / request functions — no class instance. Failures use
tagged errors (error.kind) so callers narrow with types instead of relying
on try/catch for normal HTTP outcomes.
This is not pure FP end-to-end: fetch, the network, and logging are
ordinary side effects. Think FP-style errors and surface area, not a fully
pure program.
Every operation returns a Result<T, E> — either { ok: true, value } or
{ ok: false, error }. Errors are tagged unions you can narrow with
error.kind:
error.kind |
When |
|---|---|
missing_options |
No options passed to createTrembita |
options_not_object |
Options is not an object |
missing_endpoint |
endpoint field is missing |
endpoint_not_string |
endpoint is not a string |
endpoint_invalid_url |
endpoint URL cannot be parsed or bad scheme |
invalid_request_options |
Missing or invalid path/url in request |
fetch_failed |
Network error (DNS, timeout, connection reset) |
timeout |
Request exceeded configured timeout |
circuit_open |
Circuit breaker is open; request short-circuited |
invalid_json |
Response body is not valid JSON |
unexpected_status |
HTTP status not in expectedCodes |
Full TypeDoc documentation is published at
oleg-koval.github.io/trembita (core
trembita entry only). @trembita/openapi types are published with
that package — see packages/openapi/README.md.
createTrembita(options)Creates a client bound to a base URL.
| Option | Type | Default | Description |
|---|---|---|---|
endpoint |
string (required) |
— | Base URL for all requests |
fetchImpl |
typeof fetch |
globalThis.fetch |
Custom fetch for testing |
log |
Logger |
no-op logger | Logger with trace..error. |
timeoutMs |
number |
— | Default timeout for requests |
circuitBreaker |
{ failureThreshold: number; cooldownMs: number } |
— | Optional consecutive-failure breaker |
Returns Result<TrembitaClient, TrembitaInitError>.
log is provided, trembita emits lifecycle events:
request:start (debug): endpoint, path, method, sanitized headersrequest:success (info): endpoint, path, statusCode, durationMsrequest:unexpected_status (warn): endpoint, path, statusCode,
expectedCodesrequest:fetch_failed / request:invalid_json (error): endpoint, path,
error kindauthorization, cookie,
set-cookie, x-api-key, proxy-authorization).request(options)Sends a request and returns the parsed JSON body if the status matches
expectedCodes (default: [200, 201]).
Returns Promise<Result<unknown, TrembitaRequestError>>.
client(options)Sends a request and returns { statusCode, body, path } regardless of status.
Returns Promise<Result<TrembitaHttpResponse, TrembitaSendError>>.
These ship from trembita and are also re-exported from
@trembita/openapi for OpenAPI-shaped call sites:
| Export | Role |
|---|---|
validateStandardSchema |
Run a Standard Schema v1 validate → Result (no throw). |
requestWithStandardSchema |
request + optional response body validation. |
createRetryingFetch |
Wrap fetch with backoff retries for status / transport failures. |
traceContextHeaders |
Build traceparent / tracestate headers (W3C Trace Context). |
| Option | Type | Default |
|---|---|---|
path or url |
string |
— (required) |
method |
string |
GET / POST with body |
headers |
Record<string, string> |
{} |
query or qs |
Record<string, string|...> |
— |
body |
unknown |
— |
expectedCodes |
number[] |
[200, 201] |
timeoutMs |
number |
init timeoutMs |
signal |
AbortSignal |
— |
fetch and URL (same ESM entry).npm install trembita
This version line is a breaking v2 migration to the functional Result API.
| v1 | v2 |
|---|---|
const T = require('trembita') / new Trembita(opts) |
import { createTrembita } from 'trembita' then createTrembita(opts) → Result |
this.request({ url, qs, expectedCodes }) throwing |
request({ path or url, query or qs, expectedCodes }) → Promise<Result<unknown, TrembitaRequestError>> |
catch (UnexpectedStatusCodeError) |
Narrow !result.ok and check result.error.kind === 'unexpected_status' (prefer kind, not message text) |
request / bluebird / validator |
fetch, URL, optional fetchImpl for tests |
| Topic | Action |
|---|---|
| Install | npm install trembita @trembita/openapi — trembita is a peer dependency (^2) of the OpenAPI package. |
| Path typing | Generate paths with openapi-typescript, keep templates aligned with keyof paths, expand with Result. |
| Publishing | Same semantic-release run publishes trembita then @trembita/openapi. Use Trusted Publishing for both packages on npm (or an Automation granular token); see CONTRIBUTING.md. If only trembita has OIDC and NPM_TOKEN is set, OpenAPI may fall back to token auth and fail with EOTP under 2FA. |
Trembita is designed to be agent-friendly. When implementing API integrations, refer to:
These resources teach agents and developers how to build type-safe, testable API integrations.
See CONTRIBUTING.md.
MIT © 2018–2026