npm package: @indiekitai/throttled
Description: Rate limiting for TypeScript/Node.js - 5 algorithms, memory & Redis stores
Latest version: 0.1.0
Deprecated: Deprecated: functionality consolidated into @indiekitai/pg-dash. See https://github.com/indiekitai/pg-dash

# @indiekit/throttled

[![npm version](https://img.shields.io/npm/v/@indiekit/throttled)](https://www.npmjs.com/package/@indiekit/throttled)
[![license](https://img.shields.io/npm/l/@indiekit/throttled)](https://github.com/indiekitai/throttled/blob/main/LICENSE)
[![tests](https://img.shields.io/github/actions/workflow/status/indiekitai/throttled/test.yml?label=tests)](https://github.com/indiekitai/throttled/actions)

Lightweight, zero-dependency rate limiting for TypeScript/Node.js.

- **5 algorithms** — Token Bucket, Leaky Bucket, Fixed Window, Sliding Window, GCRA
- **2 stores** — Memory (built-in LRU) and Redis (via ioredis)
- **Express middleware** — drop-in `429` handling with standard headers
- **MCP server** — expose rate limiting as AI-agent tools
- **CLI** — test rate limits from the terminal with JSON output
- **TypeScript-first** — full type exports, JSDoc on all public APIs

## Install

```bash
npm install @indiekit/throttled
```

For Redis support:

```bash
npm install @indiekit/throttled ioredis
```

## Quick Start

```ts
import { Throttled } from '@indiekit/throttled';

const limiter = new Throttled({
  algorithm: 'token-bucket',
  limit: 10,
  period: '1s',
});

const result = limiter.allow('user:123');
if (result.limited) {
  console.log(`Rate limited. Retry after ${result.state.retryAfter}s`);
} else {
  console.log(`Allowed. ${result.state.remaining} remaining`);
}
```

## Algorithms

### Token Bucket

Tokens refill at a steady rate. Good for APIs allowing short bursts.

```ts
const limiter = new Throttled({
  algorithm: 'token-bucket',
  limit: 100,    // 100 tokens max
  period: '1m',  // refill 100 tokens per minute
  burst: 150,    // allow bursts up to 150
});
```

**Use when:** You want to allow short bursts while enforcing an average rate. Most common choice for API rate limiting.

### Leaky Bucket

Requests drain at a fixed rate. Smooths out traffic spikes.

```ts
const limiter = new Throttled({
  algorithm: 'leaky-bucket',
  limit: 10,
  period: '1s',
});
```

**Use when:** You need smooth, steady throughput — e.g. sending messages to an external API that can't handle bursts.

### Fixed Window

Counts requests in fixed time windows. Simple and predictable.

```ts
const limiter = new Throttled({
  algorithm: 'fixed-window',
  limit: 1000,
  period: '1h',
});
```

**Use when:** Simple counting is enough and you don't mind a burst at window boundaries. Good for billing/quota enforcement.

### Sliding Window

Combines current and previous window with weighted counting. Eliminates boundary burst issues.

```ts
const limiter = new Throttled({
  algorithm: 'sliding-window',
  limit: 100,
  period: '1m',
});
```

**Use when:** You want fixed-window simplicity with better accuracy at window edges.

### GCRA (Generic Cell Rate Algorithm)

Single-counter algorithm equivalent to a virtual scheduling approach. Memory-efficient.

```ts
const limiter = new Throttled({
  algorithm: 'gcra',
  limit: 10,
  period: '1s',
});
```

**Use when:** You want the most memory-efficient algorithm (1 value per key). Great for distributed systems.

## Express Middleware

```ts
import express from 'express';
import { throttleMiddleware } from '@indiekit/throttled/express';

const app = express();

app.use(throttleMiddleware({
  algorithm: 'sliding-window',
  limit: 100,
  period: '1m',
  keyFn: (req) => req.ip,
}));

app.get('/api/data', (req, res) => {
  res.json({ ok: true });
});
```

Sets standard headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After` (on 429).

## Redis Store

```ts
import Redis from 'ioredis';
import { Throttled, RedisStore } from '@indiekit/throttled';

const redis = new Redis();
const limiter = new Throttled({
  algorithm: 'token-bucket',
  limit: 100,
  period: '1m',
  store: new RedisStore(redis),
});
```

## CLI

Test rate limits from your terminal:

```bash
npx @indiekit/throttled test --algorithm token-bucket --limit 10 --period 1s
```

Options:

| Flag | Default | Description |
|------|---------|-------------|
| `--algorithm` | `token-bucket` | Algorithm to use |
| `--limit` | `10` | Max requests per period |
| `--period` | `1s` | Time period |
| `--key` | `test` | Rate limit key |
| `--requests` | `1` | Number of requests to simulate |
| `--cost` | `1` | Cost per request |

Output is JSON. Exit code `0` = allowed, `1` = rate limited.

```json
{
  "limited": false,
  "state": {
    "limit": 10,
    "remaining": 9,
    "resetAfter": 1,
    "retryAfter": 0
  }
}
```

## MCP Server

Expose rate limiting as AI-agent tools:

```bash
npx @indiekit/throttled mcp
```

Tools:

- **check_rate** — Check if a key is rate limited
- **get_status** — Get current state of a key
- **reset** — Reset a key's rate limit state

## API Reference

### `new Throttled(options?)`

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `algorithm` | `AlgorithmType` | `'token-bucket'` | Algorithm |
| `limit` | `number` | `60` | Requests per period |
| `period` | `PeriodString` | `'1m'` | Period (`'10s'`, `'1m'`, `'1h'`, `'1d'`) |
| `burst` | `number` | `limit` | Burst capacity |
| `store` | `Store` | `MemoryStore` | Storage backend |
| `key` | `string` | — | Default key |
| `cost` | `number` | `1` | Default cost per request |

### `throttled.allow(key?, cost?): RateLimitResult`

Check if a request is allowed, consuming `cost` from the quota.

### `throttled.peek(key?): RateLimitState`

Peek at current state without consuming quota.

### `RateLimitResult`

```ts
interface RateLimitResult {
  limited: boolean;
  state: RateLimitState;
}
```

### `RateLimitState`

```ts
interface RateLimitState {
  limit: number;       // Max requests
  remaining: number;   // Remaining in current window
  resetAfter: number;  // Seconds until full reset
  retryAfter: number;  // Seconds until retry (0 if allowed)
}
```

### Quota Helpers

```ts
import { perSec, perMin, perHour, perDay, createQuota, parsePeriod } from '@indiekit/throttled';

const q1 = perSec(10);           // 10 req/sec
const q2 = perMin(100, 150);     // 100 req/min, burst 150
const q3 = perHour(1000);        // 1000 req/hour
const q4 = createQuota({ period: parsePeriod('30s'), limit: 50 });
```

### Express Middleware

```ts
import { throttleMiddleware } from '@indiekit/throttled/express';

throttleMiddleware({
  ...throttledOptions,
  keyFn: (req) => req.ip,  // Custom key extraction
});
```

## Comparison

| Feature | @indiekit/throttled | bottleneck | p-limit | rate-limiter-flexible |
|---------|:------------------:|:----------:|:-------:|:---------------------:|
| Algorithms | 5 | 1 | 0 | 5 |
| TypeScript | ✅ native | ❌ @types | ✅ | ❌ @types |
| Memory store | ✅ LRU | ✅ | N/A | ✅ |
| Redis store | ✅ | ✅ | N/A | ✅ |
| Express middleware | ✅ | ❌ | N/A | ✅ |
| Zero deps | ✅ | ✅ | ✅ | ❌ |
| MCP server | ✅ | ❌ | ❌ | ❌ |
| CLI | ✅ | ❌ | ❌ | ❌ |
| ESM + CJS | ✅ | ✅ | ✅ | ✅ |
| Bundle size | ~5 KB | ~30 KB | ~1 KB | ~50 KB |

**bottleneck** — Job scheduler/queue with concurrency control. Different use case (job scheduling vs rate limiting).

**p-limit** — Concurrency limiter, not a rate limiter. Limits parallel execution, not requests per time window.

**rate-limiter-flexible** — Feature-rich but heavier. Good if you need Mongo/Memcached/Cluster stores. @indiekit/throttled is lighter with better TypeScript and agent tooling.

## License

MIT