@thi.ng/leaky-bucket 0.1.0 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2025-03-09T19:21:53Z
3
+ - **Last updated**: 2025-03-17T13:40:35Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -11,6 +11,16 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
11
11
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
12
12
  and/or version bumps of transitive dependencies.
13
13
 
14
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/leaky-bucket@0.2.0) (2025-03-10)
15
+
16
+ #### 🚀 Features
17
+
18
+ - add `.hasCapacity()` ([bdb13d2](https://github.com/thi-ng/umbrella/commit/bdb13d2))
19
+
20
+ #### 🩹 Bug fixes
21
+
22
+ - fix delta time handling in `.leak()` ([a42c9d5](https://github.com/thi-ng/umbrella/commit/a42c9d5))
23
+
14
24
  ## [0.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/leaky-bucket@0.1.0) (2025-03-09)
15
25
 
16
26
  #### 🚀 Features
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <!-- This file is generated - DO NOT EDIT! -->
2
2
  <!-- Please see: https://github.com/thi-ng/umbrella/blob/develop/CONTRIBUTING.md#changes-to-readme-files -->
3
- # ![@thi.ng/leaky-bucket](https://media.thi.ng/umbrella/banners-20230807/thing-leaky-bucket.svg?92dc6543)
3
+ # ![@thi.ng/leaky-bucket](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/banners/thing-leaky-bucket.svg?92dc6543)
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@thi.ng/leaky-bucket.svg)](https://www.npmjs.com/package/@thi.ng/leaky-bucket)
6
6
  ![npm downloads](https://img.shields.io/npm/dm/@thi.ng/leaky-bucket.svg)
@@ -24,11 +24,59 @@
24
24
 
25
25
  ## About
26
26
 
27
- Configurable, counter-based Leaky Bucket abstractions.
27
+ Configurable, counter-based Leaky Bucket abstractions for generalized rate-limiting purposes.
28
28
 
29
- Reference:
29
+ [Leaky Buckets](https://en.wikipedia.org/wiki/Leaky_bucket) are commonly used in
30
+ communication networks for rate limiting, traffic shaping and bandwidth control,
31
+ but are equally useful in other domains requiring similar constraints.
30
32
 
31
- - https://en.wikipedia.org/wiki/Leaky_bucket
33
+ A Leaky Bucket is a managed counter with an enforced maximum value (i.e. bucket
34
+ capacity). The counter is incremented for each a new event to check if it
35
+ can/should be processed. If the bucket capacity has already been reached, the
36
+ bucket will report an overflow, which we can then handle accordingly (e.g. by
37
+ dropping or queuing events). The bucket also has a configurable time interval at
38
+ which the counter is decreasing (aka the "leaking" behavior) until it reaches
39
+ zero again (i.e. until the bucket is empty). Altogether, this setup can be
40
+ utilized to ensure both an average rate, whilst also supporting temporary
41
+ bursting in a controlled fashion.
42
+
43
+ ```ts tangle:export/readme-1.ts
44
+ import { LeakyBucket } from "@thi.ng/leaky-bucket";
45
+
46
+ // create bucket w/ 1Hz mean target rate, burstable to 3Hz
47
+ const bucket = new LeakyBucket({ capacity: 3, leakInterval: 1000 });
48
+
49
+ let event = 0;
50
+ let t0 = Date.now();
51
+
52
+ // trigger events at 5Hz
53
+ setInterval(() => {
54
+ event++;
55
+ // update bucket and only log successful events (discard the rest)
56
+ if (bucket.update()) {
57
+ console.log("time", Date.now() - t0, "/ event", event);
58
+ }
59
+ }, 200);
60
+
61
+ // time 200 / event 1 <-- initial burst
62
+ // time 401 / event 2 <-- initial burst
63
+ // time 601 / event 3 <-- initial burst
64
+ // time 1003 / event 5 <-- average rate enforced
65
+ // time 2007 / event 10
66
+ // time 3012 / event 15
67
+ // time 4017 / event 20
68
+ // ...
69
+ ```
70
+
71
+ In addition to individual `LeakyBucket`s, this package also provides a
72
+ `LeakyBucketMap` for managing multiple buckets in a key-value store, with shared
73
+ configuration and more efficient updates (only using a single timer). Other
74
+ features include:
75
+
76
+ - enforces max number of active (non-empty) buckets
77
+ - auto-pruning of empty buckets
78
+ - auto-creation of new buckets
79
+ - per-bucket capacity overrides
32
80
 
33
81
  ## Status
34
82
 
@@ -62,7 +110,7 @@ For Node.js REPL:
62
110
  const lb = await import("@thi.ng/leaky-bucket");
63
111
  ```
64
112
 
65
- Package sizes (brotli'd, pre-treeshake): ESM: 591 bytes
113
+ Package sizes (brotli'd, pre-treeshake): ESM: 624 bytes
66
114
 
67
115
  ## Dependencies
68
116
 
@@ -74,7 +122,7 @@ Note: @thi.ng/api is in _most_ cases a type-only import (not used at runtime)
74
122
 
75
123
  [Generated API docs](https://docs.thi.ng/umbrella/leaky-bucket/)
76
124
 
77
- ```ts tangle:export/readme-1.ts
125
+ ```ts tangle:export/readme-2.ts
78
126
  import { LeakyBucketMap } from "@thi.ng/leaky-bucket";
79
127
 
80
128
  const buckets = new LeakyBucketMap({
@@ -83,9 +131,9 @@ const buckets = new LeakyBucketMap({
83
131
  leakInterval: 1000,
84
132
  });
85
133
 
86
- buckets.update("a") //true
87
- buckets.update("a") //true
88
- buckets.update("a") //true
134
+ buckets.update("a") // true
135
+ buckets.update("a") // true
136
+ buckets.update("a") // true
89
137
 
90
138
  // max capacity=3 reached
91
139
  buckets.update("a"); // false
package/index.d.ts CHANGED
@@ -67,7 +67,18 @@ export declare class LeakyBucketMap<K> implements IRelease {
67
67
  lastLeak: number;
68
68
  timer: any;
69
69
  constructor(opts?: Partial<LeakyBucketMapOpts<K>>);
70
+ /**
71
+ * Returns true, if there's an active bucket for given `key`.
72
+ *
73
+ * @param key
74
+ */
70
75
  has(key: K): boolean;
76
+ /**
77
+ * Attempts to look up bucket for given `key` and returns it (or `undefined`
78
+ * if there's no active bucket for that key).
79
+ *
80
+ * @param key
81
+ */
71
82
  get(key: K): LeakyBucket | undefined;
72
83
  /**
73
84
  * Calls {@link LeakyBucket.update} for given bucket ID and returns its
@@ -84,6 +95,14 @@ export declare class LeakyBucketMap<K> implements IRelease {
84
95
  * @param capacity
85
96
  */
86
97
  update(key: K, capacity?: number | undefined): boolean;
98
+ /**
99
+ * Returns true if bucket for given `key` has free capacity, or if no such
100
+ * bucket yet exists, if the map itself has capacity for creating a new
101
+ * bucket.
102
+ *
103
+ * @param key
104
+ */
105
+ hasCapacity(key: K): boolean;
87
106
  /**
88
107
  * Leaks all bucket counters (taking into account current/given timestamp)
89
108
  * and removes those which emptied. If {@link LeakyBucketMapOpts.onEmpty} is
@@ -118,6 +137,10 @@ export declare class LeakyBucket implements IRelease {
118
137
  * capacity had already been reached.
119
138
  */
120
139
  update(): boolean;
140
+ /**
141
+ * Returns true if the bucket has free capacity.
142
+ */
143
+ hasCapacity(): boolean;
121
144
  /**
122
145
  * Leaks bucket's counter (taking into account current/given timestamp) and
123
146
  * then returns one of the following values:
package/index.js CHANGED
@@ -13,9 +13,20 @@ class LeakyBucketMap {
13
13
  leakInterval;
14
14
  lastLeak = -1;
15
15
  timer;
16
+ /**
17
+ * Returns true, if there's an active bucket for given `key`.
18
+ *
19
+ * @param key
20
+ */
16
21
  has(key) {
17
22
  return this.buckets.has(key);
18
23
  }
24
+ /**
25
+ * Attempts to look up bucket for given `key` and returns it (or `undefined`
26
+ * if there's no active bucket for that key).
27
+ *
28
+ * @param key
29
+ */
19
30
  get(key) {
20
31
  return this.buckets.get(key);
21
32
  }
@@ -46,6 +57,17 @@ class LeakyBucketMap {
46
57
  }
47
58
  return true;
48
59
  }
60
+ /**
61
+ * Returns true if bucket for given `key` has free capacity, or if no such
62
+ * bucket yet exists, if the map itself has capacity for creating a new
63
+ * bucket.
64
+ *
65
+ * @param key
66
+ */
67
+ hasCapacity(key) {
68
+ const bucket = this.buckets.get(key);
69
+ return bucket ? bucket.level < bucket.capacity : this.buckets.size < this.maxBuckets;
70
+ }
49
71
  /**
50
72
  * Leaks all bucket counters (taking into account current/given timestamp)
51
73
  * and removes those which emptied. If {@link LeakyBucketMapOpts.onEmpty} is
@@ -101,6 +123,12 @@ class LeakyBucket {
101
123
  this.level++;
102
124
  return true;
103
125
  }
126
+ /**
127
+ * Returns true if the bucket has free capacity.
128
+ */
129
+ hasCapacity() {
130
+ return this.level < this.capacity;
131
+ }
104
132
  /**
105
133
  * Leaks bucket's counter (taking into account current/given timestamp) and
106
134
  * then returns one of the following values:
@@ -116,13 +144,13 @@ class LeakyBucket {
116
144
  leak(now = Date.now()) {
117
145
  const delta = now - this.lastLeak;
118
146
  if (delta < this.leakInterval || !this.level) return;
147
+ this.lastLeak = now;
119
148
  const loss = Math.floor(delta / this.leakInterval);
120
149
  if (this.level <= loss) {
121
150
  this.level = 0;
122
151
  return false;
123
152
  }
124
153
  this.level -= loss;
125
- this.lastLeak = now;
126
154
  return true;
127
155
  }
128
156
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@thi.ng/leaky-bucket",
3
- "version": "0.1.0",
4
- "description": "Configurable, counter-based Leaky Bucket abstractions",
3
+ "version": "0.2.1",
4
+ "description": "Configurable, counter-based Leaky Bucket abstractions for generalized rate-limiting purposes",
5
5
  "type": "module",
6
6
  "module": "./index.js",
7
7
  "typings": "./index.d.ts",
@@ -39,12 +39,12 @@
39
39
  "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@thi.ng/api": "^8.11.22"
42
+ "@thi.ng/api": "^8.11.23"
43
43
  },
44
44
  "devDependencies": {
45
- "esbuild": "^0.25.0",
46
- "typedoc": "^0.27.7",
47
- "typescript": "^5.7.3"
45
+ "esbuild": "^0.25.1",
46
+ "typedoc": "^0.28.0",
47
+ "typescript": "^5.8.2"
48
48
  },
49
49
  "keywords": [
50
50
  "associative",
@@ -79,5 +79,5 @@
79
79
  "status": "alpha",
80
80
  "year": 2025
81
81
  },
82
- "gitHead": "55987d581e4985b1c3091ba6be3f9b53a0c5eeea\n"
82
+ "gitHead": "dfecb91b5b6a05db32d96f261e0c0ac6319240b9\n"
83
83
  }