ttc-rate-limit 0.1.3 → 0.1.7
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/README.md +70 -75
- package/dist/cluster.d.ts +3 -3
- package/dist/cluster.d.ts.map +1 -1
- package/dist/cluster.js +14 -7
- package/dist/cluster.js.map +1 -1
- package/dist/ratelimit.d.ts +3 -2
- package/dist/ratelimit.d.ts.map +1 -1
- package/dist/ratelimit.js +15 -3
- package/dist/ratelimit.js.map +1 -1
- package/dist/test.js +2 -1
- package/dist/test.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
# ttc_rate_limit
|
|
2
2
|
|
|
3
|
-
A rate limiting library with worker pool support for parallel timeout management
|
|
3
|
+
A rate limiting library with worker pool support for parallel timeout management.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Core Rate Limiting**: Token bucket and sliding window algorithms via `RateLimiter`
|
|
8
|
-
- **
|
|
9
|
-
- **Cluster Management**: Coordinate multiple rate limiters with `Cluster`
|
|
10
|
-
- **Multi-Provider LLM Provisioning**: Route requests across 6 providers (OpenAI, Grok, Minimax, Kimi, DeepSeek, Anthropic)
|
|
11
|
-
- **7 Routing Strategies**: Random, round-robin, failover, load-balance, cost-optimized, latency-optimal, priority
|
|
12
|
-
- **Built-in Rate Limiting**: Each provider can have its own rate limits configured
|
|
13
|
-
- **Provider Metrics**: Track requests, latency, and success rates per provider
|
|
8
|
+
- **Cluster Management**: Coordinate multiple rate limiters with `Cluster` for load balancing and failover
|
|
14
9
|
|
|
15
10
|
## Installation
|
|
16
11
|
|
|
@@ -30,7 +25,7 @@ const apiLimiter = new RateLimiter({
|
|
|
30
25
|
id: 'api-limiter',
|
|
31
26
|
request: 100, // 100 requests
|
|
32
27
|
per: 'minute', // per minute
|
|
33
|
-
mode: 'spread', // spread evenly
|
|
28
|
+
mode: 'spread', // spread evenly over time
|
|
34
29
|
cb: async (params) => {
|
|
35
30
|
// Your API call here
|
|
36
31
|
return await fetch('https://api.example.com', params);
|
|
@@ -41,90 +36,90 @@ const apiLimiter = new RateLimiter({
|
|
|
41
36
|
const result = await apiLimiter.invoke({ url: '/api/data' });
|
|
42
37
|
```
|
|
43
38
|
|
|
44
|
-
|
|
39
|
+
#### Rate Limiting Modes
|
|
40
|
+
|
|
41
|
+
- **`spread`**: Distributes requests evenly over the time period. For 100 requests per minute, sends ~1.67 requests per second.
|
|
42
|
+
- **`burst`**: Allows requests up to the limit instantly, then waits for the full time window to reset. For 100/minute, sends 100 requests at once, then waits 60 seconds.
|
|
43
|
+
- **`hybrid`**: Combines burst and spread. Uses `hybridRatio` to divide the limit into smaller bursts. For 100/minute with `hybridRatio: 10`, sends 10 requests every 6 seconds.
|
|
45
44
|
|
|
46
45
|
```typescript
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
rateLimit: { requests: 60, per: 60000 },
|
|
55
|
-
}],
|
|
56
|
-
['deepseek', {
|
|
57
|
-
apiKey: process.env.DEEPSEEK_API_KEY!,
|
|
58
|
-
model: 'deepseek-chat',
|
|
59
|
-
rateLimit: { requests: 1000, per: 60000 },
|
|
60
|
-
}],
|
|
61
|
-
]),
|
|
62
|
-
defaultStrategy: 'latency-optimal',
|
|
46
|
+
// Burst mode example
|
|
47
|
+
const burstLimiter = new RateLimiter({
|
|
48
|
+
id: 'burst-limiter',
|
|
49
|
+
request: 100,
|
|
50
|
+
per: 'minute',
|
|
51
|
+
mode: 'burst',
|
|
52
|
+
cb: async (input) => apiCall(input),
|
|
63
53
|
});
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
// Hybrid mode example
|
|
56
|
+
const hybridLimiter = new RateLimiter({
|
|
57
|
+
id: 'hybrid-limiter',
|
|
58
|
+
request: 100,
|
|
59
|
+
per: 'minute',
|
|
60
|
+
mode: 'hybrid',
|
|
61
|
+
hybridRatio: 10, // 10 bursts per 6 seconds
|
|
62
|
+
cb: async (input) => apiCall(input),
|
|
67
63
|
});
|
|
68
|
-
|
|
69
|
-
console.log(response.content);
|
|
70
64
|
```
|
|
71
65
|
|
|
72
|
-
## Supported LLM Providers
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
|----------|----------|-------------------|
|
|
76
|
-
| OpenAI | api.openai.com/v1 | Native |
|
|
77
|
-
| Grok | api.x.ai/v1 | OpenAI-compatible |
|
|
78
|
-
| Minimax | api.minimax.chat/v1 | OpenAI-compatible |
|
|
79
|
-
| Kimi | api.moonshot.ai/v1 | OpenAI-compatible |
|
|
80
|
-
| DeepSeek | api.deepseek.com/v1 | OpenAI-compatible |
|
|
81
|
-
| Anthropic | api.anthropic.com/v1 | Native SDK |
|
|
67
|
+
### Cluster Management
|
|
82
68
|
|
|
83
|
-
|
|
69
|
+
Use `Cluster` to manage multiple rate limiters and distribute tasks among them using load balancing strategies.
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
71
|
+
```typescript
|
|
72
|
+
import { Cluster } from 'ttc_rate_limit';
|
|
73
|
+
|
|
74
|
+
type Task = { modelId: string; chatId: string; };
|
|
75
|
+
|
|
76
|
+
const cluster = new Cluster<Task, string>('roundrobin');
|
|
77
|
+
|
|
78
|
+
// Add multiple rate limiters
|
|
79
|
+
cluster.addOption({
|
|
80
|
+
id: 'api-1',
|
|
81
|
+
request: 100,
|
|
82
|
+
per: 'minute',
|
|
83
|
+
mode: 'hybrid',
|
|
84
|
+
hybridRatio: 10,
|
|
85
|
+
cb: async (task) => {
|
|
86
|
+
// Process task with API 1
|
|
87
|
+
return `Processed by API 1: ${task.modelId} - ${task.chatId}`;
|
|
88
|
+
},
|
|
89
|
+
});
|
|
94
90
|
|
|
95
|
-
|
|
91
|
+
cluster.addOption({
|
|
92
|
+
id: 'api-2',
|
|
93
|
+
request: 50,
|
|
94
|
+
per: 'minute',
|
|
95
|
+
mode: 'spread',
|
|
96
|
+
cb: async (task) => {
|
|
97
|
+
// Process task with API 2
|
|
98
|
+
return `Processed by API 2: ${task.modelId} - ${task.chatId}`;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
96
101
|
|
|
97
|
-
|
|
102
|
+
// Listen for events
|
|
103
|
+
cluster.on('completed', (data) => {
|
|
104
|
+
console.log('Task completed:', data.response);
|
|
105
|
+
});
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// Streaming completion
|
|
107
|
-
completeStream(params: LLMCompletionParams, onChunk: (chunk: string) => void): Promise<void>
|
|
108
|
-
|
|
109
|
-
// Add/remove providers dynamically
|
|
110
|
-
addProvider(type: ProviderType, config: ProviderConfig): void
|
|
111
|
-
removeProvider(type: ProviderType): boolean
|
|
112
|
-
|
|
113
|
-
// Strategy management
|
|
114
|
-
setStrategy(strategy: ProvisioningStrategy): void
|
|
115
|
-
getStrategy(): ProvisioningStrategy
|
|
116
|
-
|
|
117
|
-
// Metrics
|
|
118
|
-
getProviderStats(): ProviderStats[]
|
|
119
|
-
getMetrics(): Map<ProviderType, ProviderMetrics>
|
|
107
|
+
cluster.on('error', (data) => {
|
|
108
|
+
console.error('Task error:', data.response);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Invoke tasks (will be distributed among limiters)
|
|
112
|
+
for (let i = 0; i < 10; i++) {
|
|
113
|
+
cluster.invoke({ modelId: 'gpt-4', chatId: `chat-${i}` });
|
|
120
114
|
}
|
|
121
115
|
```
|
|
122
116
|
|
|
123
|
-
|
|
117
|
+
#### Cluster Strategies
|
|
124
118
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
- **`random`**: Randomly select a limiter for each task.
|
|
120
|
+
- **`roundrobin`**: Cycle through limiters sequentially.
|
|
121
|
+
|
|
122
|
+
## License
|
|
128
123
|
|
|
129
124
|
## License
|
|
130
125
|
|
package/dist/cluster.d.ts
CHANGED
|
@@ -2,18 +2,18 @@ import { RateLimiter, RateLimiterConfig } from "./ratelimit";
|
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
export declare class Cluster<T, R> {
|
|
4
4
|
config: {
|
|
5
|
-
type: 'random' | 'roundrobin'
|
|
5
|
+
type: 'random' | 'roundrobin';
|
|
6
6
|
perferedLimiter?: string;
|
|
7
7
|
};
|
|
8
8
|
emitter: EventEmitter;
|
|
9
9
|
limiters: RateLimiter<T, R>[];
|
|
10
|
-
constructor(type: 'random' | 'roundrobin'
|
|
10
|
+
constructor(type: 'random' | 'roundrobin');
|
|
11
11
|
on: (event: "completed" | "error", listener: (data: {
|
|
12
12
|
request: T;
|
|
13
13
|
response: any;
|
|
14
14
|
}) => void) => void;
|
|
15
15
|
addOption: (config: RateLimiterConfig<T, R>, specific?: boolean) => this;
|
|
16
|
-
invoke: (item: T) => Promise<void>;
|
|
16
|
+
invoke: (item: T, preferredLimiterId?: string, retryCount?: number) => Promise<void>;
|
|
17
17
|
randomLimiter: () => RateLimiter<T, R>;
|
|
18
18
|
}
|
|
19
19
|
//# sourceMappingURL=cluster.d.ts.map
|
package/dist/cluster.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../src/cluster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,qBAAa,OAAO,CAAC,CAAC,EAAE,CAAC;IAErB,MAAM,EAAE;QACJ,IAAI,EAAE,QAAQ,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../src/cluster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,qBAAa,OAAO,CAAC,CAAC,EAAE,CAAC;IAErB,MAAM,EAAE;QACJ,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC;QAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAsB;IAEvB,OAAO,EAAE,YAAY,CAAsB;IAE3C,QAAQ,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAM;gBAEvB,IAAI,EAAE,QAAQ,GAAG,YAAY;IAMzC,EAAE,GAAI,OAAO,WAAW,GAAG,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,KAAK,IAAI,UAE1F;IAED,SAAS,GAAI,QAAQ,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,WAAU,OAAe,UAWtE;IAED,MAAM,GAAU,MAAM,CAAC,EAAE,qBAAqB,MAAM,EAAE,aAAY,MAAU,KAAG,OAAO,CAAC,IAAI,CAAC,CA0B3F;IAED,aAAa,QAAO,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAGpC;CACJ"}
|
package/dist/cluster.js
CHANGED
|
@@ -23,10 +23,16 @@ class Cluster {
|
|
|
23
23
|
});
|
|
24
24
|
return this;
|
|
25
25
|
};
|
|
26
|
-
this.invoke = async (item) => {
|
|
26
|
+
this.invoke = async (item, preferredLimiterId, retryCount = 0) => {
|
|
27
27
|
try {
|
|
28
28
|
let limiter;
|
|
29
|
-
if (
|
|
29
|
+
if (preferredLimiterId) {
|
|
30
|
+
limiter = this.limiters.find(l => l.config.id === preferredLimiterId);
|
|
31
|
+
if (!limiter) {
|
|
32
|
+
limiter = this.randomLimiter();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (this.config.type === 'random') {
|
|
30
36
|
limiter = this.randomLimiter();
|
|
31
37
|
}
|
|
32
38
|
else if (this.config.type === 'roundrobin') {
|
|
@@ -34,15 +40,16 @@ class Cluster {
|
|
|
34
40
|
this.limiters.push(limiter);
|
|
35
41
|
}
|
|
36
42
|
else {
|
|
37
|
-
limiter = this.limiters
|
|
38
|
-
if (!limiter) {
|
|
39
|
-
limiter = this.randomLimiter();
|
|
40
|
-
}
|
|
43
|
+
limiter = this.limiters[0];
|
|
41
44
|
}
|
|
42
45
|
await limiter.invoke(item);
|
|
43
46
|
}
|
|
44
47
|
catch (error) {
|
|
45
|
-
|
|
48
|
+
if (retryCount >= 3) {
|
|
49
|
+
this.emitter.emit('error', { request: item, response: error });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
return await this.invoke(item, preferredLimiterId, retryCount + 1);
|
|
46
53
|
}
|
|
47
54
|
};
|
|
48
55
|
this.randomLimiter = () => {
|
package/dist/cluster.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster.js","sourceRoot":"","sources":["../src/cluster.ts"],"names":[],"mappings":";;;AAAA,2CAA6D;AAC7D,mCAAsC;AAEtC,MAAa,OAAO;IAWhB,YAAY,
|
|
1
|
+
{"version":3,"file":"cluster.js","sourceRoot":"","sources":["../src/cluster.ts"],"names":[],"mappings":"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"}
|
package/dist/ratelimit.d.ts
CHANGED
|
@@ -6,11 +6,12 @@ export interface RateLimiterConfig<T, R> {
|
|
|
6
6
|
request: number;
|
|
7
7
|
per: 'second' | 'minute' | 'hour' | 'day';
|
|
8
8
|
interval?: number;
|
|
9
|
-
cb: (input: T) => Promise<R>;
|
|
9
|
+
cb: (input: T, limiterId: string) => Promise<R>;
|
|
10
10
|
_count?: number;
|
|
11
11
|
rps?: number;
|
|
12
12
|
_Tmarker?: number;
|
|
13
|
-
mode: 'spread' | 'burst';
|
|
13
|
+
mode: 'spread' | 'burst' | 'hybrid';
|
|
14
|
+
hybridRatio?: number;
|
|
14
15
|
}
|
|
15
16
|
export type queueData<T> = {
|
|
16
17
|
data: T;
|
package/dist/ratelimit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ratelimit.d.ts","sourceRoot":"","sources":["../src/ratelimit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC,eAAO,MAAM,IAAI,aAAsB,CAAC;AAExC,MAAM,WAAW,iBAAiB,CAAC,CAAC,EAAE,CAAC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ratelimit.d.ts","sourceRoot":"","sources":["../src/ratelimit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC,eAAO,MAAM,IAAI,aAAsB,CAAC;AAExC,MAAM,WAAW,iBAAiB,CAAC,CAAC,EAAE,CAAC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACvB,IAAI,EAAE,CAAC,CAAC;IACR,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CACvC,CAAA;AAED,qBAAa,WAAW,CAAC,CAAC,EAAE,CAAC;IAEzB,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC/B,KAAK,EAAE,CAAC,EAAE,CAAM;IAChB,YAAY,EAAE,YAAY,CAAC;gBACf,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC;IAM3C,QAAQ;IAqBR,EAAE,GAAI,OAAO,WAAW,GAAG,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE;QACjD,OAAO,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAC5B,KAAK,IAAI,UAET;IAEK,YAAY;IA6BZ,eAAe,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;QACrC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;QACvC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAChC,CAAC;IAmCI,cAAc,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;QACpC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;QACvC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAChC,CAAC;IA+DI,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;QAC5B,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;QACvC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAChC,CAAC;CAoBL"}
|
package/dist/ratelimit.js
CHANGED
|
@@ -23,7 +23,16 @@ class RateLimiter {
|
|
|
23
23
|
};
|
|
24
24
|
const interval = intervals[this.config.per] ?? 1;
|
|
25
25
|
this.config.rps = Math.ceil(this.config.request / interval);
|
|
26
|
-
this.config.
|
|
26
|
+
if (this.config.mode === 'spread') {
|
|
27
|
+
this.config.interval = interval / this.config.request;
|
|
28
|
+
}
|
|
29
|
+
else if (this.config.mode === 'burst') {
|
|
30
|
+
this.config.interval = interval;
|
|
31
|
+
}
|
|
32
|
+
else if (this.config.mode === 'hybrid') {
|
|
33
|
+
this.config.interval = interval / (this.config.hybridRatio ?? 2);
|
|
34
|
+
this.config.request = Math.ceil(this.config.request / (this.config.hybridRatio ?? 2));
|
|
35
|
+
}
|
|
27
36
|
this.config._count = 0;
|
|
28
37
|
console.log(this.config);
|
|
29
38
|
}
|
|
@@ -65,7 +74,7 @@ class RateLimiter {
|
|
|
65
74
|
}, this.config.interval);
|
|
66
75
|
}
|
|
67
76
|
try {
|
|
68
|
-
const response = await this.config.cb(input);
|
|
77
|
+
const response = await this.config.cb(input, this.config.id);
|
|
69
78
|
this.eventManager.emit('completed', {
|
|
70
79
|
request: input,
|
|
71
80
|
response
|
|
@@ -91,6 +100,7 @@ class RateLimiter {
|
|
|
91
100
|
async burstAlgorithm(input) {
|
|
92
101
|
const now = Date.now();
|
|
93
102
|
const windowMs = this.config.interval * 1000;
|
|
103
|
+
// console.log(windowMs, 'windowMs');
|
|
94
104
|
// Reset count if window has passed
|
|
95
105
|
if (this.config._Tmarker && now - this.config._Tmarker >= windowMs) {
|
|
96
106
|
this.config._count = 0;
|
|
@@ -118,13 +128,14 @@ class RateLimiter {
|
|
|
118
128
|
// If this request hits the limit, schedule reset after remaining time
|
|
119
129
|
if (this.config._count === this.config.request && timeRemaining > 0) {
|
|
120
130
|
const waitSeconds = timeRemaining / 1000;
|
|
131
|
+
console.log(timeRemaining, waitSeconds);
|
|
121
132
|
await exports.time.waitFor(async () => {
|
|
122
133
|
this.config._count = 0;
|
|
123
134
|
await this.processQueue();
|
|
124
135
|
}, waitSeconds);
|
|
125
136
|
}
|
|
126
137
|
try {
|
|
127
|
-
const response = await this.config.cb(input);
|
|
138
|
+
const response = await this.config.cb(input, this.config.id);
|
|
128
139
|
this.eventManager.emit('completed', {
|
|
129
140
|
request: input,
|
|
130
141
|
response
|
|
@@ -142,6 +153,7 @@ class RateLimiter {
|
|
|
142
153
|
}
|
|
143
154
|
async invoke(input) {
|
|
144
155
|
let response = null;
|
|
156
|
+
// we need to ensure that the parametes are not in the queue
|
|
145
157
|
try {
|
|
146
158
|
if (this.config.mode === 'spread') {
|
|
147
159
|
response = await this.spreadAlgorithm(input);
|
package/dist/ratelimit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../src/ratelimit.ts"],"names":[],"mappings":";;;AAAA,mCAAsC;AACtC,iCAAqC;AAExB,QAAA,IAAI,GAAG,IAAI,kBAAW,CAAC,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../src/ratelimit.ts"],"names":[],"mappings":"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"}
|
package/dist/test.js
CHANGED
package/dist/test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":";;AACA,uCAAoC;AAQpC,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAe,YAAY,CAAC,CAAC;AAExD,OAAO,CAAC,SAAS,CAAC;IACd,EAAE,EAAE,UAAU;IACd,OAAO,EAAE,GAAG;IACZ,GAAG,EAAE,QAAQ;IACb,IAAI,EAAE,QAAQ;IACd,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAChB,OAAO,cAAc,KAAK,CAAC,OAAO,MAAM,KAAK,CAAC,MAAM,cAAc,CAAC;IACvE,CAAC;CACJ,EAAE,IAAI,CAAC,CAAC;AAET,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE;IAC7B,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;IACzB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC;AAEH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;IAC3B,OAAO,CAAC,MAAM,CAAC;QACX,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,QAAQ,CAAC,EAAE;KACtB,CAAC,CAAC;AACP,CAAC"}
|
|
1
|
+
{"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":";;AACA,uCAAoC;AAQpC,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAe,YAAY,CAAC,CAAC;AAExD,OAAO,CAAC,SAAS,CAAC;IACd,EAAE,EAAE,UAAU;IACd,OAAO,EAAE,GAAG;IACZ,GAAG,EAAE,QAAQ;IACb,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,EAAE;IACf,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAChB,OAAO,cAAc,KAAK,CAAC,OAAO,MAAM,KAAK,CAAC,MAAM,cAAc,CAAC;IACvE,CAAC;CACJ,EAAE,IAAI,CAAC,CAAC;AAET,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE;IAC7B,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;IACzB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC;AAEH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;IAC3B,OAAO,CAAC,MAAM,CAAC;QACX,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,QAAQ,CAAC,EAAE;KACtB,CAAC,CAAC;AACP,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ttc-rate-limit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "A rate limiting library with worker pool support for parallel timeout management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
15
|
"dev": "tsc --watch",
|
|
16
|
-
"test": "bun src/
|
|
16
|
+
"test": "bun src/test.ts",
|
|
17
17
|
"prepublishOnly": "npm run build"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|