# klaim

> Klaim is a lightweight TypeScript library designed to manage APIs and record requests, optimized for an optimal user experience.

Latest version **2.0.0** (published 2026-09-14) · MIT license · 0 weekly downloads

## Install

```sh
npm install klaim
pnpm add klaim
yarn add klaim
bun add klaim
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 2.0.0 |
| Published | 2026-09-14 |
| First published | 2024-07-01 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 772.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 6 |
| Author | antharuu |
| Maintainers | antharuu |
| Keywords | typescript, api, request, user experience, optimization, lightweight |

## Links

- npm: https://www.npmjs.com/package/klaim
- Repository: https://github.com/antharuu/klaim
- Homepage: https://github.com/antharuu/klaim#readme
- Issues: https://github.com/antharuu/klaim/issues
- npm.io page: https://npm.io/package/klaim

## Alternatives

- [launchdarkly-js-client-sdk](https://npm.io/package/launchdarkly-js-client-sdk.md) — 2.5M weekly downloads
- [@elastic/elasticsearch](https://npm.io/package/@elastic/elasticsearch.md) — 2.1M weekly downloads
- [@c8y/client](https://npm.io/package/@c8y/client.md) — 15.3K weekly downloads
- [@signaldb/maverickjs](https://npm.io/package/@signaldb/maverickjs.md) — 1.7K weekly downloads
- [@bbc/http-transport-cache](https://npm.io/package/@bbc/http-transport-cache.md) — 1.2K weekly downloads

## Recent versions

- 2.0.0 (latest) — 2026-09-14
- 1.7.0-alpha.2 (alpha) — 2024-12-06
- 1.12.137 — 2026-08-30
- 1.12.136 — 2026-08-29
- 1.12.135 — 2026-08-28
- 1.12.134 — 2026-08-27
- 1.12.133 — 2026-08-26
- 1.12.132 — 2026-08-25
- 1.12.131 — 2026-08-24
- 1.12.130 — 2026-08-23
- 1.12.129 — 2026-08-22
- 1.12.128 — 2026-08-21
- 1.12.127 — 2026-08-20
- 1.12.126 — 2026-08-19
- 1.12.125 — 2026-08-18
- … 522 more at https://npm.io/package/klaim/versions

## README

![NPM Downloads](https://img.shields.io/npm/d18m/klaim?style=flat)

## 📚 Table of Contents

- [Features](#-features)
- [Next features](#-next-features)
- [Installation](#-installation)
- [Usage](#-usage)
  - [Basic API Configuration](#basic-api-configuration)
  - [Route Definition](#route-definition)
  - [Request Handling](#request-handling)
  - [Groups](#groups)
    - [API Groups](#api-groups)
    - [Route Groups](#route-groups)
    - [Nested Groups](#nested-groups)
    - [Group Configuration](#group-configuration)
  - [Middleware Usage](#middleware-usage)
  - [Global Middleware](#global-middleware)
  - [Hook Subscription](#hook-subscription)
  - [Stats & Observability](#stats--observability)
  - [Caching Requests](#caching-requests)
  - [Retry Mechanism](#retry-mechanism)
  - [Rate Limiting](#rate-limiting)
  - [Circuit Breaker](#circuit-breaker)
  - [Request Deduplication](#request-deduplication)
  - [Response Validation](#response-validation)
  - [Batch Requests](#batch-requests)
- [Links](#-links)
- [Contributing](#-contributing)
- [License](#-license)

## 🚀 Features

- **Efficient API Management**: Easily manage multiple APIs with streamlined integration and interaction capabilities.
- **API Grouping**: Organize related APIs into logical groups with shared settings and configuration.
- **Route Grouping**: Organize related routes into logical groups with inherited settings.
- **Request Recording**: Seamlessly track requests for debugging and monitoring.
- **User Experience Optimization**: Focused on performance and usability for a smooth user experience.
- **Lightweight**: Minimal footprint for fast load times and minimal performance impact.
- **Middleware Support**: Easily add middleware to modify requests and responses (`before` and `after`).
- **Hook System**: Subscribe to hooks to monitor and react to specific events.
- **Stats & Observability**: Track calls, latency, error rate and cache hit rate per route out of the box.
- **Caching**: Enable caching on requests to reduce network load and improve performance.
- **Retry Mechanism**: Automatically retry failed requests to enhance reliability.
- **Rate Limiting**: Control the frequency of API calls to prevent abuse and respect API provider limits.
- **Circuit Breaker**: Fail fast and stop hammering a failing API/route after repeated failures, with automatic recovery testing.
- **Request Deduplication**: Coalesce concurrent identical GET calls into a single network request.
- **Timeout**: Abort requests that exceed a specified duration with an optional custom error message.
- **TypeScript Support**: Fully typed for enhanced code quality and developer experience.
- **Response Validation**: Validate responses using schemas for increased reliability and consistency.
- **Pagination**: Handle paginated requests easily with support for both page and offset based pagination.
- **Batch Requests**: Run several route calls concurrently and get partial success/failure results, `Promise.allSettled`-style.

## 📥 Installation

Install Klaim via npm:

```sh
// Using npm
npm install klaim

// Using bun
bun add klaim

// Using deno
deno add @antharuu/klaim
```

## 🛠 Usage

### Basic API Configuration

First, set up the API configuration. Define the API and its base URL.

```typescript
import {Api, Route} from 'klaim';
// For deno: import { Api, Route } from "@antharuu/klaim";

// Your simple Todo type
type Todo = {
    userId: number;
    id: number;
    title: string;
    completed: boolean;
};

// Create a new API with the name "hello" and the base URL "https://jsonplaceholder.typicode.com/"
Api.create("hello", "https://jsonplaceholder.typicode.com/", () => {
    // Define routes for the API
    Route.get<Todo[]>("listTodos", "todos");
    Route.get<Todo>("getTodo", "todos/[id]");
    Route.post<Todo>("addTodo", "todos");
});
```

### Route Definition

Routes represent endpoints in your API and can be defined with different HTTP methods. Routes can include parameters and
custom configurations:

```typescript
Api.create("api", "https://api.example.com", () => {
    // Basic GET route
    Route.get("listUsers", "/users");

    // GET route with URL parameter
    Route.get("getUser", "/users/[id]");

    // POST route with custom headers and body
    Route.post("createUser", "/users", {
        "Content-Type": "application/json"
    }, {userId: 1, name: "John Doe"});

    // PUT route with parameter
    Route.put("updateUser", "/users/[id]");

    // DELETE route
    Route.delete("deleteUser", "/users/[id]");

    // PATCH route
    Route.patch("updateUserStatus", "/users/[id]/status");

    // OPTIONS route
    Route.options("userOptions", "/users");
});
```

### Groups

Klaim provides powerful grouping capabilities for both APIs and routes. Groups can be used to organize related elements,
share configuration, and maintain a clean structure in your application.

#### API Groups

Organize multiple APIs that serve related purposes:

```typescript
import {Group, Api, Route} from 'klaim';

// Create a group for user-related services
Group.create("userServices", () => {
    // Authentication API
    Api.create("auth", "https://auth.example.com", () => {
        Route.post("login", "/login");
        Route.post("register", "/register");
    });

    // User Management API
    Api.create("users", "https://users.example.com", () => {
        Route.get("list", "/users");
        Route.get("getOne", "/users/[id]");
    });
}).withRetry(3); // Apply retry mechanism to all APIs in the group

// Access grouped APIs
await Klaim.userServices.auth.login({}, {username: "user", password: "pass"});
await Klaim.userServices.users.list();
```

#### Route Groups

Organize routes within an API into logical groups:

```typescript
Api.create("hello", "https://api.example.com/", () => {
    // Group user-related routes
    Group.create("users", () => {
        Route.get<User[]>("list", "/users");
        Route.get<User>("getOne", "/users/[id]");
        Route.post<User>("create", "/users");
    }).withCache(60);  // Cache all user routes for 60 seconds

    // Group product-related routes
    Group.create("products", () => {
        Route.get("list", "/products");
        Route.get("getOne", "/products/[id]");
    });
});

// Use grouped routes
const users = await Klaim.hello.users.list();
const product = await Klaim.hello.products.getOne({id: 1});
```

#### Nested Groups

Create complex hierarchies with nested groups:

```typescript
Group.create("services", () => {
    // Internal services group
    Group.create("internal", () => {
        Api.create("logs", "https://logs.internal.example.com", () => {
            Route.post("write", "/logs");
        });

        Api.create("metrics", "https://metrics.internal.example.com", () => {
            Route.post("track", "/metrics");
        });
    }).withRetry(5);  // More retries for internal services

    // External services group
    Group.create("external", () => {
        Api.create("weather", "https://api.weather.com", () => {
            Route.get("forecast", "/forecast/[city]");
        });

        Api.create("geocoding", "https://api.geocoding.com", () => {
            Route.get("search", "/search/[query]");
        });
    }).withCache(300);  // Cache external services longer
});

// Access nested groups
await Klaim.services.internal.logs.write({}, {message: "Log entry"});
await Klaim.services.external.weather.forecast({city: "Paris"});
```

#### Group Configuration

Groups can share configuration among all their members:

```typescript
Group.create("apis", () => {
    Api.create("service1", "https://api1.example.com", () => {
        Route.get("test", "/test");
    });

    Api.create("service2", "https://api2.example.com", () => {
        Route.get("test", "/test");
    });
})
    .withCache(60)  // Enable caching for all APIs
    .withRetry(3)   // Enable retries for all APIs
    .before(({config}) => {  // Add authentication for all APIs
        config.headers.Authorization = `Bearer ${getToken()}`;
    })
    .after(({data}) => {  // Process all responses
        logResponse(data);
    });
```

### Request Handling

Handle requests using the defined routes:

```typescript
import {Klaim} from 'klaim';
// For deno: import { Klaim } from "@antharuu/klaim";

// Make a request to the "listTodos" route
const listOfTodos = await Klaim.hello.listTodos<Todo[]>();

// Make a request to the "getTodo" route with the parameter "id"
const todo = await Klaim.hello.getTodo<Todo>({id: 1});

// Make a request to the "addTodo" route
const newTodo = await Klaim.hello.addTodo<Todo>({}, {title: "New Todo", completed: false, userId: 1});
```

### Middleware Usage

Add middleware to modify requests and responses. Use `before` middleware to alter requests before they are sent
and `after` middleware to process responses:

```typescript
Api.create("hello", "https://jsonplaceholder.typicode.com/", () => {
    // With before middleware
    Route.get<Todo>("getRandomTodo", "todos")
        .before(({url}) => {
            const random = Math.floor(Math.random() * 10) + 1;
            return {url: `${url}/${random}`};
        });

    // With after middleware
    Route.get<Todo>("getFirstTodo", "todos")
        .after(({data: [first]}) => ({data: first}));
});
```

### Global Middleware

`before`/`after` middleware also exists at a **global** level, on the `Klaim` object itself. A
global middleware runs for **every route of every API**, in addition to (not instead of) any
local `before`/`after` already set on a route. This is the same before/after concept, just
extended to a global scope — no new middleware system was introduced.

```typescript
import {Klaim} from 'klaim';
// For deno: import { Klaim } from "@antharuu/klaim";

// Runs before every request, for every API/route
Klaim.before(({url, config}) => {
    console.log(`[global] requesting ${url}`);
    return {config: {...config, headers: {...config.headers as object, "X-Trace-Id": "123"}}};
});

// Runs after every response, for every API/route
Klaim.after(({data}) => {
    console.log("[global] response received");
    return {data};
});
```

Multiple global middlewares can be stacked — each call to `Klaim.before`/`Klaim.after` adds
another one, executed in registration order (unlike the local `before`/`after`, which is a
single callback replaced if called again).

#### Execution order

For a single route call, the full order is:

1. **global before** middlewares (registration order, each fed the previous result)
2. *(api-level `before`, if the API used `.before(...)` — see note below)*
3. **route before** (the existing local `.before(...)` hook)
4. network request execution
5. **route after** (the existing local `.after(...)` hook)
6. *(api-level `after`, if the API used `.after(...)` — see note below)*
7. **global after** middlewares (registration order, each fed the previous result)

> **Known limitation:** as of this version, an API-level `before`/`after` (set with
> `Api.create(...).before(...)` directly on the API, as opposed to on one of its routes) is
> stored but not invoked during a request — only the route-level `before`/`after` runs. This is
> a pre-existing behavior, unrelated to global middleware, and is documented here for
> transparency rather than fixed by this feature.

### Hook Subscription

Subscribe to hooks to monitor specific events:

```typescript
import {Hook} from 'klaim';
// For deno: import { Hook } from "@antharuu/klaim";

// Subscribe to the "hello.getFirstTodo" hook
Hook.subscribe("hello.getFirstTodo", ({url}) => {
    console.log(`Requesting ${url}`);
});
```

### Stats & Observability

Klaim ships a built-in `Stats` singleton that observes **every** route call automatically, without
touching or conflicting with your own `Hook.subscribe` callbacks. It's built on `Hook.onAny`, a
multi-listener observation point separate from the single-callback-per-route `Hook.subscribe` API,
so Stats and your user hooks can coexist on the same route.

For each route it tracks, non-blocking and with a bounded memory footprint:

- **calls**: total number of calls observed
- **errors** / **errorRate**: failed calls and their ratio
- **cacheHits** / **cacheHitRate**: calls served from cache and their ratio
- **avgLatencyMs**: cumulative running average latency (no per-call array growth)
- **p95LatencyMs**: approximate 95th percentile from a bounded sliding window (last 100 calls)

```typescript
import {Stats} from 'klaim';
// For deno: import { Stats } from "@antharuu/klaim";

// After making some calls...
await Klaim.hello.listTodos();
await Klaim.hello.getTodo({id: 1});

// Read metrics for a single route
const routeStats = Stats.i.get("hello.listTodos");
console.log(routeStats?.calls, routeStats?.avgLatencyMs, routeStats?.errorRate);

// Read metrics for every observed route
const allStats = Stats.i.getAll();
console.table(allStats);

// Reset all collected metrics (e.g. between test runs)
Stats.i.reset();
```

### Caching Requests

Enable caching on requests to reduce network load and improve performance. By default, the cache duration is 20 seconds,
but you can specify a custom duration in seconds.

#### Caching Individual Routes

You can enable caching on individual routes:

```typescript
Api.create("hello", "https://jsonplaceholder.typicode.com/", () => {
    // Get a list of todos with default cache duration (20 seconds)
    Route.get<Todo[]>("listTodos", "todos").withCache();

    // Get a specific todo by id with custom cache duration (300 seconds)
    Route.get<Todo>("getTodo", "todos/[id]").withCache(300);

    // Add a new todo (no cache)
    Route.post<Todo>("addTodo", "todos");
});
```

Now, when making requests, the caching feature will be applied.

#### Invalidating Cached Routes

Cache entries are namespaced internally as `${parent}.${routeName}` (e.g. `"hello.getTodo"`).
All parameterized variants of a route (different `args`, query strings, TTLs or response
policies) share that namespace, so a single call clears every cached variant — without wiping
unrelated entries the way `Cache.clear()` would.

```typescript
import {Cache, Klaim} from 'klaim';

// Invalidate only "hello.getTodo", leaving other cached routes untouched
Cache.i.invalidate("hello.getTodo");

// Or invalidate directly from the route handler
Klaim.hello.getTodo.invalidate();

// A broader pattern invalidates every route namespaced under it
Cache.i.invalidate("hello"); // clears hello.getTodo, hello.listTodos, ...
```

#### Caching the Entire API

You can also enable caching for all routes defined within an API:

```typescript
Api.create("hello", "https://jsonplaceholder.typicode.com/", () => {
    // Define routes for the API
    Route.get<Todo[]>("listTodos", "todos");
    Route.get<Todo>("getTodo", "todos/[id]");
    Route.post<Todo>("addTodo", "todos");
}).withCache(); // Enable default cache duration (20 seconds) for all routes
```

### Retry Mechanism

Configure automatic retries for failed requests:

```typescript
// Apply retry at the API level
Api.create("api", "https://api.example.com", () => {
    Route.get("users", "/users");
}).withRetry(3);  // Will retry failed requests up to 3 times

// Apply retry at the route level
Api.create("api", "https://api.example.com", () => {
    Route.get("unstableRoute", "/unstable-endpoint").withRetry(5);  // Will retry up to 5 times
});
```

### Rate Limiting

Control the frequency of API calls to prevent abuse and respect API provider rate limits:

```typescript
// Apply rate limiting at the API level
Api.create("api", "https://api.example.com", () => {
    Route.get("users", "/users");
    Route.get("posts", "/posts");
}).withRate({ limit: 5, duration: 10 });  // Max 5 requests every 10 seconds for this API

// Apply rate limiting at the route level
Api.create("api", "https://api.example.com", () => {
    // This route has its own stricter limits
    Route.get("expensive", "/expensive-operation").withRate({ limit: 2, duration: 60 });  // Max 2 requests per minute
    
    // This route uses the default limits (5 per 10 seconds if not specified)
    Route.get("normal", "/normal-operation").withRate();
});

// Handling rate limit errors
try {
    await Klaim.api.expensive();
} catch (error) {
    if (error.message.includes('Rate limit exceeded')) {
        console.log('Please wait before trying again');
    }
}
```

### Circuit Breaker

Stop hammering an API/route that keeps failing: once a configurable number of **consecutive**
failures is reached, the circuit **opens** and subsequent calls fail fast with a `CircuitOpenError`
— no network request, no retry attempt consumed — until a reset timeout elapses. After the
timeout, the circuit moves to **half-open** and lets exactly one probe call through: if it
succeeds the circuit **closes** again (normal traffic resumes and the failure count resets); if
it fails the circuit **reopens** and the timeout restarts.

The breaker guards the whole call, not each individual retry attempt: it is checked once before
`fetchWithRetry`'s retry loop starts, and only the loop's overall outcome (all attempts
exhausted vs. at least one success) is recorded against it. This keeps the breaker focused on
"is this endpoint down" rather than reacting to single transient errors that retry/backoff is
already designed to absorb.

Like retry and rate limiting, the breaker can be configured at the route level (takes precedence)
or at the API level (shared across all routes of that API that don't set their own):

```typescript
// Apply a circuit breaker at the route level
Api.create("api", "https://api.example.com", () => {
    Route.get("flaky", "/flaky-endpoint").withBreaker({ failureThreshold: 3, resetTimeout: 15 });
});

// Apply a circuit breaker at the API level (shared by all its routes)
Api.create("api", "https://api.example.com", () => {
    Route.get("users", "/users");
    Route.get("posts", "/posts");
}).withBreaker({ failureThreshold: 5, resetTimeout: 30 }); // Defaults if omitted

// Handling circuit breaker errors
try {
    await Klaim.api.flaky();
} catch (error) {
    if (error.name === 'CircuitOpenError') {
        console.log(`Circuit is open, retry in ${error.retryAfterMs}ms`);
    }
}
```

### Request Deduplication

When several callers trigger the exact same **GET** request while a first call for it is still in flight, Klaim coalesces them: only one network call is made, and every caller receives the same result (success or failure). The dedup key is built from the route and its fully-resolved URL/params, so two GET calls with different parameters are never mixed up.

This only ever applies to `GET` requests. Mutating methods (`POST`, `PUT`, `PATCH`, `DELETE`) always hit the network, since sharing or replaying a write between callers would be incorrect.

⚠️ **Note**: cancelling a call via `.cancel()` (see [Request Cancellation](#request-cancellation)) aborts the shared in-flight request, which also rejects every other caller currently coalesced onto that same GET. Avoid cancelling calls you expect other code to be relying on concurrently.

```typescript
Api.create("api", "https://api.example.com", () => {
    Route.get("users", "/users");
});

// Both calls share a single underlying fetch
const [a, b] = await Promise.all([
    Klaim.api.users(),
    Klaim.api.users()
]);
```

### Request Timeout

Bound each request attempt with a timeout in seconds and an optional custom message. Timeout is disabled by default; `.withTimeout()` enables 5 seconds, and a route setting takes precedence over the API setting.

The budget starts after `before`, rate limiting and `onCall`, before cache lookup or fetch. It includes headers and body reading/decoding, but excludes validation, `after`, hooks and retry backoff. Each retry gets a fresh budget and transport controller. Cache hits are also inside the budget, but do not fetch or abort.

On expiration, Klaim rejects with `TimeoutError` and asks cooperative transports to abort. Late responses cannot start body reading, update the cache or run success callbacks; an already-started body read cannot publish a late result. Timers and caller-signal relays are removed when the attempt settles, without aborting successful requests. With retries enabled, the final timeout remains the `cause` of `RetryExhaustedError`.

A signal supplied through `before` as `config.signal` is relayed with its reason when timeout is enabled, and passed through unchanged otherwise. Caller cancellation preserves the existing retry/backoff behavior; it is not a new global cancellation policy. Without `AbortController`, only logical timeout and late-result guards are available. Server-side effects already performed cannot be undone, and synchronous work cannot be preempted. Native transport cancellation is tested on Node; Bun, Deno and browser transports are not verified by these tests.

```typescript
Api.create("api", "https://api.example.com", () => {
    Route.get("slow", "/slow").withTimeout(5, "Too slow");
}).withTimeout(10);

try {
    await Klaim.api.slow();
} catch (error) {
    console.error(error);
}
```

### Request Cancellation

Every call returned by a route exposes `.cancel(reason?)`, letting you cancel that specific in-flight call without affecting concurrent calls to the same route.

```typescript
Api.create("api", "https://api.example.com", () => {
    Route.get("search", "/search");
});

const call = Klaim.api.search();

// Somewhere else, e.g. a component unmount or a newer request superseding this one
call.cancel();

try {
    await call;
} catch (error) {
    if (error.name === "CancelledError") {
        console.log("Call was cancelled");
    }
}
```

Calling `.cancel()` a second time, or after the call has already settled, is a no-op. Pass a custom `reason` to reject with something other than the default `CancelledError`.

When the route has `.withTimeout()` enabled, cancellation reuses the same per-attempt `AbortController` used for the timeout budget, so the underlying transport (e.g. `fetch`) is aborted immediately with no extra allocation and no signal collision between concurrent calls. Without a timeout, cancellation still settles the call immediately and guards any late result (cache write, success callbacks) from completing, but cannot abort synchronous work already in progress. This is independent of, and composes with, a signal supplied through `before` as `config.signal`.

### Response Validation

You can use [Yup](https://www.npmjs.com/package/yup) to validate the response schema for increased reliability and
consistency. You can specify a schema for
individual routes to ensure the response data conforms to the expected structure.

⚠️ **Note**: This feature requires the `yup` package to be installed.

#### Adding Validation to Individual Routes

Enable validation on individual routes:

```typescript
import * as yup from 'yup';

// Define the schema using Yup
const todoSchema = yup.object().shape({
    userId: yup.number().required(),
    id: yup.number().min(1).max(10).required(),
    title: yup.string().required(),
    completed: yup.boolean().required()
});

Api.create("hello", "https://jsonplaceholder.typicode.com/", () => {
    // Get a specific todo by id with validation
    Route.get<Todo>("getTodo", "todos/[id]").validate(todoSchema);
});

// This request will fail because the id is out of range
const todoFail = await Klaim.hello.getTodo<Todo>({id: 15});

// This request will succeed
const todo = await Klaim.hello.getTodo<Todo>({id: 1});
```

#### Using Zod for Validation

You can also validate responses with [zod](https://www.npmjs.com/package/zod) via the built-in `zodAdapter`. `zod`
schemas expose `parseAsync`/`safeParseAsync` instead of `validate`, so `zodAdapter` wraps a zod schema into the
`{ validate }` interface expected by `Route#validate`.

⚠️ **Note**: `zod` is not a dependency of Klaim - it stays entirely optional and must be installed by your project to
use `zodAdapter`.

```typescript
import { z } from 'zod';
import { Api, Klaim, Route, zodAdapter } from 'klaim';

// Define the schema using zod
const todoSchema = z.object({
    userId: z.number(),
    id: z.number().min(1).max(10),
    title: z.string(),
    completed: z.boolean()
});

Api.create("hello", "https://jsonplaceholder.typicode.com/", () => {
    // Get a specific todo by id with validation
    Route.get<Todo>("getTodo", "todos/[id]").validate(zodAdapter(todoSchema));
});

// This request will fail because the id is out of range,
// throwing a ValidationError with the zod issues as `cause`
const todoFail = await Klaim.hello.getTodo<Todo>({id: 15});

// This request will succeed
const todo = await Klaim.hello.getTodo<Todo>({id: 1});
```

### Pagination

Configure pagination for routes that require it:

```typescript
// Basic usage with custom limit and offset parameter
Api.create("api", "https://api.example.com", () => {
    Route.get("list", "/items").withPagination({
        limit: 20,          // Items per page
        page: 1,            // Default page number
        pageParam: "offset", // Parameter name for page/offset or any other custom parameter
        limitParam: "limit"  // Parameter name for limit
    }); // All options are optional
});

// Using paginated endpoints
const page1 = await Klaim.api.list();        // First page
const page2 = await Klaim.api.list(2);       // Second page
const customPage = await Klaim.api.list(5);  // Fifth page
```

⚠️ **Note**: The pagination feature simplifies your pagination parameters, but your API/backend needs to respond to these
parameters. Klaim does not handle the pagination logic, only the parameters management.

### Batch Requests

Run several route calls concurrently with `batch()` and get a partial-success result, the same way
[`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)
works: a failing call never rejects the whole batch nor blocks the other calls. Each entry of the result is either
`{status: 'fulfilled', value}` or `{status: 'rejected', reason}`.

`batch()` only orchestrates existing route calls — it does not bypass per-route protections, so caching, retry,
timeout and rate limiting configured on your routes/APIs still apply exactly as if each call had been made
individually.

Two input shapes are supported, both returning a result with the same shape as the input:

- **Named object** (recommended): keys let you destructure results by name.
- **Array**: results keep the same order/positions as the input.

Prefer thunks (`() => Klaim.api.route()`) over bare promises so the request is only fired when `batch()` runs.

```typescript
import { batch, Klaim } from "klaim";

// Named form
const {todos, user} = await batch({
    todos: () => Klaim.hello.listTodos(),
    user: () => Klaim.hello.getUser({id: 1})
});

if (todos.status === "fulfilled") console.log(todos.value);
if (user.status === "rejected") console.error(user.reason);

// Array form
const [todosResult, userResult] = await batch([
    () => Klaim.hello.listTodos(),
    () => Klaim.hello.getUser({id: 1})
]);
```

## 🔗 Links

- [NPM](https://www.npmjs.com/package/klaim)
- [JSR](https://jsr.io/@antharuu/klaim)
- [GitHub](https://github.com/antharuu/klaim)

## 📢 Project Status

Klaim is now considered **feature complete**. The library has reached a state where it provides all the core functionality originally envisioned *(even more)*, and no new features are currently planned.

However, this doesn't mean the project is abandoned:

- **Bug fixes and maintenance** will continue to be addressed
- **Issues** remain open for bug reports and suggestions
- **Pull requests** are welcome if you'd like to contribute additional features or improvements

If you have ideas for new features that would enhance Klaim, please feel free to open an issue to discuss them. Collaborative contributions through pull requests are especially appreciated!

Thank you for your interest in this project. I personally will be moving on to focus on other libraries, but I'm grateful for all the support and feedback the community has provided.

## 🤝 Contributing

Contributions are welcome! Please see the [Contributing Guide](CONTRIBUTING.md) for more details.

## 📜 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

---
_Source: https://npm.io/package/klaim · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
