@npclfg/nano-limit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/dist/cjs/limit.d.ts +148 -0
- package/dist/cjs/limit.d.ts.map +1 -0
- package/dist/cjs/limit.js +317 -0
- package/dist/cjs/limit.js.map +1 -0
- package/dist/esm/limit.js +308 -0
- package/dist/esm/limit.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 npclfg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# nano-limit
|
|
2
|
+
|
|
3
|
+
[](https://npmjs.com/package/@npclfg/nano-limit)
|
|
4
|
+
[](https://npmjs.com/package/@npclfg/nano-limit)
|
|
5
|
+
[](https://github.com/npclfg/nano-limit/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Tiny concurrency and rate limiter with priorities, AbortSignal, and zero dependencies.
|
|
8
|
+
|
|
9
|
+
- **Zero dependencies**
|
|
10
|
+
- **TypeScript-first** with full type inference
|
|
11
|
+
- **ESM and CommonJS** support
|
|
12
|
+
|
|
13
|
+
## The Problem
|
|
14
|
+
|
|
15
|
+
You need to limit API calls. You reach for p-limit but:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// p-limit: only concurrency, no rate limiting
|
|
19
|
+
// p-limit: can't do "max 60 requests per minute"
|
|
20
|
+
// p-limit: no priority queue, no AbortSignal
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
You try bottleneck but:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// bottleneck: complex API with reservoir, highWater, strategy...
|
|
27
|
+
// bottleneck: must call disconnect() or memory leaks
|
|
28
|
+
// bottleneck: breaks with mock timers in tests
|
|
29
|
+
// bottleneck: last major release was 2019
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## The Fix
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { createLimit } from "@npclfg/nano-limit";
|
|
36
|
+
|
|
37
|
+
// Concurrency + rate limiting in one
|
|
38
|
+
const limit = createLimit({
|
|
39
|
+
concurrency: 5, // max 5 parallel
|
|
40
|
+
rate: 60, // max 60 per minute
|
|
41
|
+
interval: 60000,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await limit(() => openai.chat.completions.create({ model: "gpt-4", messages }));
|
|
45
|
+
|
|
46
|
+
// Priority queue: important requests go first
|
|
47
|
+
await limit(() => criticalOperation(), { priority: 10 });
|
|
48
|
+
|
|
49
|
+
// Cancel pending operations
|
|
50
|
+
await limit(() => fetch(url), { signal: controller.signal });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
That's it. Concurrency limiting, rate limiting, priorities, and cancellation in one tiny package.
|
|
54
|
+
|
|
55
|
+
## Why nano-limit?
|
|
56
|
+
|
|
57
|
+
| Feature | p-limit | bottleneck | nano-limit |
|
|
58
|
+
|---------|---------|------------|------------|
|
|
59
|
+
| Dependencies | 2 | 0 | **0** |
|
|
60
|
+
| Concurrency limiting | ✅ | ✅ | ✅ |
|
|
61
|
+
| Rate limiting | ❌ | ✅ | ✅ |
|
|
62
|
+
| Priority queue | ❌ | ✅ | ✅ |
|
|
63
|
+
| AbortSignal | ❌ | ❌ | ✅ |
|
|
64
|
+
| Per-key limiting | ❌ | Manual | ✅ Built-in |
|
|
65
|
+
| Queue size limit | ❌ | ✅ | ✅ |
|
|
66
|
+
| onIdle() | ❌ | ✅ | ✅ |
|
|
67
|
+
| Memory leak risk | Low | disconnect() required | **None** |
|
|
68
|
+
| ESM + CJS | ESM-only (v4+) | ✅ | ✅ |
|
|
69
|
+
| Last updated | Active | 2019 | Active |
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install @npclfg/nano-limit
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Requirements:** Node.js 16+ or modern browsers (ES2020)
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { createLimit } from "@npclfg/nano-limit";
|
|
83
|
+
|
|
84
|
+
// Concurrency only (like p-limit)
|
|
85
|
+
const limit = createLimit({ concurrency: 5 });
|
|
86
|
+
await Promise.all(urls.map(url => limit(() => fetch(url))));
|
|
87
|
+
|
|
88
|
+
// Rate limiting (max 10 per second)
|
|
89
|
+
const rateLimited = createLimit({ rate: 10, interval: 1000 });
|
|
90
|
+
|
|
91
|
+
// Both together
|
|
92
|
+
const apiLimit = createLimit({
|
|
93
|
+
concurrency: 5,
|
|
94
|
+
rate: 60,
|
|
95
|
+
interval: 60000,
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API Reference
|
|
100
|
+
|
|
101
|
+
### `createLimit(options?): Limiter`
|
|
102
|
+
|
|
103
|
+
Create a concurrency and/or rate limiter.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const limit = createLimit({
|
|
107
|
+
concurrency: 5, // max concurrent (default: Infinity)
|
|
108
|
+
rate: 100, // max per interval
|
|
109
|
+
interval: 1000, // interval in ms (default: 1000)
|
|
110
|
+
maxQueueSize: 1000 // max queued operations (default: Infinity)
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const result = await limit(() => fetchData());
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Options
|
|
117
|
+
|
|
118
|
+
| Option | Type | Default | Description |
|
|
119
|
+
|--------|------|---------|-------------|
|
|
120
|
+
| `concurrency` | `number` | `Infinity` | Maximum concurrent operations |
|
|
121
|
+
| `rate` | `number` | - | Maximum operations per interval |
|
|
122
|
+
| `interval` | `number` | `1000` | Interval in ms for rate limiting |
|
|
123
|
+
| `maxQueueSize` | `number` | `Infinity` | Max queued operations (throws QueueFullError) |
|
|
124
|
+
|
|
125
|
+
#### Limiter Properties
|
|
126
|
+
|
|
127
|
+
| Property | Type | Description |
|
|
128
|
+
|----------|------|-------------|
|
|
129
|
+
| `activeCount` | `number` | Currently running operations |
|
|
130
|
+
| `pendingCount` | `number` | Operations waiting in queue |
|
|
131
|
+
| `clearQueue(reject?)` | `function` | Clear pending operations |
|
|
132
|
+
| `onIdle()` | `Promise<void>` | Resolves when queue is empty and all done |
|
|
133
|
+
|
|
134
|
+
### Operation Options
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
await limit(() => fn(), {
|
|
138
|
+
priority: 10, // higher = sooner (default: 0)
|
|
139
|
+
signal: controller.signal // AbortSignal for cancellation
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `createKeyedLimit(options?): KeyedLimiter`
|
|
144
|
+
|
|
145
|
+
Per-key rate limiting for multi-tenant systems.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const userLimit = createKeyedLimit({
|
|
149
|
+
concurrency: 2,
|
|
150
|
+
rate: 10,
|
|
151
|
+
interval: 1000
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Each user gets their own limit
|
|
155
|
+
await userLimit("user-123", () => fetchUserData("123"));
|
|
156
|
+
await userLimit("user-456", () => fetchUserData("456"));
|
|
157
|
+
|
|
158
|
+
// Manage keys
|
|
159
|
+
userLimit.get("user-123"); // Get limiter for key
|
|
160
|
+
userLimit.delete("user-123"); // Remove key
|
|
161
|
+
userLimit.clear(); // Remove all keys
|
|
162
|
+
userLimit.size; // Number of active keys
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `limited(fn, options?): WrappedFunction`
|
|
166
|
+
|
|
167
|
+
Create a pre-configured limited function.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const fetchWithLimit = limited(
|
|
171
|
+
(url: string) => fetch(url),
|
|
172
|
+
{ concurrency: 5, rate: 10, interval: 1000 }
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
await fetchWithLimit("/api/data");
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Error Types
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { AbortError, QueueClearedError, QueueFullError } from "nano-limit";
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
await limit(() => fn(), { signal });
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error instanceof AbortError) {
|
|
187
|
+
// Operation was aborted
|
|
188
|
+
}
|
|
189
|
+
if (error instanceof QueueClearedError) {
|
|
190
|
+
// clearQueue() was called
|
|
191
|
+
}
|
|
192
|
+
if (error instanceof QueueFullError) {
|
|
193
|
+
// maxQueueSize exceeded
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Patterns & Recipes
|
|
199
|
+
|
|
200
|
+
### OpenAI/Anthropic Rate Limiting
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const aiLimit = createLimit({
|
|
204
|
+
concurrency: 5, // parallel requests
|
|
205
|
+
rate: 60, // 60 RPM
|
|
206
|
+
interval: 60000,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const response = await aiLimit(() =>
|
|
210
|
+
openai.chat.completions.create({
|
|
211
|
+
model: "gpt-4",
|
|
212
|
+
messages,
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Priority Queue
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const limit = createLimit({ concurrency: 1 });
|
|
221
|
+
|
|
222
|
+
// High priority runs first
|
|
223
|
+
limit(() => lowPriorityTask());
|
|
224
|
+
limit(() => criticalTask(), { priority: 10 }); // Runs first
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Graceful Shutdown
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const limit = createLimit({ concurrency: 10 });
|
|
231
|
+
|
|
232
|
+
// On shutdown
|
|
233
|
+
process.on("SIGTERM", async () => {
|
|
234
|
+
limit.clearQueue(); // Cancel pending
|
|
235
|
+
await limit.onIdle(); // Wait for active to finish
|
|
236
|
+
process.exit(0);
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### With Timeout
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const controller = new AbortController();
|
|
244
|
+
setTimeout(() => controller.abort(), 5000);
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
await limit(() => slowOperation(), { signal: controller.signal });
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error instanceof AbortError) {
|
|
250
|
+
console.log("Timed out");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Backpressure / Queue Overflow
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const limit = createLimit({
|
|
259
|
+
concurrency: 5,
|
|
260
|
+
maxQueueSize: 100, // Reject if queue grows too large
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
await limit(() => processRequest());
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error instanceof QueueFullError) {
|
|
267
|
+
// Return 503 Service Unavailable
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Multi-Tenant API
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const userLimit = createKeyedLimit({
|
|
276
|
+
concurrency: 2,
|
|
277
|
+
rate: 100,
|
|
278
|
+
interval: 60000,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
app.use(async (req, res, next) => {
|
|
282
|
+
try {
|
|
283
|
+
await userLimit(req.user.id, () => next());
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (error instanceof QueueFullError) {
|
|
286
|
+
res.status(429).send("Too Many Requests");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## TypeScript Usage
|
|
293
|
+
|
|
294
|
+
Full type inference is supported:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { createLimit, LimitOptions, Limiter } from "nano-limit";
|
|
298
|
+
|
|
299
|
+
interface ApiResponse {
|
|
300
|
+
data: string[];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Return type is inferred as Promise<ApiResponse>
|
|
304
|
+
const result = await limit(async (): Promise<ApiResponse> => {
|
|
305
|
+
const res = await fetch("/api");
|
|
306
|
+
return res.json();
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when a queued operation is aborted.
|
|
3
|
+
*/
|
|
4
|
+
export declare class AbortError extends Error {
|
|
5
|
+
readonly isLimitAbort = true;
|
|
6
|
+
constructor(message?: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Error thrown when the queue is cleared.
|
|
10
|
+
*/
|
|
11
|
+
export declare class QueueClearedError extends Error {
|
|
12
|
+
readonly isQueueCleared = true;
|
|
13
|
+
constructor(message?: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Error thrown when the queue is full.
|
|
17
|
+
*/
|
|
18
|
+
export declare class QueueFullError extends Error {
|
|
19
|
+
readonly isQueueFull = true;
|
|
20
|
+
constructor(message?: string);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Options for creating a limiter.
|
|
24
|
+
*/
|
|
25
|
+
export interface LimitOptions {
|
|
26
|
+
/** Maximum concurrent operations. Default: Infinity */
|
|
27
|
+
concurrency?: number;
|
|
28
|
+
/** Maximum operations per interval (rate limiting). */
|
|
29
|
+
rate?: number;
|
|
30
|
+
/** Interval in ms for rate limiting. Default: 1000 */
|
|
31
|
+
interval?: number;
|
|
32
|
+
/** Maximum queue size. Throws QueueFullError when exceeded. Default: Infinity */
|
|
33
|
+
maxQueueSize?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Options for a single operation.
|
|
37
|
+
*/
|
|
38
|
+
export interface OperationOptions {
|
|
39
|
+
/** Priority (higher = sooner). Default: 0 */
|
|
40
|
+
priority?: number;
|
|
41
|
+
/** AbortSignal to cancel the operation. */
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A limiter function that controls concurrency and rate.
|
|
46
|
+
*/
|
|
47
|
+
export interface Limiter {
|
|
48
|
+
/**
|
|
49
|
+
* Execute a function with limiting applied.
|
|
50
|
+
*/
|
|
51
|
+
<T>(fn: () => T | Promise<T>, options?: OperationOptions): Promise<T>;
|
|
52
|
+
/** Number of operations currently running. */
|
|
53
|
+
readonly activeCount: number;
|
|
54
|
+
/** Number of operations waiting in the queue. */
|
|
55
|
+
readonly pendingCount: number;
|
|
56
|
+
/**
|
|
57
|
+
* Clear all pending operations from the queue.
|
|
58
|
+
* @param reject If true (default), reject pending promises with QueueClearedError.
|
|
59
|
+
*/
|
|
60
|
+
clearQueue(reject?: boolean): void;
|
|
61
|
+
/**
|
|
62
|
+
* Returns a promise that resolves when the queue is empty and all operations are complete.
|
|
63
|
+
*/
|
|
64
|
+
onIdle(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a concurrency and/or rate limiter.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Configuration options.
|
|
70
|
+
* @returns A limiter function.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* // Concurrency only (like p-limit)
|
|
75
|
+
* const limit = createLimit({ concurrency: 5 });
|
|
76
|
+
* await limit(() => fetch(url));
|
|
77
|
+
*
|
|
78
|
+
* // Rate limiting (max 10 per second)
|
|
79
|
+
* const limit = createLimit({ rate: 10, interval: 1000 });
|
|
80
|
+
*
|
|
81
|
+
* // Both
|
|
82
|
+
* const limit = createLimit({ concurrency: 5, rate: 60, interval: 60000 });
|
|
83
|
+
*
|
|
84
|
+
* // With priority
|
|
85
|
+
* await limit(() => importantCall(), { priority: 10 });
|
|
86
|
+
*
|
|
87
|
+
* // With AbortSignal
|
|
88
|
+
* await limit(() => fetch(url), { signal: controller.signal });
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function createLimit(options?: LimitOptions): Limiter;
|
|
92
|
+
/**
|
|
93
|
+
* Create a pre-limited version of a function.
|
|
94
|
+
*
|
|
95
|
+
* @param fn - The function to wrap.
|
|
96
|
+
* @param options - Limit options.
|
|
97
|
+
* @returns A function that applies limiting.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* const fetchWithLimit = limited(
|
|
102
|
+
* (url: string) => fetch(url),
|
|
103
|
+
* { concurrency: 5 }
|
|
104
|
+
* );
|
|
105
|
+
* await fetchWithLimit("/api/data");
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export declare function limited<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => TResult | Promise<TResult>, options?: LimitOptions): (...args: TArgs) => Promise<TResult>;
|
|
109
|
+
/**
|
|
110
|
+
* A keyed limiter for per-key rate limiting.
|
|
111
|
+
*/
|
|
112
|
+
export interface KeyedLimiter {
|
|
113
|
+
/**
|
|
114
|
+
* Execute a function with limiting applied to a specific key.
|
|
115
|
+
*/
|
|
116
|
+
<T>(key: string, fn: () => T | Promise<T>, options?: OperationOptions): Promise<T>;
|
|
117
|
+
/**
|
|
118
|
+
* Get the limiter for a specific key.
|
|
119
|
+
*/
|
|
120
|
+
get(key: string): Limiter;
|
|
121
|
+
/**
|
|
122
|
+
* Delete a limiter for a specific key.
|
|
123
|
+
*/
|
|
124
|
+
delete(key: string): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Clear all limiters.
|
|
127
|
+
*/
|
|
128
|
+
clear(): void;
|
|
129
|
+
/** Number of active keys. */
|
|
130
|
+
readonly size: number;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a keyed limiter for per-key rate limiting.
|
|
134
|
+
*
|
|
135
|
+
* @param options - Limit options applied to each key.
|
|
136
|
+
* @returns A keyed limiter.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* const userLimit = createKeyedLimit({ concurrency: 2, rate: 10, interval: 1000 });
|
|
141
|
+
*
|
|
142
|
+
* // Each user gets their own limit
|
|
143
|
+
* await userLimit("user-123", () => fetchUserData("123"));
|
|
144
|
+
* await userLimit("user-456", () => fetchUserData("456"));
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export declare function createKeyedLimit(options?: LimitOptions): KeyedLimiter;
|
|
148
|
+
//# sourceMappingURL=limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limit.d.ts","sourceRoot":"","sources":["../../src/limit.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,YAAY,QAAQ;gBACjB,OAAO,SAA0B;CAI9C;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,cAAc,QAAQ;gBACnB,OAAO,SAAsB;CAI1C;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,WAAW,QAAQ;gBAChB,OAAO,SAAkB;CAItC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAWD;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACtE,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iDAAiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAoO/D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EACtD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAClD,OAAO,GAAE,YAAiB,GACzB,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAGtC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACnF;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;IACd,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,YAAiB,GAAG,YAAY,CA2CzE"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueueFullError = exports.QueueClearedError = exports.AbortError = void 0;
|
|
4
|
+
exports.createLimit = createLimit;
|
|
5
|
+
exports.limited = limited;
|
|
6
|
+
exports.createKeyedLimit = createKeyedLimit;
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when a queued operation is aborted.
|
|
9
|
+
*/
|
|
10
|
+
class AbortError extends Error {
|
|
11
|
+
constructor(message = "Operation was aborted") {
|
|
12
|
+
super(message);
|
|
13
|
+
this.isLimitAbort = true;
|
|
14
|
+
this.name = "AbortError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.AbortError = AbortError;
|
|
18
|
+
/**
|
|
19
|
+
* Error thrown when the queue is cleared.
|
|
20
|
+
*/
|
|
21
|
+
class QueueClearedError extends Error {
|
|
22
|
+
constructor(message = "Queue was cleared") {
|
|
23
|
+
super(message);
|
|
24
|
+
this.isQueueCleared = true;
|
|
25
|
+
this.name = "QueueClearedError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.QueueClearedError = QueueClearedError;
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown when the queue is full.
|
|
31
|
+
*/
|
|
32
|
+
class QueueFullError extends Error {
|
|
33
|
+
constructor(message = "Queue is full") {
|
|
34
|
+
super(message);
|
|
35
|
+
this.isQueueFull = true;
|
|
36
|
+
this.name = "QueueFullError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.QueueFullError = QueueFullError;
|
|
40
|
+
/**
|
|
41
|
+
* Create a concurrency and/or rate limiter.
|
|
42
|
+
*
|
|
43
|
+
* @param options - Configuration options.
|
|
44
|
+
* @returns A limiter function.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* // Concurrency only (like p-limit)
|
|
49
|
+
* const limit = createLimit({ concurrency: 5 });
|
|
50
|
+
* await limit(() => fetch(url));
|
|
51
|
+
*
|
|
52
|
+
* // Rate limiting (max 10 per second)
|
|
53
|
+
* const limit = createLimit({ rate: 10, interval: 1000 });
|
|
54
|
+
*
|
|
55
|
+
* // Both
|
|
56
|
+
* const limit = createLimit({ concurrency: 5, rate: 60, interval: 60000 });
|
|
57
|
+
*
|
|
58
|
+
* // With priority
|
|
59
|
+
* await limit(() => importantCall(), { priority: 10 });
|
|
60
|
+
*
|
|
61
|
+
* // With AbortSignal
|
|
62
|
+
* await limit(() => fetch(url), { signal: controller.signal });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
function createLimit(options = {}) {
|
|
66
|
+
const { concurrency = Infinity, rate, interval = 1000, maxQueueSize = Infinity, } = options;
|
|
67
|
+
// Validate options
|
|
68
|
+
if (concurrency !== Infinity && (concurrency <= 0 || !Number.isInteger(concurrency))) {
|
|
69
|
+
throw new TypeError("concurrency must be a positive integer or Infinity");
|
|
70
|
+
}
|
|
71
|
+
if (rate !== undefined && (rate <= 0 || !Number.isInteger(rate))) {
|
|
72
|
+
throw new TypeError("rate must be a positive integer");
|
|
73
|
+
}
|
|
74
|
+
if (interval <= 0) {
|
|
75
|
+
throw new TypeError("interval must be positive");
|
|
76
|
+
}
|
|
77
|
+
if (maxQueueSize !== Infinity && (maxQueueSize <= 0 || !Number.isInteger(maxQueueSize))) {
|
|
78
|
+
throw new TypeError("maxQueueSize must be a positive integer or Infinity");
|
|
79
|
+
}
|
|
80
|
+
const queue = [];
|
|
81
|
+
let activeCount = 0;
|
|
82
|
+
let idleResolvers = [];
|
|
83
|
+
// Token bucket for rate limiting
|
|
84
|
+
let tokens = rate ?? Infinity;
|
|
85
|
+
let lastRefill = Date.now();
|
|
86
|
+
let refillTimeoutId = null;
|
|
87
|
+
function refillTokens() {
|
|
88
|
+
if (rate === undefined)
|
|
89
|
+
return;
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const elapsed = now - lastRefill;
|
|
92
|
+
if (elapsed >= interval) {
|
|
93
|
+
// Full refill
|
|
94
|
+
const periods = Math.floor(elapsed / interval);
|
|
95
|
+
tokens = Math.min(rate, tokens + rate * periods);
|
|
96
|
+
lastRefill += periods * interval;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function scheduleRefill() {
|
|
100
|
+
if (rate === undefined || refillTimeoutId !== null)
|
|
101
|
+
return;
|
|
102
|
+
if (queue.length === 0)
|
|
103
|
+
return;
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
const timeSinceLastRefill = now - lastRefill;
|
|
106
|
+
const timeUntilRefill = Math.max(0, interval - timeSinceLastRefill);
|
|
107
|
+
refillTimeoutId = setTimeout(() => {
|
|
108
|
+
refillTimeoutId = null;
|
|
109
|
+
refillTokens();
|
|
110
|
+
processQueue();
|
|
111
|
+
}, timeUntilRefill);
|
|
112
|
+
}
|
|
113
|
+
function checkIdle() {
|
|
114
|
+
if (activeCount === 0 && queue.length === 0 && idleResolvers.length > 0) {
|
|
115
|
+
const resolvers = idleResolvers;
|
|
116
|
+
idleResolvers = [];
|
|
117
|
+
for (const resolve of resolvers) {
|
|
118
|
+
resolve();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function processQueue() {
|
|
123
|
+
refillTokens();
|
|
124
|
+
while (queue.length > 0 && activeCount < concurrency && tokens > 0) {
|
|
125
|
+
const op = queue.shift();
|
|
126
|
+
// Remove abort listener since we're about to execute
|
|
127
|
+
if (op.onAbort && op.signal) {
|
|
128
|
+
op.signal.removeEventListener("abort", op.onAbort);
|
|
129
|
+
}
|
|
130
|
+
// Check if already aborted
|
|
131
|
+
if (op.signal?.aborted) {
|
|
132
|
+
op.reject(new AbortError());
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
activeCount++;
|
|
136
|
+
if (rate !== undefined) {
|
|
137
|
+
tokens--;
|
|
138
|
+
}
|
|
139
|
+
Promise.resolve()
|
|
140
|
+
.then(() => op.fn())
|
|
141
|
+
.then((result) => {
|
|
142
|
+
activeCount--;
|
|
143
|
+
op.resolve(result);
|
|
144
|
+
processQueue();
|
|
145
|
+
checkIdle();
|
|
146
|
+
}, (error) => {
|
|
147
|
+
activeCount--;
|
|
148
|
+
op.reject(error);
|
|
149
|
+
processQueue();
|
|
150
|
+
checkIdle();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Schedule refill if we're rate limited and have pending work
|
|
154
|
+
if (queue.length > 0 && tokens <= 0) {
|
|
155
|
+
scheduleRefill();
|
|
156
|
+
}
|
|
157
|
+
// Check if we're idle
|
|
158
|
+
checkIdle();
|
|
159
|
+
}
|
|
160
|
+
function enqueue(fn, options = {}) {
|
|
161
|
+
// Validate fn
|
|
162
|
+
if (typeof fn !== "function") {
|
|
163
|
+
throw new TypeError("fn must be a function");
|
|
164
|
+
}
|
|
165
|
+
const { priority = 0, signal } = options;
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
// Check if already aborted
|
|
168
|
+
if (signal?.aborted) {
|
|
169
|
+
reject(new AbortError());
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Check queue size
|
|
173
|
+
if (queue.length >= maxQueueSize) {
|
|
174
|
+
reject(new QueueFullError());
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const op = {
|
|
178
|
+
fn,
|
|
179
|
+
priority,
|
|
180
|
+
resolve: resolve,
|
|
181
|
+
reject,
|
|
182
|
+
signal,
|
|
183
|
+
};
|
|
184
|
+
// Set up abort handler
|
|
185
|
+
if (signal) {
|
|
186
|
+
op.onAbort = () => {
|
|
187
|
+
const index = queue.indexOf(op);
|
|
188
|
+
if (index !== -1) {
|
|
189
|
+
queue.splice(index, 1);
|
|
190
|
+
reject(new AbortError());
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
signal.addEventListener("abort", op.onAbort, { once: true });
|
|
194
|
+
}
|
|
195
|
+
// Insert in priority order (higher priority first)
|
|
196
|
+
let inserted = false;
|
|
197
|
+
for (let i = 0; i < queue.length; i++) {
|
|
198
|
+
if (priority > queue[i].priority) {
|
|
199
|
+
queue.splice(i, 0, op);
|
|
200
|
+
inserted = true;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!inserted) {
|
|
205
|
+
queue.push(op);
|
|
206
|
+
}
|
|
207
|
+
processQueue();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function clearQueue(reject = true) {
|
|
211
|
+
// Cancel refill timer
|
|
212
|
+
if (refillTimeoutId !== null) {
|
|
213
|
+
clearTimeout(refillTimeoutId);
|
|
214
|
+
refillTimeoutId = null;
|
|
215
|
+
}
|
|
216
|
+
// Process all queued items
|
|
217
|
+
while (queue.length > 0) {
|
|
218
|
+
const op = queue.shift();
|
|
219
|
+
// Clean up abort listener
|
|
220
|
+
if (op.onAbort && op.signal) {
|
|
221
|
+
op.signal.removeEventListener("abort", op.onAbort);
|
|
222
|
+
}
|
|
223
|
+
if (reject) {
|
|
224
|
+
op.reject(new QueueClearedError());
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Check if we're now idle
|
|
228
|
+
checkIdle();
|
|
229
|
+
}
|
|
230
|
+
function onIdle() {
|
|
231
|
+
if (activeCount === 0 && queue.length === 0) {
|
|
232
|
+
return Promise.resolve();
|
|
233
|
+
}
|
|
234
|
+
return new Promise((resolve) => {
|
|
235
|
+
idleResolvers.push(resolve);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const limiter = enqueue;
|
|
239
|
+
Object.defineProperty(limiter, "activeCount", {
|
|
240
|
+
get: () => activeCount,
|
|
241
|
+
});
|
|
242
|
+
Object.defineProperty(limiter, "pendingCount", {
|
|
243
|
+
get: () => queue.length,
|
|
244
|
+
});
|
|
245
|
+
limiter.clearQueue = clearQueue;
|
|
246
|
+
limiter.onIdle = onIdle;
|
|
247
|
+
return limiter;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Create a pre-limited version of a function.
|
|
251
|
+
*
|
|
252
|
+
* @param fn - The function to wrap.
|
|
253
|
+
* @param options - Limit options.
|
|
254
|
+
* @returns A function that applies limiting.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```typescript
|
|
258
|
+
* const fetchWithLimit = limited(
|
|
259
|
+
* (url: string) => fetch(url),
|
|
260
|
+
* { concurrency: 5 }
|
|
261
|
+
* );
|
|
262
|
+
* await fetchWithLimit("/api/data");
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
function limited(fn, options = {}) {
|
|
266
|
+
const limit = createLimit(options);
|
|
267
|
+
return (...args) => limit(() => fn(...args));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Create a keyed limiter for per-key rate limiting.
|
|
271
|
+
*
|
|
272
|
+
* @param options - Limit options applied to each key.
|
|
273
|
+
* @returns A keyed limiter.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```typescript
|
|
277
|
+
* const userLimit = createKeyedLimit({ concurrency: 2, rate: 10, interval: 1000 });
|
|
278
|
+
*
|
|
279
|
+
* // Each user gets their own limit
|
|
280
|
+
* await userLimit("user-123", () => fetchUserData("123"));
|
|
281
|
+
* await userLimit("user-456", () => fetchUserData("456"));
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
function createKeyedLimit(options = {}) {
|
|
285
|
+
const limiters = new Map();
|
|
286
|
+
function getOrCreate(key) {
|
|
287
|
+
let limiter = limiters.get(key);
|
|
288
|
+
if (!limiter) {
|
|
289
|
+
limiter = createLimit(options);
|
|
290
|
+
limiters.set(key, limiter);
|
|
291
|
+
}
|
|
292
|
+
return limiter;
|
|
293
|
+
}
|
|
294
|
+
function keyedLimit(key, fn, options) {
|
|
295
|
+
return getOrCreate(key)(fn, options);
|
|
296
|
+
}
|
|
297
|
+
keyedLimit.get = getOrCreate;
|
|
298
|
+
keyedLimit.delete = (key) => {
|
|
299
|
+
const limiter = limiters.get(key);
|
|
300
|
+
if (limiter) {
|
|
301
|
+
limiter.clearQueue(true);
|
|
302
|
+
return limiters.delete(key);
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
};
|
|
306
|
+
keyedLimit.clear = () => {
|
|
307
|
+
for (const limiter of limiters.values()) {
|
|
308
|
+
limiter.clearQueue(true);
|
|
309
|
+
}
|
|
310
|
+
limiters.clear();
|
|
311
|
+
};
|
|
312
|
+
Object.defineProperty(keyedLimit, "size", {
|
|
313
|
+
get: () => limiters.size,
|
|
314
|
+
});
|
|
315
|
+
return keyedLimit;
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limit.js","sourceRoot":"","sources":["../../src/limit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when a queued operation is aborted.
|
|
3
|
+
*/
|
|
4
|
+
export class AbortError extends Error {
|
|
5
|
+
constructor(message = "Operation was aborted") {
|
|
6
|
+
super(message);
|
|
7
|
+
this.isLimitAbort = true;
|
|
8
|
+
this.name = "AbortError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when the queue is cleared.
|
|
13
|
+
*/
|
|
14
|
+
export class QueueClearedError extends Error {
|
|
15
|
+
constructor(message = "Queue was cleared") {
|
|
16
|
+
super(message);
|
|
17
|
+
this.isQueueCleared = true;
|
|
18
|
+
this.name = "QueueClearedError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when the queue is full.
|
|
23
|
+
*/
|
|
24
|
+
export class QueueFullError extends Error {
|
|
25
|
+
constructor(message = "Queue is full") {
|
|
26
|
+
super(message);
|
|
27
|
+
this.isQueueFull = true;
|
|
28
|
+
this.name = "QueueFullError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a concurrency and/or rate limiter.
|
|
33
|
+
*
|
|
34
|
+
* @param options - Configuration options.
|
|
35
|
+
* @returns A limiter function.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // Concurrency only (like p-limit)
|
|
40
|
+
* const limit = createLimit({ concurrency: 5 });
|
|
41
|
+
* await limit(() => fetch(url));
|
|
42
|
+
*
|
|
43
|
+
* // Rate limiting (max 10 per second)
|
|
44
|
+
* const limit = createLimit({ rate: 10, interval: 1000 });
|
|
45
|
+
*
|
|
46
|
+
* // Both
|
|
47
|
+
* const limit = createLimit({ concurrency: 5, rate: 60, interval: 60000 });
|
|
48
|
+
*
|
|
49
|
+
* // With priority
|
|
50
|
+
* await limit(() => importantCall(), { priority: 10 });
|
|
51
|
+
*
|
|
52
|
+
* // With AbortSignal
|
|
53
|
+
* await limit(() => fetch(url), { signal: controller.signal });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function createLimit(options = {}) {
|
|
57
|
+
const { concurrency = Infinity, rate, interval = 1000, maxQueueSize = Infinity, } = options;
|
|
58
|
+
// Validate options
|
|
59
|
+
if (concurrency !== Infinity && (concurrency <= 0 || !Number.isInteger(concurrency))) {
|
|
60
|
+
throw new TypeError("concurrency must be a positive integer or Infinity");
|
|
61
|
+
}
|
|
62
|
+
if (rate !== undefined && (rate <= 0 || !Number.isInteger(rate))) {
|
|
63
|
+
throw new TypeError("rate must be a positive integer");
|
|
64
|
+
}
|
|
65
|
+
if (interval <= 0) {
|
|
66
|
+
throw new TypeError("interval must be positive");
|
|
67
|
+
}
|
|
68
|
+
if (maxQueueSize !== Infinity && (maxQueueSize <= 0 || !Number.isInteger(maxQueueSize))) {
|
|
69
|
+
throw new TypeError("maxQueueSize must be a positive integer or Infinity");
|
|
70
|
+
}
|
|
71
|
+
const queue = [];
|
|
72
|
+
let activeCount = 0;
|
|
73
|
+
let idleResolvers = [];
|
|
74
|
+
// Token bucket for rate limiting
|
|
75
|
+
let tokens = rate ?? Infinity;
|
|
76
|
+
let lastRefill = Date.now();
|
|
77
|
+
let refillTimeoutId = null;
|
|
78
|
+
function refillTokens() {
|
|
79
|
+
if (rate === undefined)
|
|
80
|
+
return;
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const elapsed = now - lastRefill;
|
|
83
|
+
if (elapsed >= interval) {
|
|
84
|
+
// Full refill
|
|
85
|
+
const periods = Math.floor(elapsed / interval);
|
|
86
|
+
tokens = Math.min(rate, tokens + rate * periods);
|
|
87
|
+
lastRefill += periods * interval;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function scheduleRefill() {
|
|
91
|
+
if (rate === undefined || refillTimeoutId !== null)
|
|
92
|
+
return;
|
|
93
|
+
if (queue.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
const timeSinceLastRefill = now - lastRefill;
|
|
97
|
+
const timeUntilRefill = Math.max(0, interval - timeSinceLastRefill);
|
|
98
|
+
refillTimeoutId = setTimeout(() => {
|
|
99
|
+
refillTimeoutId = null;
|
|
100
|
+
refillTokens();
|
|
101
|
+
processQueue();
|
|
102
|
+
}, timeUntilRefill);
|
|
103
|
+
}
|
|
104
|
+
function checkIdle() {
|
|
105
|
+
if (activeCount === 0 && queue.length === 0 && idleResolvers.length > 0) {
|
|
106
|
+
const resolvers = idleResolvers;
|
|
107
|
+
idleResolvers = [];
|
|
108
|
+
for (const resolve of resolvers) {
|
|
109
|
+
resolve();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function processQueue() {
|
|
114
|
+
refillTokens();
|
|
115
|
+
while (queue.length > 0 && activeCount < concurrency && tokens > 0) {
|
|
116
|
+
const op = queue.shift();
|
|
117
|
+
// Remove abort listener since we're about to execute
|
|
118
|
+
if (op.onAbort && op.signal) {
|
|
119
|
+
op.signal.removeEventListener("abort", op.onAbort);
|
|
120
|
+
}
|
|
121
|
+
// Check if already aborted
|
|
122
|
+
if (op.signal?.aborted) {
|
|
123
|
+
op.reject(new AbortError());
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
activeCount++;
|
|
127
|
+
if (rate !== undefined) {
|
|
128
|
+
tokens--;
|
|
129
|
+
}
|
|
130
|
+
Promise.resolve()
|
|
131
|
+
.then(() => op.fn())
|
|
132
|
+
.then((result) => {
|
|
133
|
+
activeCount--;
|
|
134
|
+
op.resolve(result);
|
|
135
|
+
processQueue();
|
|
136
|
+
checkIdle();
|
|
137
|
+
}, (error) => {
|
|
138
|
+
activeCount--;
|
|
139
|
+
op.reject(error);
|
|
140
|
+
processQueue();
|
|
141
|
+
checkIdle();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// Schedule refill if we're rate limited and have pending work
|
|
145
|
+
if (queue.length > 0 && tokens <= 0) {
|
|
146
|
+
scheduleRefill();
|
|
147
|
+
}
|
|
148
|
+
// Check if we're idle
|
|
149
|
+
checkIdle();
|
|
150
|
+
}
|
|
151
|
+
function enqueue(fn, options = {}) {
|
|
152
|
+
// Validate fn
|
|
153
|
+
if (typeof fn !== "function") {
|
|
154
|
+
throw new TypeError("fn must be a function");
|
|
155
|
+
}
|
|
156
|
+
const { priority = 0, signal } = options;
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
// Check if already aborted
|
|
159
|
+
if (signal?.aborted) {
|
|
160
|
+
reject(new AbortError());
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Check queue size
|
|
164
|
+
if (queue.length >= maxQueueSize) {
|
|
165
|
+
reject(new QueueFullError());
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const op = {
|
|
169
|
+
fn,
|
|
170
|
+
priority,
|
|
171
|
+
resolve: resolve,
|
|
172
|
+
reject,
|
|
173
|
+
signal,
|
|
174
|
+
};
|
|
175
|
+
// Set up abort handler
|
|
176
|
+
if (signal) {
|
|
177
|
+
op.onAbort = () => {
|
|
178
|
+
const index = queue.indexOf(op);
|
|
179
|
+
if (index !== -1) {
|
|
180
|
+
queue.splice(index, 1);
|
|
181
|
+
reject(new AbortError());
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
signal.addEventListener("abort", op.onAbort, { once: true });
|
|
185
|
+
}
|
|
186
|
+
// Insert in priority order (higher priority first)
|
|
187
|
+
let inserted = false;
|
|
188
|
+
for (let i = 0; i < queue.length; i++) {
|
|
189
|
+
if (priority > queue[i].priority) {
|
|
190
|
+
queue.splice(i, 0, op);
|
|
191
|
+
inserted = true;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!inserted) {
|
|
196
|
+
queue.push(op);
|
|
197
|
+
}
|
|
198
|
+
processQueue();
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function clearQueue(reject = true) {
|
|
202
|
+
// Cancel refill timer
|
|
203
|
+
if (refillTimeoutId !== null) {
|
|
204
|
+
clearTimeout(refillTimeoutId);
|
|
205
|
+
refillTimeoutId = null;
|
|
206
|
+
}
|
|
207
|
+
// Process all queued items
|
|
208
|
+
while (queue.length > 0) {
|
|
209
|
+
const op = queue.shift();
|
|
210
|
+
// Clean up abort listener
|
|
211
|
+
if (op.onAbort && op.signal) {
|
|
212
|
+
op.signal.removeEventListener("abort", op.onAbort);
|
|
213
|
+
}
|
|
214
|
+
if (reject) {
|
|
215
|
+
op.reject(new QueueClearedError());
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Check if we're now idle
|
|
219
|
+
checkIdle();
|
|
220
|
+
}
|
|
221
|
+
function onIdle() {
|
|
222
|
+
if (activeCount === 0 && queue.length === 0) {
|
|
223
|
+
return Promise.resolve();
|
|
224
|
+
}
|
|
225
|
+
return new Promise((resolve) => {
|
|
226
|
+
idleResolvers.push(resolve);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const limiter = enqueue;
|
|
230
|
+
Object.defineProperty(limiter, "activeCount", {
|
|
231
|
+
get: () => activeCount,
|
|
232
|
+
});
|
|
233
|
+
Object.defineProperty(limiter, "pendingCount", {
|
|
234
|
+
get: () => queue.length,
|
|
235
|
+
});
|
|
236
|
+
limiter.clearQueue = clearQueue;
|
|
237
|
+
limiter.onIdle = onIdle;
|
|
238
|
+
return limiter;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Create a pre-limited version of a function.
|
|
242
|
+
*
|
|
243
|
+
* @param fn - The function to wrap.
|
|
244
|
+
* @param options - Limit options.
|
|
245
|
+
* @returns A function that applies limiting.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* const fetchWithLimit = limited(
|
|
250
|
+
* (url: string) => fetch(url),
|
|
251
|
+
* { concurrency: 5 }
|
|
252
|
+
* );
|
|
253
|
+
* await fetchWithLimit("/api/data");
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
export function limited(fn, options = {}) {
|
|
257
|
+
const limit = createLimit(options);
|
|
258
|
+
return (...args) => limit(() => fn(...args));
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Create a keyed limiter for per-key rate limiting.
|
|
262
|
+
*
|
|
263
|
+
* @param options - Limit options applied to each key.
|
|
264
|
+
* @returns A keyed limiter.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* const userLimit = createKeyedLimit({ concurrency: 2, rate: 10, interval: 1000 });
|
|
269
|
+
*
|
|
270
|
+
* // Each user gets their own limit
|
|
271
|
+
* await userLimit("user-123", () => fetchUserData("123"));
|
|
272
|
+
* await userLimit("user-456", () => fetchUserData("456"));
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
export function createKeyedLimit(options = {}) {
|
|
276
|
+
const limiters = new Map();
|
|
277
|
+
function getOrCreate(key) {
|
|
278
|
+
let limiter = limiters.get(key);
|
|
279
|
+
if (!limiter) {
|
|
280
|
+
limiter = createLimit(options);
|
|
281
|
+
limiters.set(key, limiter);
|
|
282
|
+
}
|
|
283
|
+
return limiter;
|
|
284
|
+
}
|
|
285
|
+
function keyedLimit(key, fn, options) {
|
|
286
|
+
return getOrCreate(key)(fn, options);
|
|
287
|
+
}
|
|
288
|
+
keyedLimit.get = getOrCreate;
|
|
289
|
+
keyedLimit.delete = (key) => {
|
|
290
|
+
const limiter = limiters.get(key);
|
|
291
|
+
if (limiter) {
|
|
292
|
+
limiter.clearQueue(true);
|
|
293
|
+
return limiters.delete(key);
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
};
|
|
297
|
+
keyedLimit.clear = () => {
|
|
298
|
+
for (const limiter of limiters.values()) {
|
|
299
|
+
limiter.clearQueue(true);
|
|
300
|
+
}
|
|
301
|
+
limiters.clear();
|
|
302
|
+
};
|
|
303
|
+
Object.defineProperty(keyedLimit, "size", {
|
|
304
|
+
get: () => limiters.size,
|
|
305
|
+
});
|
|
306
|
+
return keyedLimit;
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limit.js","sourceRoot":"","sources":["../../src/limit.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@npclfg/nano-limit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Tiny concurrency and rate limiter with priorities, AbortSignal, and zero dependencies.",
|
|
5
|
+
"author": "npclfg",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/npclfg/nano-limit.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/npclfg/nano-limit/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/npclfg/nano-limit#readme",
|
|
14
|
+
"main": "./dist/cjs/limit.js",
|
|
15
|
+
"module": "./dist/esm/limit.js",
|
|
16
|
+
"types": "./dist/cjs/limit.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": {
|
|
20
|
+
"types": "./dist/cjs/limit.d.ts",
|
|
21
|
+
"default": "./dist/esm/limit.js"
|
|
22
|
+
},
|
|
23
|
+
"require": {
|
|
24
|
+
"types": "./dist/cjs/limit.d.ts",
|
|
25
|
+
"default": "./dist/cjs/limit.js"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "npm run build:cjs && npm run build:esm",
|
|
36
|
+
"build:cjs": "tsc",
|
|
37
|
+
"build:esm": "tsc -p tsconfig.esm.json && node scripts/postbuild-esm.js",
|
|
38
|
+
"test": "npm run build && node test/run.js",
|
|
39
|
+
"verify": "npm run build && node scripts/verify.js",
|
|
40
|
+
"bench": "npm run build && node benchmark/bench.js",
|
|
41
|
+
"sample": "npm run build && node sample/app.js",
|
|
42
|
+
"prepublishOnly": "npm run build && npm test"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"limit",
|
|
46
|
+
"rate-limit",
|
|
47
|
+
"concurrency",
|
|
48
|
+
"throttle",
|
|
49
|
+
"queue",
|
|
50
|
+
"async",
|
|
51
|
+
"promise",
|
|
52
|
+
"p-limit",
|
|
53
|
+
"bottleneck",
|
|
54
|
+
"typescript"
|
|
55
|
+
],
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=16"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"typescript": "^5.0.0"
|
|
62
|
+
}
|
|
63
|
+
}
|