@fetchmax/plugin-rate-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/dist/index.cjs ADDED
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ RateLimitError: () => RateLimitError,
24
+ default: () => index_default,
25
+ rateLimitPlugin: () => rateLimitPlugin
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ var RateLimitError = class _RateLimitError extends Error {
29
+ constructor(message, queueSize) {
30
+ super(message);
31
+ this.name = "RateLimitError";
32
+ this.queueSize = queueSize;
33
+ if (Error.captureStackTrace) {
34
+ Error.captureStackTrace(this, _RateLimitError);
35
+ }
36
+ }
37
+ };
38
+ function rateLimitPlugin(config) {
39
+ const {
40
+ maxRequests,
41
+ perMilliseconds,
42
+ queueOnLimit = true,
43
+ maxQueueSize = Infinity,
44
+ onRateLimit
45
+ } = config;
46
+ const timestamps = [];
47
+ const queue = [];
48
+ let queueProcessorTimeout = null;
49
+ function removeExpiredTimestamps() {
50
+ const now = Date.now();
51
+ const cutoff = now - perMilliseconds;
52
+ while (timestamps.length > 0 && timestamps[0] < cutoff) {
53
+ timestamps.shift();
54
+ }
55
+ }
56
+ function canMakeRequest() {
57
+ removeExpiredTimestamps();
58
+ return timestamps.length < maxRequests;
59
+ }
60
+ function getWaitTime() {
61
+ if (timestamps.length === 0) return 0;
62
+ const oldestTimestamp = timestamps[0];
63
+ const now = Date.now();
64
+ const elapsed = now - oldestTimestamp;
65
+ return Math.max(0, perMilliseconds - elapsed);
66
+ }
67
+ function processQueue() {
68
+ queueProcessorTimeout = null;
69
+ removeExpiredTimestamps();
70
+ while (queue.length > 0 && canMakeRequest()) {
71
+ const item = queue.shift();
72
+ if (item) {
73
+ timestamps.push(Date.now());
74
+ item.resolve();
75
+ }
76
+ }
77
+ scheduleQueueProcessing();
78
+ }
79
+ function scheduleQueueProcessing() {
80
+ if (queueProcessorTimeout) {
81
+ clearTimeout(queueProcessorTimeout);
82
+ queueProcessorTimeout = null;
83
+ }
84
+ if (queue.length === 0) {
85
+ return;
86
+ }
87
+ const waitTime = getWaitTime();
88
+ queueProcessorTimeout = setTimeout(() => {
89
+ processQueue();
90
+ }, waitTime + 10);
91
+ }
92
+ async function waitForSlot() {
93
+ removeExpiredTimestamps();
94
+ if (canMakeRequest()) {
95
+ timestamps.push(Date.now());
96
+ return;
97
+ }
98
+ if (queue.length >= maxQueueSize) {
99
+ throw new RateLimitError(
100
+ `Rate limit queue is full (${maxQueueSize} requests queued)`,
101
+ queue.length
102
+ );
103
+ }
104
+ if (!queueOnLimit) {
105
+ throw new RateLimitError(
106
+ `Rate limit exceeded: ${maxRequests} requests per ${perMilliseconds}ms`,
107
+ queue.length
108
+ );
109
+ }
110
+ const waitTime = getWaitTime();
111
+ if (onRateLimit) {
112
+ onRateLimit(queue.length, waitTime);
113
+ }
114
+ console.log(
115
+ `[RateLimit] Request queued. ${queue.length + 1} in queue. Wait time: ~${waitTime}ms`
116
+ );
117
+ const promise = new Promise((resolve, reject) => {
118
+ queue.push({ resolve, reject });
119
+ });
120
+ scheduleQueueProcessing();
121
+ return promise;
122
+ }
123
+ const plugin = {
124
+ name: "rate-limit",
125
+ async onRequest(request, _context) {
126
+ await waitForSlot();
127
+ return request;
128
+ },
129
+ /**
130
+ * Get rate limit statistics
131
+ */
132
+ getStats() {
133
+ removeExpiredTimestamps();
134
+ return {
135
+ requestCount: timestamps.length,
136
+ queueSize: queue.length,
137
+ timestamps: [...timestamps]
138
+ };
139
+ },
140
+ /**
141
+ * Reset rate limiter (clear timestamps and queue)
142
+ */
143
+ reset() {
144
+ timestamps.length = 0;
145
+ queue.length = 0;
146
+ if (queueProcessorTimeout) {
147
+ clearTimeout(queueProcessorTimeout);
148
+ queueProcessorTimeout = null;
149
+ }
150
+ }
151
+ };
152
+ return plugin;
153
+ }
154
+ var index_default = rateLimitPlugin;
155
+ // Annotate the CommonJS export names for ESM import in node:
156
+ 0 && (module.exports = {
157
+ RateLimitError,
158
+ rateLimitPlugin
159
+ });
@@ -0,0 +1,51 @@
1
+ import { Plugin } from '@fetchmax/core';
2
+
3
+ interface RateLimitConfig {
4
+ /** Maximum number of requests allowed */
5
+ maxRequests: number;
6
+ /** Time window in milliseconds */
7
+ perMilliseconds: number;
8
+ /** Queue requests when limit is reached (default: true) */
9
+ queueOnLimit?: boolean;
10
+ /** Maximum queue size (default: Infinity) */
11
+ maxQueueSize?: number;
12
+ /** Callback when rate limit is hit */
13
+ onRateLimit?: (queueSize: number, waitTime: number) => void;
14
+ }
15
+ declare class RateLimitError extends Error {
16
+ name: string;
17
+ queueSize: number;
18
+ constructor(message: string, queueSize: number);
19
+ }
20
+ interface RateLimitPlugin extends Plugin {
21
+ getStats: () => {
22
+ requestCount: number;
23
+ queueSize: number;
24
+ timestamps: number[];
25
+ };
26
+ reset: () => void;
27
+ }
28
+ /**
29
+ * Rate Limit Plugin
30
+ *
31
+ * Controls the rate of requests to prevent overwhelming servers or hitting API limits.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * // Allow 10 requests per second
36
+ * const client = new HttpClient().use(rateLimitPlugin({
37
+ * maxRequests: 10,
38
+ * perMilliseconds: 1000
39
+ * }));
40
+ *
41
+ * // Allow 100 requests per minute, throw error instead of queueing
42
+ * const client = new HttpClient().use(rateLimitPlugin({
43
+ * maxRequests: 100,
44
+ * perMilliseconds: 60000,
45
+ * queueOnLimit: false
46
+ * }));
47
+ * ```
48
+ */
49
+ declare function rateLimitPlugin(config: RateLimitConfig): RateLimitPlugin;
50
+
51
+ export { type RateLimitConfig, RateLimitError, type RateLimitPlugin, rateLimitPlugin as default, rateLimitPlugin };
@@ -0,0 +1,51 @@
1
+ import { Plugin } from '@fetchmax/core';
2
+
3
+ interface RateLimitConfig {
4
+ /** Maximum number of requests allowed */
5
+ maxRequests: number;
6
+ /** Time window in milliseconds */
7
+ perMilliseconds: number;
8
+ /** Queue requests when limit is reached (default: true) */
9
+ queueOnLimit?: boolean;
10
+ /** Maximum queue size (default: Infinity) */
11
+ maxQueueSize?: number;
12
+ /** Callback when rate limit is hit */
13
+ onRateLimit?: (queueSize: number, waitTime: number) => void;
14
+ }
15
+ declare class RateLimitError extends Error {
16
+ name: string;
17
+ queueSize: number;
18
+ constructor(message: string, queueSize: number);
19
+ }
20
+ interface RateLimitPlugin extends Plugin {
21
+ getStats: () => {
22
+ requestCount: number;
23
+ queueSize: number;
24
+ timestamps: number[];
25
+ };
26
+ reset: () => void;
27
+ }
28
+ /**
29
+ * Rate Limit Plugin
30
+ *
31
+ * Controls the rate of requests to prevent overwhelming servers or hitting API limits.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * // Allow 10 requests per second
36
+ * const client = new HttpClient().use(rateLimitPlugin({
37
+ * maxRequests: 10,
38
+ * perMilliseconds: 1000
39
+ * }));
40
+ *
41
+ * // Allow 100 requests per minute, throw error instead of queueing
42
+ * const client = new HttpClient().use(rateLimitPlugin({
43
+ * maxRequests: 100,
44
+ * perMilliseconds: 60000,
45
+ * queueOnLimit: false
46
+ * }));
47
+ * ```
48
+ */
49
+ declare function rateLimitPlugin(config: RateLimitConfig): RateLimitPlugin;
50
+
51
+ export { type RateLimitConfig, RateLimitError, type RateLimitPlugin, rateLimitPlugin as default, rateLimitPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,133 @@
1
+ // src/index.ts
2
+ var RateLimitError = class _RateLimitError extends Error {
3
+ constructor(message, queueSize) {
4
+ super(message);
5
+ this.name = "RateLimitError";
6
+ this.queueSize = queueSize;
7
+ if (Error.captureStackTrace) {
8
+ Error.captureStackTrace(this, _RateLimitError);
9
+ }
10
+ }
11
+ };
12
+ function rateLimitPlugin(config) {
13
+ const {
14
+ maxRequests,
15
+ perMilliseconds,
16
+ queueOnLimit = true,
17
+ maxQueueSize = Infinity,
18
+ onRateLimit
19
+ } = config;
20
+ const timestamps = [];
21
+ const queue = [];
22
+ let queueProcessorTimeout = null;
23
+ function removeExpiredTimestamps() {
24
+ const now = Date.now();
25
+ const cutoff = now - perMilliseconds;
26
+ while (timestamps.length > 0 && timestamps[0] < cutoff) {
27
+ timestamps.shift();
28
+ }
29
+ }
30
+ function canMakeRequest() {
31
+ removeExpiredTimestamps();
32
+ return timestamps.length < maxRequests;
33
+ }
34
+ function getWaitTime() {
35
+ if (timestamps.length === 0) return 0;
36
+ const oldestTimestamp = timestamps[0];
37
+ const now = Date.now();
38
+ const elapsed = now - oldestTimestamp;
39
+ return Math.max(0, perMilliseconds - elapsed);
40
+ }
41
+ function processQueue() {
42
+ queueProcessorTimeout = null;
43
+ removeExpiredTimestamps();
44
+ while (queue.length > 0 && canMakeRequest()) {
45
+ const item = queue.shift();
46
+ if (item) {
47
+ timestamps.push(Date.now());
48
+ item.resolve();
49
+ }
50
+ }
51
+ scheduleQueueProcessing();
52
+ }
53
+ function scheduleQueueProcessing() {
54
+ if (queueProcessorTimeout) {
55
+ clearTimeout(queueProcessorTimeout);
56
+ queueProcessorTimeout = null;
57
+ }
58
+ if (queue.length === 0) {
59
+ return;
60
+ }
61
+ const waitTime = getWaitTime();
62
+ queueProcessorTimeout = setTimeout(() => {
63
+ processQueue();
64
+ }, waitTime + 10);
65
+ }
66
+ async function waitForSlot() {
67
+ removeExpiredTimestamps();
68
+ if (canMakeRequest()) {
69
+ timestamps.push(Date.now());
70
+ return;
71
+ }
72
+ if (queue.length >= maxQueueSize) {
73
+ throw new RateLimitError(
74
+ `Rate limit queue is full (${maxQueueSize} requests queued)`,
75
+ queue.length
76
+ );
77
+ }
78
+ if (!queueOnLimit) {
79
+ throw new RateLimitError(
80
+ `Rate limit exceeded: ${maxRequests} requests per ${perMilliseconds}ms`,
81
+ queue.length
82
+ );
83
+ }
84
+ const waitTime = getWaitTime();
85
+ if (onRateLimit) {
86
+ onRateLimit(queue.length, waitTime);
87
+ }
88
+ console.log(
89
+ `[RateLimit] Request queued. ${queue.length + 1} in queue. Wait time: ~${waitTime}ms`
90
+ );
91
+ const promise = new Promise((resolve, reject) => {
92
+ queue.push({ resolve, reject });
93
+ });
94
+ scheduleQueueProcessing();
95
+ return promise;
96
+ }
97
+ const plugin = {
98
+ name: "rate-limit",
99
+ async onRequest(request, _context) {
100
+ await waitForSlot();
101
+ return request;
102
+ },
103
+ /**
104
+ * Get rate limit statistics
105
+ */
106
+ getStats() {
107
+ removeExpiredTimestamps();
108
+ return {
109
+ requestCount: timestamps.length,
110
+ queueSize: queue.length,
111
+ timestamps: [...timestamps]
112
+ };
113
+ },
114
+ /**
115
+ * Reset rate limiter (clear timestamps and queue)
116
+ */
117
+ reset() {
118
+ timestamps.length = 0;
119
+ queue.length = 0;
120
+ if (queueProcessorTimeout) {
121
+ clearTimeout(queueProcessorTimeout);
122
+ queueProcessorTimeout = null;
123
+ }
124
+ }
125
+ };
126
+ return plugin;
127
+ }
128
+ var index_default = rateLimitPlugin;
129
+ export {
130
+ RateLimitError,
131
+ index_default as default,
132
+ rateLimitPlugin
133
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@fetchmax/plugin-rate-limit",
3
+ "version": "1.0.0",
4
+ "description": "Rate limiting plugin for FetchMax to control request frequency",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
22
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
23
+ },
24
+ "keywords": [
25
+ "fetchmax",
26
+ "plugin",
27
+ "rate-limit",
28
+ "throttle",
29
+ "http",
30
+ "fetch"
31
+ ],
32
+ "author": "FetchMax Contributors",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/fetchmax/fetchmax.git",
37
+ "directory": "packages/plugins/rate-limit"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/fetchmax/fetchmax/issues"
41
+ },
42
+ "homepage": "https://github.com/fetchmax/fetchmax#readme",
43
+ "sideEffects": false,
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "engines": {
48
+ "node": ">=18.0.0"
49
+ },
50
+ "peerDependencies": {
51
+ "@fetchmax/core": "^1.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@fetchmax/core": "*",
55
+ "tsup": "^8.0.1",
56
+ "typescript": "^5.3.3"
57
+ }
58
+ }