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 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
- ## 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("api", "Too many requests", 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" }
36
61
  ```
37
62
 
38
- The constructor accepts a limiter key, optional rejection message, maximum
39
- requests, and window duration in milliseconds.
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
- ### Sliding window
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 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 }`.
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
- ## API
135
-
136
- The package currently exports:
199
+ The same middleware pattern works with `FixedWindow` and `SlidingWindow`:
137
200
 
138
- - `TokenBucket`
139
- - `FixedWindow`
140
- - `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
+ ```
141
206
 
142
- All limiters are in-memory and local to the running process. They are not
143
- intended to coordinate limits across multiple Node.js processes or servers.
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
 
@@ -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(key: string, message: string | undefined, maxNum: number, window: number);
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;AAExE,qBAAa,WAAY,YAAW,gBAAgB;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IAEf,YACE,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,YAAsB,EACrC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EAMf;IAED,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CA0C3C;CACF"}
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
@@ -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
  });
@@ -28,9 +29,8 @@ module.exports = __toCommonJS(index_exports);
28
29
 
29
30
  // src/fixed-window.ts
30
31
  var FixedWindow = class {
31
- constructor(key, message = "Too many requests", maxNum, window) {
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(key, message = "Too many requests", maxNum, window) {
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bucketflow",
3
- "version": "1.0.0",
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": [