Tiny concurrency and rate limiter with priorities, AbortSignal, and zero dependencies.
- Zero dependencies
- TypeScript-first with full type inference
- ESM and CommonJS support
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 2019import { 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.
| 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 |
npm install @npclfg/nano-limitRequirements: Node.js 16+ or modern browsers (ES2020)
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,
});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());| 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) |
| 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 |
await limit(() => fn(), {
priority: 10, // higher = sooner (default: 0)
signal: controller.signal // AbortSignal for cancellation
});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 keysCreate a pre-configured limited function.
const fetchWithLimit = limited(
(url: string) => fetch(url),
{ concurrency: 5, rate: 10, interval: 1000 }
);
await fetchWithLimit("/api/data");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
}
}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,
})
);const limit = createLimit({ concurrency: 1 });
// High priority runs first
limit(() => lowPriorityTask());
limit(() => criticalTask(), { priority: 10 }); // Runs firstconst 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);
});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");
}
}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
}
}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");
}
}
});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();
});MIT