bucketflow 1.0.1 → 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 CHANGED
@@ -8,47 +8,94 @@ Lightweight in-memory rate limiting utilities for Node.js and TypeScript.
8
8
  npm install bucketflow
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Public exports
12
12
 
13
- ### Token bucket
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, 1000);
32
+ const limiter = new TokenBucket(10, 1_000);
19
33
  const result = limiter.checkLimit("user-123");
20
34
 
21
- if (!result.allowed) {
22
- console.log(result.message);
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
- The constructor accepts `capacity`, `refillRate` in milliseconds, and an
27
- optional rejection message.
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
- ### Fixed window
51
+ ### `FixedWindow`
30
52
 
31
53
  ```ts
32
54
  import { FixedWindow } from "bucketflow";
33
55
 
34
- const limiter = new FixedWindow(100, 60_000);
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" }
61
+ ```
62
+
63
+ Constructor:
64
+
65
+ ```ts
66
+ new FixedWindow(maxNum, window, message?)
36
67
  ```
37
68
 
38
- The constructor accepts the maximum number of requests, the window duration
39
- in milliseconds, and an optional rejection message.
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.
40
72
 
41
- ### Sliding window
73
+ `checkLimit(key)` returns `{ allowed, reqRemaining, message }`.
74
+
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
- The constructor accepts the window duration, refresh rate, maximum requests,
51
- and an optional rejection message. Durations and rates are in milliseconds.
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 }`.
52
99
 
53
100
  ## Return values
54
101
 
@@ -86,6 +133,15 @@ app.use((req, res, next) => {
86
133
  });
87
134
  ```
88
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
+
89
145
  ### Fastify
90
146
 
91
147
  Fastify can use a limiter in a `preHandler` hook:
@@ -140,13 +196,13 @@ export class RateLimitMiddleware implements NestMiddleware {
140
196
  Register the middleware in a module with `MiddlewareConsumer`, or adapt the
141
197
  same pattern into a NestJS guard when route-level control is preferred.
142
198
 
143
- ## API
144
-
145
- The package currently exports these classes:
199
+ The same middleware pattern works with `FixedWindow` and `SlidingWindow`:
146
200
 
147
- - `TokenBucket`
148
- - `FixedWindow`
149
- - `SlidingWindow`
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
+ ```
150
206
 
151
207
  All limiters are in-memory and local to the running process. They do not share
152
208
  state across multiple Node.js processes or servers, and state is lost when the
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./fixed-window";
2
2
  export * from "./token-bucket";
3
3
  export * from "./sliding-window";
4
+ export * from "./leaky-bucket";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -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
  });
@@ -159,9 +160,31 @@ var SlidingWindow = class {
159
160
  };
160
161
  }
161
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
+ };
162
184
  // Annotate the CommonJS export names for ESM import in node:
163
185
  0 && (module.exports = {
164
186
  FixedWindow,
187
+ LeakyBucket,
165
188
  SlidingWindow,
166
189
  TokenBucket
167
190
  });
package/dist/index.mjs CHANGED
@@ -131,8 +131,30 @@ var SlidingWindow = class {
131
131
  };
132
132
  }
133
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
+ };
134
155
  export {
135
156
  FixedWindow,
157
+ LeakyBucket,
136
158
  SlidingWindow,
137
159
  TokenBucket
138
160
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bucketflow",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Rate limiting for Node.js with token bucket, fixed window, and leaky bucket algorithms",
5
5
  "main": "dist/index.js",
6
6
  "keywords": [