bucketflow 1.0.0 → 1.0.2
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 +90 -24
- package/dist/fixed-window.d.ts +2 -3
- package/dist/fixed-window.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.mjs +23 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,47 +8,103 @@ Lightweight in-memory rate limiting utilities for Node.js and TypeScript.
|
|
|
8
8
|
npm install bucketflow
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Public exports
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
`src/index.ts` currently exports these three classes:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
export { FixedWindow } from "./fixed-window";
|
|
17
|
+
export { TokenBucket } from "./token-bucket";
|
|
18
|
+
export { SlidingWindow } from "./sliding-window";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Each limiter stores its counters in memory and exposes a `checkLimit(key)`
|
|
22
|
+
method. The `key` identifies the client, user, IP address, API key, or any
|
|
23
|
+
other group that should have its own limit.
|
|
24
|
+
|
|
25
|
+
## Using the limiters
|
|
26
|
+
|
|
27
|
+
### `TokenBucket`
|
|
14
28
|
|
|
15
29
|
```ts
|
|
16
30
|
import { TokenBucket } from "bucketflow";
|
|
17
31
|
|
|
18
|
-
const limiter = new TokenBucket(10,
|
|
32
|
+
const limiter = new TokenBucket(10, 1_000);
|
|
19
33
|
const result = limiter.checkLimit("user-123");
|
|
20
34
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
console.log(result);
|
|
36
|
+
// { allowed: true, message: "Too many requests", tokens: 9 }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Constructor:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
new TokenBucket(capacity, refillRate, message?)
|
|
24
43
|
```
|
|
25
44
|
|
|
26
|
-
|
|
27
|
-
|
|
45
|
+
- `capacity`: maximum number of tokens.
|
|
46
|
+
- `refillRate`: milliseconds between token refills.
|
|
47
|
+
- `message`: optional message returned when a request is rejected.
|
|
48
|
+
|
|
49
|
+
`checkLimit(key)` returns `{ allowed, message, tokens }`.
|
|
28
50
|
|
|
29
|
-
###
|
|
51
|
+
### `FixedWindow`
|
|
30
52
|
|
|
31
53
|
```ts
|
|
32
54
|
import { FixedWindow } from "bucketflow";
|
|
33
55
|
|
|
34
|
-
const limiter = new FixedWindow(
|
|
56
|
+
const limiter = new FixedWindow(100, 60_000, "Too many requests");
|
|
35
57
|
const result = limiter.checkLimit("user-123");
|
|
58
|
+
|
|
59
|
+
console.log(result);
|
|
60
|
+
// { allowed: true, reqRemaining: 99, message: "Too many requests" }
|
|
36
61
|
```
|
|
37
62
|
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
Constructor:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
new FixedWindow(maxNum, window, message?)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `maxNum`: maximum number of requests in the window.
|
|
70
|
+
- `window`: window duration in milliseconds.
|
|
71
|
+
- `message`: optional message returned when a request is rejected.
|
|
72
|
+
|
|
73
|
+
`checkLimit(key)` returns `{ allowed, reqRemaining, message }`.
|
|
40
74
|
|
|
41
|
-
###
|
|
75
|
+
### `SlidingWindow`
|
|
42
76
|
|
|
43
77
|
```ts
|
|
44
78
|
import { SlidingWindow } from "bucketflow";
|
|
45
79
|
|
|
46
|
-
const limiter = new SlidingWindow(60_000, 1_000, 100);
|
|
80
|
+
const limiter = new SlidingWindow(60_000, 1_000, 100, "Too many requests");
|
|
47
81
|
const result = limiter.checkLimit("user-123");
|
|
82
|
+
|
|
83
|
+
console.log(result);
|
|
84
|
+
// { allowed: true, counter: 1, message: "Too many requests" }
|
|
48
85
|
```
|
|
49
86
|
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
Constructor:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
new SlidingWindow(window, refreshRate, maxRequests, message?)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- `window`: window duration in milliseconds.
|
|
94
|
+
- `refreshRate`: counter refresh interval in milliseconds.
|
|
95
|
+
- `maxRequests`: maximum requests tracked by the limiter.
|
|
96
|
+
- `message`: optional message returned when a request is rejected.
|
|
97
|
+
|
|
98
|
+
`checkLimit(key)` returns `{ allowed, counter, message }`.
|
|
99
|
+
|
|
100
|
+
## Return values
|
|
101
|
+
|
|
102
|
+
Each limiter returns an object with an `allowed` boolean and a message. The
|
|
103
|
+
additional field depends on the limiter:
|
|
104
|
+
|
|
105
|
+
- `TokenBucket`: `tokens` is the number of tokens remaining.
|
|
106
|
+
- `FixedWindow`: `reqRemaining` is the reported number of requests remaining.
|
|
107
|
+
- `SlidingWindow`: `counter` is the current request counter.
|
|
52
108
|
|
|
53
109
|
## Framework integrations
|
|
54
110
|
|
|
@@ -77,6 +133,15 @@ app.use((req, res, next) => {
|
|
|
77
133
|
});
|
|
78
134
|
```
|
|
79
135
|
|
|
136
|
+
You can use `FixedWindow` or `SlidingWindow` in the same middleware by
|
|
137
|
+
replacing the limiter construction:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const limiter = new FixedWindow(100, 60_000);
|
|
141
|
+
// or
|
|
142
|
+
const limiter = new SlidingWindow(60_000, 1_000, 100);
|
|
143
|
+
```
|
|
144
|
+
|
|
80
145
|
### Fastify
|
|
81
146
|
|
|
82
147
|
Fastify can use a limiter in a `preHandler` hook:
|
|
@@ -131,16 +196,17 @@ export class RateLimitMiddleware implements NestMiddleware {
|
|
|
131
196
|
Register the middleware in a module with `MiddlewareConsumer`, or adapt the
|
|
132
197
|
same pattern into a NestJS guard when route-level control is preferred.
|
|
133
198
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
The package currently exports:
|
|
199
|
+
The same middleware pattern works with `FixedWindow` and `SlidingWindow`:
|
|
137
200
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
201
|
+
```ts
|
|
202
|
+
private readonly limiter = new FixedWindow(100, 60_000);
|
|
203
|
+
// or
|
|
204
|
+
private readonly limiter = new SlidingWindow(60_000, 1_000, 100);
|
|
205
|
+
```
|
|
141
206
|
|
|
142
|
-
All limiters are in-memory and local to the running process. They
|
|
143
|
-
|
|
207
|
+
All limiters are in-memory and local to the running process. They do not share
|
|
208
|
+
state across multiple Node.js processes or servers, and state is lost when the
|
|
209
|
+
process restarts.
|
|
144
210
|
|
|
145
211
|
## License
|
|
146
212
|
|
package/dist/fixed-window.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { FixedWindowProps, FixedWindowResponse } from "../types";
|
|
1
|
+
import type { FixedWindowProps, FixedWindowResponse } from "../types";
|
|
2
2
|
export declare class FixedWindow implements FixedWindowProps {
|
|
3
|
-
key: string;
|
|
4
3
|
private store;
|
|
5
4
|
message: string;
|
|
6
5
|
maxNum: number;
|
|
7
6
|
window: number;
|
|
8
|
-
constructor(
|
|
7
|
+
constructor(maxNum: number, window: number, message?: string);
|
|
9
8
|
checkLimit(key: string): FixedWindowResponse;
|
|
10
9
|
}
|
|
11
10
|
//# sourceMappingURL=fixed-window.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixed-window.d.ts","sourceRoot":"","sources":["../src/fixed-window.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAS,mBAAmB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"fixed-window.d.ts","sourceRoot":"","sources":["../src/fixed-window.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAS,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE7E,qBAAa,WAAY,YAAW,gBAAgB;IAClD,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IAEf,YACE,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,MAA4B,EAKtC;IACD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CA0C3C;CACF"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
FixedWindow: () => FixedWindow,
|
|
24
|
+
LeakyBucket: () => LeakyBucket,
|
|
24
25
|
SlidingWindow: () => SlidingWindow,
|
|
25
26
|
TokenBucket: () => TokenBucket
|
|
26
27
|
});
|
|
@@ -28,9 +29,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
28
29
|
|
|
29
30
|
// src/fixed-window.ts
|
|
30
31
|
var FixedWindow = class {
|
|
31
|
-
constructor(
|
|
32
|
+
constructor(maxNum, window, message = "Too many requests") {
|
|
32
33
|
this.store = /* @__PURE__ */ new Map();
|
|
33
|
-
this.key = key;
|
|
34
34
|
this.message = message;
|
|
35
35
|
this.maxNum = maxNum;
|
|
36
36
|
this.window = window;
|
|
@@ -160,9 +160,31 @@ var SlidingWindow = class {
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
};
|
|
163
|
+
|
|
164
|
+
// src/leaky-bucket.ts
|
|
165
|
+
var LeakyBucket = class {
|
|
166
|
+
constructor(capacity, leakRate) {
|
|
167
|
+
this.capacity = capacity;
|
|
168
|
+
this.queue = [];
|
|
169
|
+
this.leakRate = leakRate;
|
|
170
|
+
}
|
|
171
|
+
addRequest(req) {
|
|
172
|
+
if (this.queue.length >= this.capacity) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
this.queue.push(req);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
leak() {
|
|
179
|
+
if (this.queue.length > 0) {
|
|
180
|
+
this.queue.shift();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
163
184
|
// Annotate the CommonJS export names for ESM import in node:
|
|
164
185
|
0 && (module.exports = {
|
|
165
186
|
FixedWindow,
|
|
187
|
+
LeakyBucket,
|
|
166
188
|
SlidingWindow,
|
|
167
189
|
TokenBucket
|
|
168
190
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// src/fixed-window.ts
|
|
2
2
|
var FixedWindow = class {
|
|
3
|
-
constructor(
|
|
3
|
+
constructor(maxNum, window, message = "Too many requests") {
|
|
4
4
|
this.store = /* @__PURE__ */ new Map();
|
|
5
|
-
this.key = key;
|
|
6
5
|
this.message = message;
|
|
7
6
|
this.maxNum = maxNum;
|
|
8
7
|
this.window = window;
|
|
@@ -132,8 +131,30 @@ var SlidingWindow = class {
|
|
|
132
131
|
};
|
|
133
132
|
}
|
|
134
133
|
};
|
|
134
|
+
|
|
135
|
+
// src/leaky-bucket.ts
|
|
136
|
+
var LeakyBucket = class {
|
|
137
|
+
constructor(capacity, leakRate) {
|
|
138
|
+
this.capacity = capacity;
|
|
139
|
+
this.queue = [];
|
|
140
|
+
this.leakRate = leakRate;
|
|
141
|
+
}
|
|
142
|
+
addRequest(req) {
|
|
143
|
+
if (this.queue.length >= this.capacity) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
this.queue.push(req);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
leak() {
|
|
150
|
+
if (this.queue.length > 0) {
|
|
151
|
+
this.queue.shift();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
135
155
|
export {
|
|
136
156
|
FixedWindow,
|
|
157
|
+
LeakyBucket,
|
|
137
158
|
SlidingWindow,
|
|
138
159
|
TokenBucket
|
|
139
160
|
};
|