@npclfg/nano-limit
v1.0.1
Published
Tiny concurrency and rate limiter with priorities, AbortSignal, and zero dependencies.
Downloads
11
Maintainers
Readme
nano-limit
Tiny concurrency and rate limiter with priorities, AbortSignal, and zero dependencies.
- Zero dependencies
- TypeScript-first with full type inference
- ESM and CommonJS support
The Problem
You need to limit API calls. You reach for p-limit but:
// p-limit: only concurrency, no rate limiting
// p-limit: can't do "max 60 requests per minute"
// p-limit: no priority queue, no AbortSignalYou try bottleneck but:
// bottleneck: complex API with reservoir, highWater, strategy...
// bottleneck: must call disconnect() or memory leaks
// bottleneck: breaks with mock timers in tests
// bottleneck: last major release was 2019The Fix
import { createLimit } from "@npclfg/nano-limit";
// Concurrency + rate limiting in one
const limit = createLimit({
concurrency: 5, // max 5 parallel
rate: 60, // max 60 per minute
interval: 60000,
});
await limit(() => openai.chat.completions.create({ model: "gpt-4", messages }));
// Priority queue: important requests go first
await limit(() => criticalOperation(), { priority: 10 });
// Cancel pending operations
await limit(() => fetch(url), { signal: controller.signal });That's it. Concurrency limiting, rate limiting, priorities, and cancellation in one tiny package.
Why nano-limit?
| Feature | p-limit | bottleneck | nano-limit | |---------|---------|------------|------------| | Dependencies | 2 | 0 | 0 | | Concurrency limiting | ✅ | ✅ | ✅ | | Rate limiting | ❌ | ✅ | ✅ | | Priority queue | ❌ | ✅ | ✅ | | AbortSignal | ❌ | ❌ | ✅ | | Per-key limiting | ❌ | Manual | ✅ Built-in | | Queue size limit | ❌ | ✅ | ✅ | | onIdle() | ❌ | ✅ | ✅ | | Memory leak risk | Low | disconnect() required | None | | ESM + CJS | ESM-only (v4+) | ✅ | ✅ | | Last updated | Active | 2019 | Active |
Installation
npm install @npclfg/nano-limitRequirements: Node.js 16+ or modern browsers (ES2020)
Quick Start
import { createLimit } from "@npclfg/nano-limit";
// Concurrency only (like p-limit)
const limit = createLimit({ concurrency: 5 });
await Promise.all(urls.map(url => limit(() => fetch(url))));
// Rate limiting (max 10 per second)
const rateLimited = createLimit({ rate: 10, interval: 1000 });
// Both together
const apiLimit = createLimit({
concurrency: 5,
rate: 60,
interval: 60000,
});API Reference
createLimit(options?): Limiter
Create a concurrency and/or rate limiter.
const limit = createLimit({
concurrency: 5, // max concurrent (default: Infinity)
rate: 100, // max per interval
interval: 1000, // interval in ms (default: 1000)
maxQueueSize: 1000 // max queued operations (default: Infinity)
});
const result = await limit(() => fetchData());Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| concurrency | number | Infinity | Maximum concurrent operations |
| rate | number | - | Maximum operations per interval |
| interval | number | 1000 | Interval in ms for rate limiting |
| maxQueueSize | number | Infinity | Max queued operations (throws QueueFullError) |
Limiter Properties
| Property | Type | Description |
|----------|------|-------------|
| activeCount | number | Currently running operations |
| pendingCount | number | Operations waiting in queue |
| clearQueue(reject?) | function | Clear pending operations |
| onIdle() | Promise<void> | Resolves when queue is empty and all done |
Operation Options
await limit(() => fn(), {
priority: 10, // higher = sooner (default: 0)
signal: controller.signal // AbortSignal for cancellation
});createKeyedLimit(options?): KeyedLimiter
Per-key rate limiting for multi-tenant systems.
const userLimit = createKeyedLimit({
concurrency: 2,
rate: 10,
interval: 1000
});
// Each user gets their own limit
await userLimit("user-123", () => fetchUserData("123"));
await userLimit("user-456", () => fetchUserData("456"));
// Manage keys
userLimit.get("user-123"); // Get limiter for key
userLimit.delete("user-123"); // Remove key
userLimit.clear(); // Remove all keys
userLimit.size; // Number of active keyslimited(fn, options?): WrappedFunction
Create a pre-configured limited function.
const fetchWithLimit = limited(
(url: string) => fetch(url),
{ concurrency: 5, rate: 10, interval: 1000 }
);
await fetchWithLimit("/api/data");Error Types
import { AbortError, QueueClearedError, QueueFullError } from "nano-limit";
try {
await limit(() => fn(), { signal });
} catch (error) {
if (error instanceof AbortError) {
// Operation was aborted
}
if (error instanceof QueueClearedError) {
// clearQueue() was called
}
if (error instanceof QueueFullError) {
// maxQueueSize exceeded
}
}Patterns & Recipes
OpenAI/Anthropic Rate Limiting
const aiLimit = createLimit({
concurrency: 5, // parallel requests
rate: 60, // 60 RPM
interval: 60000,
});
const response = await aiLimit(() =>
openai.chat.completions.create({
model: "gpt-4",
messages,
})
);Priority Queue
const limit = createLimit({ concurrency: 1 });
// High priority runs first
limit(() => lowPriorityTask());
limit(() => criticalTask(), { priority: 10 }); // Runs firstGraceful Shutdown
const limit = createLimit({ concurrency: 10 });
// On shutdown
process.on("SIGTERM", async () => {
limit.clearQueue(); // Cancel pending
await limit.onIdle(); // Wait for active to finish
process.exit(0);
});With Timeout
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
await limit(() => slowOperation(), { signal: controller.signal });
} catch (error) {
if (error instanceof AbortError) {
console.log("Timed out");
}
}Backpressure / Queue Overflow
const limit = createLimit({
concurrency: 5,
maxQueueSize: 100, // Reject if queue grows too large
});
try {
await limit(() => processRequest());
} catch (error) {
if (error instanceof QueueFullError) {
// Return 503 Service Unavailable
}
}Multi-Tenant API
const userLimit = createKeyedLimit({
concurrency: 2,
rate: 100,
interval: 60000,
});
app.use(async (req, res, next) => {
try {
await userLimit(req.user.id, () => next());
} catch (error) {
if (error instanceof QueueFullError) {
res.status(429).send("Too Many Requests");
}
}
});TypeScript Usage
Full type inference is supported:
import { createLimit, LimitOptions, Limiter } from "nano-limit";
interface ApiResponse {
data: string[];
}
// Return type is inferred as Promise<ApiResponse>
const result = await limit(async (): Promise<ApiResponse> => {
const res = await fetch("/api");
return res.json();
});License
MIT
