ttc-rate-limit 0.1.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/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # ttc_rate_limit
2
+
3
+ A rate limiting library with worker pool support for parallel timeout management.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install ttc_rate_limit
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Rate Limiting**: Configurable rate limits per second, minute, hour, or day
14
+ - **Worker Pool Support**: Parallel timeout management using worker threads
15
+ - **Multiple Strategies**: Spread and burst modes for different use cases
16
+ - **Cluster Support**: Distribute rate limiting across multiple limiters
17
+ - **Event-Driven**: Listen for completion and error events
18
+
19
+ ## Usage
20
+
21
+ ### Single Rate Limiter
22
+
23
+ ```typescript
24
+ import { RateLimiter } from 'ttc_rate_limit';
25
+
26
+ // Create a rate limiter for API calls
27
+ const apiLimiter = new RateLimiter({
28
+ id: 'api-limiter',
29
+ request: 100, // 100 requests
30
+ per: 'minute', // per minute
31
+ mode: 'spread', // evenly spread requests
32
+ cb: async (data) => {
33
+ // Your async function to rate limit
34
+ const response = await fetch('https://api.example.com/data', {
35
+ method: 'POST',
36
+ body: JSON.stringify(data)
37
+ });
38
+ return await response.json();
39
+ }
40
+ });
41
+
42
+ // Listen for completed requests
43
+ apiLimiter.on('completed', (data) => {
44
+ console.log('Request completed:', data.response);
45
+ });
46
+
47
+ // Listen for errors
48
+ apiLimiter.on('error', (data) => {
49
+ console.error('Request failed:', data.response);
50
+ });
51
+
52
+ // Make requests through the limiter
53
+ for (let i = 0; i < 150; i++) {
54
+ const result = await apiLimiter.invoke({
55
+ userId: 'user123',
56
+ action: `action-${i}`
57
+ });
58
+
59
+ // Result can be: { status: 'success' }, { status: 'queued' }, or { status: 'error', message: string }
60
+ console.log(`Request ${i}: ${result.status}`);
61
+ }
62
+ ```
63
+
64
+ ### Burst Mode Example
65
+
66
+ ```typescript
67
+ import { RateLimiter } from 'ttc_rate_limit';
68
+
69
+ // Create a burst mode limiter for batch processing
70
+ const burstLimiter = new RateLimiter({
71
+ id: 'batch-processor',
72
+ request: 50, // 50 requests
73
+ per: 'second', // per second
74
+ mode: 'burst', // allow bursts up to limit
75
+ cb: async (batch) => {
76
+ // Process batch of data
77
+ return await processBatch(batch);
78
+ }
79
+ });
80
+ ```
81
+
82
+ ### Time Cluster (Worker Pool)
83
+
84
+ ```typescript
85
+ import { TimeCluster } from 'ttc_rate_limit';
86
+
87
+ // Create a time cluster with intervals
88
+ const timeCluster = new TimeCluster(20); // 20 intervals
89
+
90
+ // Wait for async operation with timeout
91
+ await timeCluster.waitFor(async () => {
92
+ console.log('Executed after 2.5 seconds');
93
+ }, 2.5); // 2.5 seconds timeout
94
+ ```
95
+
96
+ ### Rate Limiter Cluster
97
+
98
+ ```typescript
99
+ import { Cluster } from 'ttc_rate_limit';
100
+
101
+ type Task = {
102
+ modelId: string,
103
+ chatId: string,
104
+ };
105
+
106
+ // Create a cluster with round-robin distribution
107
+ const cluster = new Cluster<Task>('roundrobin');
108
+
109
+ // Add rate limiters to the cluster
110
+ cluster.addOption({
111
+ id: 'model-a',
112
+ request: 100,
113
+ per: 'minute',
114
+ mode: 'spread',
115
+ cb: async (input) => {
116
+ return `Processed by Model A: ${input.modelId} - ${input.chatId}`;
117
+ }
118
+ }, true); // true = add immediately
119
+
120
+ cluster.addOption({
121
+ id: 'model-b',
122
+ request: 200,
123
+ per: 'minute',
124
+ mode: 'burst',
125
+ cb: async (input) => {
126
+ return `Processed by Model B: ${input.modelId} - ${input.chatId}`;
127
+ }
128
+ }, true);
129
+
130
+ // Listen to cluster events
131
+ cluster.on('completed', (data) => {
132
+ console.log('Cluster completed:', data.response);
133
+ });
134
+
135
+ cluster.on('error', (data) => {
136
+ console.error('Cluster error:', data.response);
137
+ });
138
+
139
+ // Distribute requests across the cluster
140
+ for (let i = 0; i < 500; i++) {
141
+ await cluster.invoke({
142
+ modelId: 'gpt-4',
143
+ chatId: `chat-${i}`,
144
+ });
145
+ }
146
+ ```
147
+
148
+ ## API
149
+
150
+ ### RateLimiter
151
+
152
+ Main rate limiting class with configurable limits and modes.
153
+
154
+ **Constructor:**
155
+ ```typescript
156
+ new RateLimiter<T>(config: RateLimiterConfig<T>)
157
+ ```
158
+
159
+ **Methods:**
160
+ - `invoke(input: T): Promise<{ status: 'success' | 'error' | 'queued', message?: string }>` - Process a request
161
+ - `on(event: 'completed' | 'error', listener): void` - Listen for events
162
+
163
+ ### TimeCluster
164
+
165
+ Worker pool for managing parallel timeouts with callback registry.
166
+
167
+ **Constructor:**
168
+ ```typescript
169
+ new TimeCluster(workerCount: number)
170
+ ```
171
+
172
+ **Methods:**
173
+ - `setTimeout(callback: () => void, delayMs: number): string` - Schedule timeout
174
+ - `clearTimeout(timeoutId: string): void` - Cancel timeout
175
+ - `waitFor(callback: () => Promise<void>, delaySeconds: number): Promise<void>` - Wait with async callback
176
+
177
+ ### Cluster
178
+
179
+ Cluster of rate limiters for distributed rate limiting.
180
+
181
+ **Constructor:**
182
+ ```typescript
183
+ new Cluster<T>(strategy: 'roundrobin')
184
+ ```
185
+
186
+ **Methods:**
187
+ - `addOption(config: RateLimiterConfig<T>, addImmediately: boolean): void` - Add rate limiter option
188
+ - `invoke(input: T): Promise<any>` - Distribute request to cluster
189
+ - `on(event: 'completed' | 'error', listener): void` - Listen for cluster events
190
+
191
+ ## License
192
+
193
+ MIT
@@ -0,0 +1,19 @@
1
+ import { RateLimiter, RateLimiterConfig } from "./ratelimit";
2
+ import { EventEmitter } from 'events';
3
+ export declare class Cluster<T> {
4
+ config: {
5
+ type: 'random' | 'roundrobin' | 'prefered';
6
+ perferedLimiter?: string;
7
+ };
8
+ emitter: EventEmitter;
9
+ limiters: RateLimiter<T>[];
10
+ constructor(type: 'random' | 'roundrobin' | 'prefered');
11
+ on: (event: "completed" | "error", listener: (data: {
12
+ request: T;
13
+ response: any;
14
+ }) => void) => void;
15
+ addOption: (config: RateLimiterConfig<T>, specific?: boolean) => this;
16
+ invoke: (item: T) => Promise<void>;
17
+ randomLimiter: () => RateLimiter<T>;
18
+ }
19
+ //# sourceMappingURL=cluster.d.ts.map
@@ -0,0 +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;IAElB,MAAM,EAAE;QACJ,IAAI,EAAE,QAAQ,GAAG,YAAY,GAAG,UAAU,CAAC;QAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAsB;IAEvB,OAAO,EAAE,YAAY,CAAsB;IAE3C,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAM;gBAEpB,IAAI,EAAE,QAAQ,GAAG,YAAY,GAAG,UAAU;IAMtD,EAAE,GAAI,OAAO,WAAW,GAAG,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE;QAAC,OAAO,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAC,KAAK,IAAI,UAExF;IAED,SAAS,GAAI,QAAQ,iBAAiB,CAAC,CAAC,CAAC,EAAE,WAAU,OAAe,UAWnE;IAED,MAAM,GAAU,MAAM,CAAC,mBAmBtB;IAED,aAAa,QAAO,WAAW,CAAC,CAAC,CAAC,CAGjC;CACJ"}
@@ -0,0 +1,54 @@
1
+ import { RateLimiter } from "./ratelimit";
2
+ import { EventEmitter } from 'events';
3
+ export class Cluster {
4
+ config = { type: 'random' };
5
+ emitter = new EventEmitter();
6
+ limiters = [];
7
+ constructor(type) {
8
+ this.config = {
9
+ type
10
+ };
11
+ }
12
+ on = (event, listener) => {
13
+ this.emitter.on(event, listener);
14
+ };
15
+ addOption = (config, specific = false) => {
16
+ const rl = new RateLimiter(config);
17
+ this.limiters.push(rl);
18
+ this.config.perferedLimiter = specific ? rl.config.id : this.config.perferedLimiter;
19
+ rl.on('error', async (data) => {
20
+ this.emitter.emit('error', data);
21
+ });
22
+ rl.on('completed', async (data) => {
23
+ this.emitter.emit('completed', data);
24
+ });
25
+ return this;
26
+ };
27
+ invoke = async (item) => {
28
+ try {
29
+ let limiter;
30
+ if (this.config.type === 'random') {
31
+ limiter = this.randomLimiter();
32
+ }
33
+ else if (this.config.type === 'roundrobin') {
34
+ limiter = this.limiters.shift();
35
+ this.limiters.push(limiter);
36
+ }
37
+ else {
38
+ limiter = this.limiters.find(l => l.config.id === this.config.perferedLimiter);
39
+ if (!limiter) {
40
+ limiter = this.randomLimiter();
41
+ }
42
+ }
43
+ await limiter.invoke(item);
44
+ }
45
+ catch (error) {
46
+ throw error;
47
+ }
48
+ };
49
+ randomLimiter = () => {
50
+ const idx = Math.floor(Math.random() * this.limiters.length);
51
+ return this.limiters[idx];
52
+ };
53
+ }
54
+ //# sourceMappingURL=cluster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cluster.js","sourceRoot":"","sources":["../src/cluster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAqB,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,OAAO,OAAO;IAEhB,MAAM,GAGF,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAEvB,OAAO,GAAiB,IAAI,YAAY,EAAE,CAAC;IAE3C,QAAQ,GAAqB,EAAE,CAAC;IAEhC,YAAY,IAA0C;QAClD,IAAI,CAAC,MAAM,GAAG;YACV,IAAI;SACP,CAAA;IACL,CAAC;IAED,EAAE,GAAG,CAAC,KAA4B,EAAE,QAAqD,EAAE,EAAE;QACzF,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC,CAAA;IAED,SAAS,GAAG,CAAC,MAA4B,EAAE,WAAoB,KAAK,EAAE,EAAE;QACpE,MAAM,EAAE,GAAG,IAAI,WAAW,CAAI,MAAM,CAAC,CAAC;QACtC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,eAAe,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC;QACpF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC,CAAC,CAAA;QACF,EAAE,CAAC,EAAE,CAAC,WAAW,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YAC7B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IAChB,CAAC,CAAA;IAED,MAAM,GAAG,KAAK,EAAE,IAAO,EAAE,EAAE;QACxB,IAAI,CAAC;YACH,IAAI,OAAuB,CAAC;YAC5B,IAAG,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC/B,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YACnC,CAAC;iBAAM,IAAG,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC1C,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAG,CAAC;gBACjC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACJ,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,eAAe,CAAQ,CAAC;gBACtF,IAAG,CAAC,OAAO,EAAE,CAAC;oBACX,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;gBAClC,CAAC;YACL,CAAC;YAED,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACZ,MAAM,KAAK,CAAC;QACjB,CAAC;IACJ,CAAC,CAAA;IAED,aAAa,GAAG,GAAmB,EAAE;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC,CAAA;CACJ"}
@@ -0,0 +1,4 @@
1
+ export { RateLimiter, type RateLimiterConfig, type queueData } from './ratelimit';
2
+ export { TimeCluster } from './time';
3
+ export { Cluster } from './cluster';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { RateLimiter } from './ratelimit';
2
+ export { TimeCluster } from './time';
3
+ export { Cluster } from './cluster';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA0C,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { EventEmitter } from "events";
2
+ import { TimeCluster } from "./time";
3
+ export declare const time: TimeCluster;
4
+ export interface RateLimiterConfig<T> {
5
+ id: string;
6
+ request: number;
7
+ per: 'second' | 'minute' | 'hour' | 'day';
8
+ interval?: number;
9
+ cb: (input: T) => Promise<any>;
10
+ _count?: number;
11
+ rps?: number;
12
+ _Tmarker?: number;
13
+ mode: 'spread' | 'burst';
14
+ }
15
+ export type queueData<T> = {
16
+ data: T;
17
+ cb: (response: any) => Promise<any>;
18
+ };
19
+ export declare class RateLimiter<T> {
20
+ config: RateLimiterConfig<T>;
21
+ queue: T[];
22
+ eventManager: EventEmitter;
23
+ constructor(config: RateLimiterConfig<T>);
24
+ _resolve(): void;
25
+ on: (event: "completed" | "error", listener: (data: {
26
+ request: T;
27
+ response: any;
28
+ }) => void) => void;
29
+ processQueue(): Promise<void>;
30
+ spreadAlgorithm(input: T): Promise<{
31
+ status: 'success' | 'error' | 'queued';
32
+ message?: string | undefined;
33
+ }>;
34
+ burstAlgorithm(input: T): Promise<{
35
+ status: 'success' | 'error' | 'queued';
36
+ message?: string | undefined;
37
+ }>;
38
+ invoke(input: T): Promise<{
39
+ status: 'success' | 'error' | 'queued';
40
+ message?: string | undefined;
41
+ }>;
42
+ }
43
+ //# sourceMappingURL=ratelimit.d.ts.map
@@ -0,0 +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;IAChC,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,GAAG,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC5B;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;IAEtB,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAA;IAC5B,KAAK,EAAE,CAAC,EAAE,CAAM;IAChB,YAAY,EAAE,YAAY,CAAC;gBACf,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAMxC,QAAQ;IAcR,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;IA4DI,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;CAiBL"}
@@ -0,0 +1,161 @@
1
+ import { EventEmitter } from "events";
2
+ import { TimeCluster } from "./time";
3
+ export const time = new TimeCluster(20);
4
+ export class RateLimiter {
5
+ config;
6
+ queue = [];
7
+ eventManager;
8
+ constructor(config) {
9
+ this.config = config;
10
+ this._resolve();
11
+ this.eventManager = new EventEmitter();
12
+ }
13
+ _resolve() {
14
+ const intervals = {
15
+ 'second': 1,
16
+ 'minute': 60,
17
+ 'hour': 3600,
18
+ 'day': 86400
19
+ };
20
+ const interval = intervals[this.config.per] ?? 1;
21
+ this.config.rps = Math.ceil(this.config.request / interval);
22
+ this.config.interval = interval / this.config.request;
23
+ this.config._count = 0;
24
+ console.log(this.config);
25
+ }
26
+ on = (event, listener) => {
27
+ this.eventManager.on(event, listener);
28
+ };
29
+ async processQueue() {
30
+ if (this.config.mode === 'spread') {
31
+ while (this.queue.length > 0 && this.config._count < this.config.rps) {
32
+ const item = this.queue.shift();
33
+ await this.invoke(item);
34
+ }
35
+ }
36
+ else {
37
+ while (this.queue.length > 0) {
38
+ const now = Date.now();
39
+ const windowMs = this.config.interval * 1000;
40
+ // Reset if window expired
41
+ if (this.config._Tmarker && now - this.config._Tmarker >= windowMs) {
42
+ this.config._count = 0;
43
+ this.config._Tmarker = now;
44
+ }
45
+ // If we have capacity in current window
46
+ if (this.config._count < this.config.request) {
47
+ const item = this.queue.shift();
48
+ await this.invoke(item);
49
+ }
50
+ else {
51
+ // Wait for window reset
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ async spreadAlgorithm(input) {
58
+ if (this.config._count < this.config.rps) {
59
+ this.config._count++;
60
+ if (this.config._count >= this.config.rps) {
61
+ await time.waitFor(async () => {
62
+ this.config._count = 0;
63
+ await this.processQueue();
64
+ }, this.config.interval);
65
+ }
66
+ try {
67
+ const response = await this.config.cb(input);
68
+ this.eventManager.emit('completed', {
69
+ request: input,
70
+ response
71
+ });
72
+ return {
73
+ status: 'success',
74
+ };
75
+ }
76
+ catch (error) {
77
+ return {
78
+ status: 'error',
79
+ message: error.message
80
+ };
81
+ }
82
+ }
83
+ else {
84
+ this.queue.push(input);
85
+ return {
86
+ status: 'queued',
87
+ };
88
+ }
89
+ }
90
+ async burstAlgorithm(input) {
91
+ const now = Date.now();
92
+ const windowMs = this.config.interval * 1000;
93
+ // Reset count if window has passed
94
+ if (this.config._Tmarker && now - this.config._Tmarker >= windowMs) {
95
+ this.config._count = 0;
96
+ }
97
+ // Initialize marker if starting fresh
98
+ if (this.config._count === 0) {
99
+ this.config._Tmarker = now;
100
+ }
101
+ const timeElapsed = now - this.config._Tmarker;
102
+ const timeRemaining = windowMs - timeElapsed;
103
+ // If we've hit request limit but window hasn't expired, queue and wait
104
+ if (this.config._count >= this.config.request && timeRemaining > 0) {
105
+ this.queue.push(input);
106
+ return {
107
+ status: 'queued',
108
+ };
109
+ }
110
+ // If window has expired, reset and proceed
111
+ if (timeRemaining <= 0) {
112
+ this.config._count = 0;
113
+ this.config._Tmarker = now;
114
+ }
115
+ // We can make the request
116
+ this.config._count++;
117
+ // If this request hits the limit, schedule reset after remaining time
118
+ if (this.config._count === this.config.request && timeRemaining > 0) {
119
+ const waitSeconds = timeRemaining / 1000;
120
+ await time.waitFor(async () => {
121
+ this.config._count = 0;
122
+ await this.processQueue();
123
+ }, waitSeconds);
124
+ }
125
+ try {
126
+ const response = await this.config.cb(input);
127
+ this.eventManager.emit('completed', {
128
+ request: input,
129
+ response
130
+ });
131
+ return {
132
+ status: 'success',
133
+ };
134
+ }
135
+ catch (error) {
136
+ return {
137
+ status: 'error',
138
+ message: error.message
139
+ };
140
+ }
141
+ }
142
+ async invoke(input) {
143
+ let response = null;
144
+ try {
145
+ if (this.config.mode === 'spread') {
146
+ response = await this.spreadAlgorithm(input);
147
+ }
148
+ else {
149
+ response = await this.burstAlgorithm(input);
150
+ }
151
+ return response;
152
+ }
153
+ catch (error) {
154
+ return {
155
+ status: 'error',
156
+ message: error.message
157
+ };
158
+ }
159
+ }
160
+ }
161
+ //# sourceMappingURL=ratelimit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../src/ratelimit.ts"],"names":[],"mappings":"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"}
package/dist/test.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":""}
package/dist/test.js ADDED
@@ -0,0 +1,24 @@
1
+ import { Cluster } from "./cluster";
2
+ const cluster = new Cluster('roundrobin');
3
+ cluster.addOption({
4
+ id: 'deepseek',
5
+ request: 100,
6
+ per: 'minute',
7
+ mode: 'spread',
8
+ cb: async (input) => {
9
+ return `Processed: ${input.modelId} - ${input.chatId} by Deepseek`;
10
+ }
11
+ }, true);
12
+ cluster.on('completed', (data) => {
13
+ console.log('Completed:', data.response);
14
+ });
15
+ cluster.on('error', (data) => {
16
+ console.error('Error processing:', data.request, 'Error:', data.response);
17
+ });
18
+ for (let i = 0; i < 500; i++) {
19
+ await cluster.invoke({
20
+ modelId: 'gpt-4',
21
+ chatId: `chat-${i}`,
22
+ });
23
+ }
24
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,YAAY,CAAC,CAAC;AAEhD,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,MAAM,OAAO,CAAC,MAAM,CAAC;QACjB,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,QAAQ,CAAC,EAAE;KACtB,CAAC,CAAC;AACP,CAAC"}
package/dist/time.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export declare class Interval {
2
+ callbacks: any[];
3
+ constructor();
4
+ add: (cb: (...args: any[]) => Promise<any>, timeout: number) => void;
5
+ }
6
+ export declare class TimeCluster {
7
+ intervals: Interval[];
8
+ constructor(size: number);
9
+ waitFor: (cb: (...args: any[]) => Promise<any>, timeout: number) => void;
10
+ }
11
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAQ;IACjB,SAAS,EAAE,GAAG,EAAE,CAAM;;IAetB,GAAG,GAAI,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,UAG3D;CACJ;AAED,qBAAa,WAAW;IAEpB,SAAS,aAAyB;gBAEtB,IAAI,EAAE,MAAM;IAMxB,OAAO,GAAI,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,UAG/D;CACJ"}
package/dist/time.js ADDED
@@ -0,0 +1,32 @@
1
+ export class Interval {
2
+ callbacks = [];
3
+ constructor() {
4
+ setInterval(async () => {
5
+ const time = Date.now();
6
+ for (let i = this.callbacks.length - 1; i >= 0; i--) {
7
+ const item = this.callbacks[i];
8
+ if (time >= item.target) {
9
+ await item.cb();
10
+ this.callbacks.splice(i, 1);
11
+ }
12
+ }
13
+ }, 250);
14
+ }
15
+ add = (cb, timeout) => {
16
+ const target = Date.now() + timeout * 1000;
17
+ this.callbacks.push({ cb, target });
18
+ };
19
+ }
20
+ export class TimeCluster {
21
+ intervals = new Array();
22
+ constructor(size) {
23
+ for (let i = 0; i < size; i++) {
24
+ this.intervals.push(new Interval());
25
+ }
26
+ }
27
+ waitFor = (cb, timeout) => {
28
+ const interval = this.intervals[Math.floor(Math.random() * this.intervals.length)];
29
+ interval.add(cb, timeout);
30
+ };
31
+ }
32
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,QAAQ;IACjB,SAAS,GAAU,EAAE,CAAC;IAEtB;QACI,WAAW,CAAC,KAAK,IAAI,EAAE;YACnB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACxB,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAClD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBAC/B,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBACtB,MAAM,IAAI,CAAC,EAAE,EAAE,CAAC;oBAChB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAChC,CAAC;YACL,CAAC;QACL,CAAC,EAAE,GAAG,CAAC,CAAC;IACZ,CAAC;IAED,GAAG,GAAG,CAAC,EAAoC,EAAE,OAAe,EAAE,EAAE;QAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAC;QAC3C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACxC,CAAC,CAAA;CACJ;AAED,MAAM,OAAO,WAAW;IAEpB,SAAS,GAAG,IAAI,KAAK,EAAY,CAAC;IAElC,YAAY,IAAY;QACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC;QACxC,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAC,EAAoC,EAAE,OAAe,EAAE,EAAE;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QACnF,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAC9B,CAAC,CAAA;CACJ"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "ttc-rate-limit",
3
+ "version": "0.1.0",
4
+ "description": "A rate limiting library with worker pool support for parallel timeout management",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "private": false,
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc --watch",
16
+ "test": "bun test",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "rate-limit",
21
+ "rate-limiting",
22
+ "worker-pool",
23
+ "parallel",
24
+ "timeout",
25
+ "ttc"
26
+ ],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "devDependencies": {
30
+ "@types/bun": "latest",
31
+ "@types/node": "^25.2.0"
32
+ },
33
+ "peerDependencies": {
34
+ "typescript": "^5.9.3"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ }
39
+ }