tiny-essentials 1.24.5 → 1.25.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/README.md +8 -8
- package/changelog/1/25/0.md +13 -0
- package/changelog/1/25/1.md +18 -0
- package/dist/v1/TinyAnalogClock.min.js +1 -0
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyTextDiffer.min.js +1 -0
- package/dist/v1/basics/html.cjs +151 -2
- package/dist/v1/basics/html.d.mts +109 -0
- package/dist/v1/basics/html.mjs +135 -2
- package/dist/v1/build/TinyAnalogClock.cjs +7 -0
- package/dist/v1/build/TinyAnalogClock.d.mts +3 -0
- package/dist/v1/build/TinyAnalogClock.mjs +2 -0
- package/dist/v1/build/TinyTextDiffer.cjs +7 -0
- package/dist/v1/build/TinyTextDiffer.d.mts +3 -0
- package/dist/v1/build/TinyTextDiffer.mjs +2 -0
- package/dist/v1/index.cjs +4 -0
- package/dist/v1/index.d.mts +3 -1
- package/dist/v1/index.mjs +3 -1
- package/dist/v1/libs/TinyAnalogClock.cjs +738 -0
- package/dist/v1/libs/TinyAnalogClock.d.mts +342 -0
- package/dist/v1/libs/TinyAnalogClock.mjs +653 -0
- package/dist/v1/libs/TinyRateLimiter.cjs +215 -31
- package/dist/v1/libs/TinyRateLimiter.d.mts +179 -25
- package/dist/v1/libs/TinyRateLimiter.mjs +215 -31
- package/dist/v1/libs/TinyTextDiffer.cjs +288 -0
- package/dist/v1/libs/TinyTextDiffer.d.mts +109 -0
- package/dist/v1/libs/TinyTextDiffer.mjs +255 -0
- package/docs/v1/README.md +5 -8
- package/docs/v1/basics/html.md +69 -0
- package/docs/v1/libs/TinyAnalogClock.md +295 -0
- package/docs/v1/libs/TinyRateLimiter.md +11 -0
- package/docs/v1/libs/TinyTextDiffer.md +114 -0
- package/package.json +12 -4
- package/docs/v1/Ai-Tips.md +0 -51
- package/docs/v1/Personal-Ai-Prompts(portuguese).md +0 -134
- package/docs/v1/Personal-Ai-Prompts.md +0 -113
|
@@ -1,60 +1,188 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Callback triggered when a group's stored hit history exceeds
|
|
5
|
+
* the configured memory limit.
|
|
6
|
+
*
|
|
7
|
+
* This callback is purely informational and does not block execution.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {(groupId: string) => void} OnMemoryExceeded
|
|
10
|
+
*/
|
|
4
11
|
|
|
5
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Callback triggered when a group is considered expired and removed
|
|
14
|
+
* during the cleanup process.
|
|
15
|
+
*
|
|
16
|
+
* This usually happens when the group remains inactive longer than
|
|
17
|
+
* its configured TTL or the global maxIdle value.
|
|
18
|
+
*
|
|
19
|
+
* @typedef {(groupId: string) => void} OnGroupExpired
|
|
20
|
+
*/
|
|
6
21
|
|
|
7
22
|
/**
|
|
8
|
-
*
|
|
23
|
+
* A lightweight and flexible rate limiter supporting both user-based
|
|
24
|
+
* and group-based throttling.
|
|
25
|
+
*
|
|
26
|
+
* ## Core Concepts
|
|
27
|
+
* - Every user belongs to a group.
|
|
28
|
+
* - If no explicit group is assigned, the user acts as their own group.
|
|
29
|
+
* - All users within the same group share the same hit history and limits.
|
|
30
|
+
*
|
|
31
|
+
* ## Supported Limiting Strategies
|
|
32
|
+
* - Max number of hits
|
|
33
|
+
* - Time-based sliding window
|
|
34
|
+
* - Combination of both
|
|
35
|
+
*
|
|
36
|
+
* ## Extra Features
|
|
37
|
+
* - Per-group TTL (time-to-live)
|
|
38
|
+
* - Automatic cleanup of inactive groups
|
|
39
|
+
* - Optional memory cap per group
|
|
40
|
+
* - Runtime metrics and statistics
|
|
9
41
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* rate
|
|
42
|
+
* ## Interval Window (Sliding Window) Behavior
|
|
43
|
+
*
|
|
44
|
+
* This rate limiter uses a **sliding time window** strategy when `interval`
|
|
45
|
+
* is configured.
|
|
46
|
+
*
|
|
47
|
+
* ### How it works
|
|
48
|
+
* - Each hit is stored as a timestamp (Date.now()).
|
|
49
|
+
* - On every new hit and rate check, timestamps older than:
|
|
50
|
+
*
|
|
51
|
+
* `now - interval`
|
|
52
|
+
*
|
|
53
|
+
* are discarded.
|
|
54
|
+
*
|
|
55
|
+
* - Only hits that occurred within the last `interval` milliseconds
|
|
56
|
+
* are considered valid.
|
|
57
|
+
*
|
|
58
|
+
* ### Practical example
|
|
59
|
+
* If:
|
|
60
|
+
* - interval = 10_000 (10 seconds)
|
|
61
|
+
* - maxHits = 5
|
|
62
|
+
*
|
|
63
|
+
* Then:
|
|
64
|
+
* - Any group may perform **up to 5 hits in any rolling 10-second window**.
|
|
65
|
+
* - The window moves continuously with time.
|
|
66
|
+
*
|
|
67
|
+
* This avoids burst issues common in fixed-window rate limiters.
|
|
68
|
+
*
|
|
69
|
+
* This class is designed to be deterministic, predictable,
|
|
70
|
+
* and safe to use in long-running Node.js processes.
|
|
13
71
|
*/
|
|
14
72
|
class TinyRateLimiter {
|
|
15
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Maximum number of timestamps stored per group.
|
|
75
|
+
* When exceeded, older entries are discarded.
|
|
76
|
+
*
|
|
77
|
+
* `null` means unlimited.
|
|
78
|
+
*
|
|
79
|
+
* @type {number|null}
|
|
80
|
+
*/
|
|
16
81
|
#maxMemory = null;
|
|
17
82
|
|
|
18
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Internal timer reference used for periodic cleanup.
|
|
85
|
+
*
|
|
86
|
+
* @type {NodeJS.Timeout|null}
|
|
87
|
+
*/
|
|
19
88
|
#cleanupTimer = null;
|
|
20
89
|
|
|
21
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Maximum number of allowed hits within the interval window.
|
|
92
|
+
*
|
|
93
|
+
* If `null`, hit count is not enforced.
|
|
94
|
+
*
|
|
95
|
+
* @type {number|null|undefined}
|
|
96
|
+
*/
|
|
22
97
|
#maxHits = null;
|
|
23
98
|
|
|
24
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* Sliding window duration in milliseconds.
|
|
101
|
+
*
|
|
102
|
+
* When defined, the rate limiter operates in **sliding window mode**:
|
|
103
|
+
* only hits that occurred within the last `interval` milliseconds
|
|
104
|
+
* are considered when evaluating limits.
|
|
105
|
+
*
|
|
106
|
+
* If `null`, time-based limiting is disabled and only `maxHits`
|
|
107
|
+
* (if defined) is used.
|
|
108
|
+
*
|
|
109
|
+
* @type {number|null|undefined}
|
|
110
|
+
*/
|
|
25
111
|
#interval = null;
|
|
26
112
|
|
|
27
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Interval (in milliseconds) at which cleanup runs automatically.
|
|
115
|
+
*
|
|
116
|
+
* If `null`, cleanup must be triggered manually.
|
|
117
|
+
*
|
|
118
|
+
* @type {number|null|undefined}
|
|
119
|
+
*/
|
|
28
120
|
#cleanupInterval = null;
|
|
29
121
|
|
|
30
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Maximum allowed inactivity time (in milliseconds)
|
|
124
|
+
* before a group is considered expired.
|
|
125
|
+
*
|
|
126
|
+
* @type {number|null|undefined}
|
|
127
|
+
*/
|
|
31
128
|
#maxIdle = null;
|
|
32
129
|
|
|
33
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* Stores hit timestamps per group.
|
|
132
|
+
*
|
|
133
|
+
* Key: groupId
|
|
134
|
+
* Value: array of timestamps (ms)
|
|
135
|
+
*
|
|
136
|
+
* @type {Map<string, number[]>}
|
|
137
|
+
*/
|
|
34
138
|
groupData = new Map(); // groupId -> timestamps[]
|
|
35
139
|
|
|
36
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Stores the timestamp of the most recent activity per group.
|
|
142
|
+
*
|
|
143
|
+
* Used for expiration and cleanup logic.
|
|
144
|
+
*
|
|
145
|
+
* @type {Map<string, number>}
|
|
146
|
+
*/
|
|
37
147
|
lastSeen = new Map(); // groupId -> timestamp
|
|
38
148
|
|
|
39
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Maps user IDs to their assigned group IDs.
|
|
151
|
+
*
|
|
152
|
+
* @type {Map<string, string>}
|
|
153
|
+
*/
|
|
40
154
|
userToGroup = new Map(); // userId -> groupId
|
|
41
155
|
|
|
42
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Flags whether an ID represents a true group or an implicit user group.
|
|
158
|
+
*
|
|
159
|
+
* `true` → explicit group
|
|
160
|
+
* `false` → implicit (user acting as group)
|
|
161
|
+
*
|
|
162
|
+
* @type {Map<string, boolean>}
|
|
163
|
+
*/
|
|
43
164
|
groupFlags = new Map(); // groupId -> boolean
|
|
44
165
|
|
|
45
166
|
/**
|
|
167
|
+
* Per-group TTL (time-to-live) overrides.
|
|
168
|
+
*
|
|
169
|
+
* If not defined for a group, `maxIdle` is used instead.
|
|
170
|
+
*
|
|
46
171
|
* @type {Map<string, number>}
|
|
47
|
-
* Stores TTL (in ms) for each groupId individually
|
|
48
172
|
*/
|
|
49
173
|
groupTTL = new Map();
|
|
50
174
|
|
|
51
175
|
/**
|
|
176
|
+
* Callback invoked when a group's memory limit is exceeded.
|
|
177
|
+
*
|
|
52
178
|
* @type {null|OnMemoryExceeded}
|
|
53
179
|
*/
|
|
54
180
|
#onMemoryExceeded = null;
|
|
55
181
|
|
|
56
182
|
/**
|
|
57
|
-
*
|
|
183
|
+
* Assign a callback to be notified when a group's stored
|
|
184
|
+
* hit history exceeds the configured memory limit.
|
|
185
|
+
*
|
|
58
186
|
* @param {OnMemoryExceeded} callback
|
|
59
187
|
*/
|
|
60
188
|
setOnMemoryExceeded(callback) {
|
|
@@ -63,13 +191,15 @@ class TinyRateLimiter {
|
|
|
63
191
|
}
|
|
64
192
|
|
|
65
193
|
/**
|
|
66
|
-
*
|
|
194
|
+
* Removes the memory-exceeded callback.
|
|
67
195
|
*/
|
|
68
196
|
clearOnMemoryExceeded() {
|
|
69
197
|
this.#onMemoryExceeded = null;
|
|
70
198
|
}
|
|
71
199
|
|
|
72
200
|
/**
|
|
201
|
+
* Callback invoked when a group expires and is removed.
|
|
202
|
+
*
|
|
73
203
|
* @type {null|OnGroupExpired}
|
|
74
204
|
*/
|
|
75
205
|
#onGroupExpired = null;
|
|
@@ -88,19 +218,28 @@ class TinyRateLimiter {
|
|
|
88
218
|
}
|
|
89
219
|
|
|
90
220
|
/**
|
|
91
|
-
*
|
|
221
|
+
* Removes the group-expiration callback.
|
|
92
222
|
*/
|
|
93
223
|
clearOnGroupExpired() {
|
|
94
224
|
this.#onGroupExpired = null;
|
|
95
225
|
}
|
|
96
226
|
|
|
97
227
|
/**
|
|
228
|
+
* Creates a new TinyRateLimiter instance.
|
|
229
|
+
*
|
|
230
|
+
* At least one of `maxHits` or `interval` must be provided.
|
|
231
|
+
*
|
|
98
232
|
* @param {Object} options
|
|
99
|
-
* @param {number|null} [options.maxMemory]
|
|
100
|
-
*
|
|
101
|
-
* @param {number} [options.
|
|
102
|
-
*
|
|
103
|
-
* @param {number} [options.
|
|
233
|
+
* @param {number|null} [options.maxMemory=100000]
|
|
234
|
+
* Maximum timestamps stored per group (memory cap).
|
|
235
|
+
* @param {number} [options.maxHits]
|
|
236
|
+
* Maximum number of allowed hits.
|
|
237
|
+
* @param {number} [options.interval]
|
|
238
|
+
* Sliding time window in milliseconds.
|
|
239
|
+
* @param {number} [options.cleanupInterval]
|
|
240
|
+
* Interval (ms) for automatic cleanup execution.
|
|
241
|
+
* @param {number} [options.maxIdle=300000]
|
|
242
|
+
* Maximum inactivity time (ms) before a group expires.
|
|
104
243
|
*/
|
|
105
244
|
constructor({ maxHits, interval, cleanupInterval, maxIdle = 300000, maxMemory = 100000 }) {
|
|
106
245
|
/** @param {number|undefined} val */
|
|
@@ -195,10 +334,14 @@ class TinyRateLimiter {
|
|
|
195
334
|
}
|
|
196
335
|
|
|
197
336
|
/**
|
|
198
|
-
*
|
|
337
|
+
* Assigns a user to a group.
|
|
338
|
+
*
|
|
339
|
+
* If the user already has recorded hits, those hits
|
|
340
|
+
* are merged into the target group.
|
|
341
|
+
*
|
|
199
342
|
* @param {string} userId
|
|
200
343
|
* @param {string} groupId
|
|
201
|
-
* @throws {Error} If
|
|
344
|
+
* @throws {Error} If the user belongs to another group
|
|
202
345
|
*/
|
|
203
346
|
assignToGroup(userId, groupId) {
|
|
204
347
|
const existingGroup = this.userToGroup.get(userId);
|
|
@@ -239,7 +382,11 @@ class TinyRateLimiter {
|
|
|
239
382
|
}
|
|
240
383
|
|
|
241
384
|
/**
|
|
242
|
-
*
|
|
385
|
+
* Resolves the effective group ID for a user.
|
|
386
|
+
*
|
|
387
|
+
* If the user is not explicitly assigned to a group,
|
|
388
|
+
* the user ID itself is treated as the group ID.
|
|
389
|
+
*
|
|
243
390
|
* @param {string} userId
|
|
244
391
|
* @returns {string}
|
|
245
392
|
*/
|
|
@@ -248,7 +395,25 @@ class TinyRateLimiter {
|
|
|
248
395
|
}
|
|
249
396
|
|
|
250
397
|
/**
|
|
251
|
-
*
|
|
398
|
+
* Registers a hit for a user and applies the sliding window logic.
|
|
399
|
+
*
|
|
400
|
+
* ⚠️ **Important usage notice**
|
|
401
|
+
* This method **must be called before** `isRateLimited(userId)`
|
|
402
|
+
* in order for rate limit checks to work correctly.
|
|
403
|
+
*
|
|
404
|
+
* ### Sliding window cleanup
|
|
405
|
+
* When `interval` is configured:
|
|
406
|
+
* - The current timestamp is added to the group's history.
|
|
407
|
+
* - All timestamps older than `now - interval` are immediately removed.
|
|
408
|
+
*
|
|
409
|
+
* This ensures that the stored history always represents
|
|
410
|
+
* the **current active window**.
|
|
411
|
+
*
|
|
412
|
+
* ### Important notes
|
|
413
|
+
* - Cleanup happens on every hit, not on a fixed schedule.
|
|
414
|
+
* - The window continuously moves forward in time.
|
|
415
|
+
* - Memory usage is naturally bounded by time, and optionally by `maxMemory`.
|
|
416
|
+
*
|
|
252
417
|
* @param {string} userId
|
|
253
418
|
*/
|
|
254
419
|
hit(userId) {
|
|
@@ -285,7 +450,22 @@ class TinyRateLimiter {
|
|
|
285
450
|
}
|
|
286
451
|
|
|
287
452
|
/**
|
|
288
|
-
*
|
|
453
|
+
* Checks whether a user is currently rate limited.
|
|
454
|
+
*
|
|
455
|
+
* ### Evaluation process
|
|
456
|
+
* When `interval` is defined:
|
|
457
|
+
* 1. The cutoff time is calculated as:
|
|
458
|
+
*
|
|
459
|
+
* `Date.now() - interval`
|
|
460
|
+
*
|
|
461
|
+
* 2. Only hits newer than this cutoff are counted.
|
|
462
|
+
* 3. If `maxHits` is defined:
|
|
463
|
+
* - The group is limited when the count exceeds `maxHits`.
|
|
464
|
+
* 4. If `maxHits` is not defined:
|
|
465
|
+
* - Any hit within the window causes a limited state.
|
|
466
|
+
*
|
|
467
|
+
* This guarantees consistent behavior regardless of when hits occur.
|
|
468
|
+
*
|
|
289
469
|
* @param {string} userId
|
|
290
470
|
* @returns {boolean}
|
|
291
471
|
*/
|
|
@@ -431,7 +611,11 @@ class TinyRateLimiter {
|
|
|
431
611
|
}
|
|
432
612
|
|
|
433
613
|
/**
|
|
434
|
-
*
|
|
614
|
+
* Returns the configured sliding window size in milliseconds.
|
|
615
|
+
*
|
|
616
|
+
* This value represents how far back in time hits are considered
|
|
617
|
+
* valid for rate limiting decisions.
|
|
618
|
+
*
|
|
435
619
|
* @returns {number}
|
|
436
620
|
*/
|
|
437
621
|
getInterval() {
|
|
@@ -1,23 +1,103 @@
|
|
|
1
1
|
export default TinyRateLimiter;
|
|
2
|
+
/**
|
|
3
|
+
* Callback triggered when a group's stored hit history exceeds
|
|
4
|
+
* the configured memory limit.
|
|
5
|
+
*
|
|
6
|
+
* This callback is purely informational and does not block execution.
|
|
7
|
+
*/
|
|
2
8
|
export type OnMemoryExceeded = (groupId: string) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Callback triggered when a group is considered expired and removed
|
|
11
|
+
* during the cleanup process.
|
|
12
|
+
*
|
|
13
|
+
* This usually happens when the group remains inactive longer than
|
|
14
|
+
* its configured TTL or the global maxIdle value.
|
|
15
|
+
*/
|
|
3
16
|
export type OnGroupExpired = (groupId: string) => void;
|
|
4
|
-
/** @typedef {(groupId: string) => void} OnMemoryExceeded */
|
|
5
|
-
/** @typedef {(groupId: string) => void} OnGroupExpired */
|
|
6
17
|
/**
|
|
7
|
-
*
|
|
18
|
+
* Callback triggered when a group's stored hit history exceeds
|
|
19
|
+
* the configured memory limit.
|
|
20
|
+
*
|
|
21
|
+
* This callback is purely informational and does not block execution.
|
|
22
|
+
*
|
|
23
|
+
* @typedef {(groupId: string) => void} OnMemoryExceeded
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Callback triggered when a group is considered expired and removed
|
|
27
|
+
* during the cleanup process.
|
|
28
|
+
*
|
|
29
|
+
* This usually happens when the group remains inactive longer than
|
|
30
|
+
* its configured TTL or the global maxIdle value.
|
|
31
|
+
*
|
|
32
|
+
* @typedef {(groupId: string) => void} OnGroupExpired
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* A lightweight and flexible rate limiter supporting both user-based
|
|
36
|
+
* and group-based throttling.
|
|
37
|
+
*
|
|
38
|
+
* ## Core Concepts
|
|
39
|
+
* - Every user belongs to a group.
|
|
40
|
+
* - If no explicit group is assigned, the user acts as their own group.
|
|
41
|
+
* - All users within the same group share the same hit history and limits.
|
|
42
|
+
*
|
|
43
|
+
* ## Supported Limiting Strategies
|
|
44
|
+
* - Max number of hits
|
|
45
|
+
* - Time-based sliding window
|
|
46
|
+
* - Combination of both
|
|
8
47
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
48
|
+
* ## Extra Features
|
|
49
|
+
* - Per-group TTL (time-to-live)
|
|
50
|
+
* - Automatic cleanup of inactive groups
|
|
51
|
+
* - Optional memory cap per group
|
|
52
|
+
* - Runtime metrics and statistics
|
|
53
|
+
*
|
|
54
|
+
* ## Interval Window (Sliding Window) Behavior
|
|
55
|
+
*
|
|
56
|
+
* This rate limiter uses a **sliding time window** strategy when `interval`
|
|
57
|
+
* is configured.
|
|
58
|
+
*
|
|
59
|
+
* ### How it works
|
|
60
|
+
* - Each hit is stored as a timestamp (Date.now()).
|
|
61
|
+
* - On every new hit and rate check, timestamps older than:
|
|
62
|
+
*
|
|
63
|
+
* `now - interval`
|
|
64
|
+
*
|
|
65
|
+
* are discarded.
|
|
66
|
+
*
|
|
67
|
+
* - Only hits that occurred within the last `interval` milliseconds
|
|
68
|
+
* are considered valid.
|
|
69
|
+
*
|
|
70
|
+
* ### Practical example
|
|
71
|
+
* If:
|
|
72
|
+
* - interval = 10_000 (10 seconds)
|
|
73
|
+
* - maxHits = 5
|
|
74
|
+
*
|
|
75
|
+
* Then:
|
|
76
|
+
* - Any group may perform **up to 5 hits in any rolling 10-second window**.
|
|
77
|
+
* - The window moves continuously with time.
|
|
78
|
+
*
|
|
79
|
+
* This avoids burst issues common in fixed-window rate limiters.
|
|
80
|
+
*
|
|
81
|
+
* This class is designed to be deterministic, predictable,
|
|
82
|
+
* and safe to use in long-running Node.js processes.
|
|
12
83
|
*/
|
|
13
84
|
declare class TinyRateLimiter {
|
|
14
85
|
/**
|
|
86
|
+
* Creates a new TinyRateLimiter instance.
|
|
87
|
+
*
|
|
88
|
+
* At least one of `maxHits` or `interval` must be provided.
|
|
89
|
+
*
|
|
15
90
|
* @param {Object} options
|
|
16
|
-
* @param {number|null} [options.maxMemory]
|
|
17
|
-
*
|
|
18
|
-
* @param {number} [options.
|
|
19
|
-
*
|
|
20
|
-
* @param {number} [options.
|
|
91
|
+
* @param {number|null} [options.maxMemory=100000]
|
|
92
|
+
* Maximum timestamps stored per group (memory cap).
|
|
93
|
+
* @param {number} [options.maxHits]
|
|
94
|
+
* Maximum number of allowed hits.
|
|
95
|
+
* @param {number} [options.interval]
|
|
96
|
+
* Sliding time window in milliseconds.
|
|
97
|
+
* @param {number} [options.cleanupInterval]
|
|
98
|
+
* Interval (ms) for automatic cleanup execution.
|
|
99
|
+
* @param {number} [options.maxIdle=300000]
|
|
100
|
+
* Maximum inactivity time (ms) before a group expires.
|
|
21
101
|
*/
|
|
22
102
|
constructor({ maxHits, interval, cleanupInterval, maxIdle, maxMemory }: {
|
|
23
103
|
maxMemory?: number | null | undefined;
|
|
@@ -26,26 +106,55 @@ declare class TinyRateLimiter {
|
|
|
26
106
|
cleanupInterval?: number | undefined;
|
|
27
107
|
maxIdle?: number | undefined;
|
|
28
108
|
});
|
|
29
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Stores hit timestamps per group.
|
|
111
|
+
*
|
|
112
|
+
* Key: groupId
|
|
113
|
+
* Value: array of timestamps (ms)
|
|
114
|
+
*
|
|
115
|
+
* @type {Map<string, number[]>}
|
|
116
|
+
*/
|
|
30
117
|
groupData: Map<string, number[]>;
|
|
31
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Stores the timestamp of the most recent activity per group.
|
|
120
|
+
*
|
|
121
|
+
* Used for expiration and cleanup logic.
|
|
122
|
+
*
|
|
123
|
+
* @type {Map<string, number>}
|
|
124
|
+
*/
|
|
32
125
|
lastSeen: Map<string, number>;
|
|
33
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Maps user IDs to their assigned group IDs.
|
|
128
|
+
*
|
|
129
|
+
* @type {Map<string, string>}
|
|
130
|
+
*/
|
|
34
131
|
userToGroup: Map<string, string>;
|
|
35
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Flags whether an ID represents a true group or an implicit user group.
|
|
134
|
+
*
|
|
135
|
+
* `true` → explicit group
|
|
136
|
+
* `false` → implicit (user acting as group)
|
|
137
|
+
*
|
|
138
|
+
* @type {Map<string, boolean>}
|
|
139
|
+
*/
|
|
36
140
|
groupFlags: Map<string, boolean>;
|
|
37
141
|
/**
|
|
142
|
+
* Per-group TTL (time-to-live) overrides.
|
|
143
|
+
*
|
|
144
|
+
* If not defined for a group, `maxIdle` is used instead.
|
|
145
|
+
*
|
|
38
146
|
* @type {Map<string, number>}
|
|
39
|
-
* Stores TTL (in ms) for each groupId individually
|
|
40
147
|
*/
|
|
41
148
|
groupTTL: Map<string, number>;
|
|
42
149
|
/**
|
|
43
|
-
*
|
|
150
|
+
* Assign a callback to be notified when a group's stored
|
|
151
|
+
* hit history exceeds the configured memory limit.
|
|
152
|
+
*
|
|
44
153
|
* @param {OnMemoryExceeded} callback
|
|
45
154
|
*/
|
|
46
155
|
setOnMemoryExceeded(callback: OnMemoryExceeded): void;
|
|
47
156
|
/**
|
|
48
|
-
*
|
|
157
|
+
* Removes the memory-exceeded callback.
|
|
49
158
|
*/
|
|
50
159
|
clearOnMemoryExceeded(): void;
|
|
51
160
|
/**
|
|
@@ -58,7 +167,7 @@ declare class TinyRateLimiter {
|
|
|
58
167
|
*/
|
|
59
168
|
setOnGroupExpired(callback: OnGroupExpired): void;
|
|
60
169
|
/**
|
|
61
|
-
*
|
|
170
|
+
* Removes the group-expiration callback.
|
|
62
171
|
*/
|
|
63
172
|
clearOnGroupExpired(): void;
|
|
64
173
|
/**
|
|
@@ -91,25 +200,66 @@ declare class TinyRateLimiter {
|
|
|
91
200
|
*/
|
|
92
201
|
deleteGroupTTL(groupId: string): void;
|
|
93
202
|
/**
|
|
94
|
-
*
|
|
203
|
+
* Assigns a user to a group.
|
|
204
|
+
*
|
|
205
|
+
* If the user already has recorded hits, those hits
|
|
206
|
+
* are merged into the target group.
|
|
207
|
+
*
|
|
95
208
|
* @param {string} userId
|
|
96
209
|
* @param {string} groupId
|
|
97
|
-
* @throws {Error} If
|
|
210
|
+
* @throws {Error} If the user belongs to another group
|
|
98
211
|
*/
|
|
99
212
|
assignToGroup(userId: string, groupId: string): void;
|
|
100
213
|
/**
|
|
101
|
-
*
|
|
214
|
+
* Resolves the effective group ID for a user.
|
|
215
|
+
*
|
|
216
|
+
* If the user is not explicitly assigned to a group,
|
|
217
|
+
* the user ID itself is treated as the group ID.
|
|
218
|
+
*
|
|
102
219
|
* @param {string} userId
|
|
103
220
|
* @returns {string}
|
|
104
221
|
*/
|
|
105
222
|
getGroupId(userId: string): string;
|
|
106
223
|
/**
|
|
107
|
-
*
|
|
224
|
+
* Registers a hit for a user and applies the sliding window logic.
|
|
225
|
+
*
|
|
226
|
+
* ⚠️ **Important usage notice**
|
|
227
|
+
* This method **must be called before** `isRateLimited(userId)`
|
|
228
|
+
* in order for rate limit checks to work correctly.
|
|
229
|
+
*
|
|
230
|
+
* ### Sliding window cleanup
|
|
231
|
+
* When `interval` is configured:
|
|
232
|
+
* - The current timestamp is added to the group's history.
|
|
233
|
+
* - All timestamps older than `now - interval` are immediately removed.
|
|
234
|
+
*
|
|
235
|
+
* This ensures that the stored history always represents
|
|
236
|
+
* the **current active window**.
|
|
237
|
+
*
|
|
238
|
+
* ### Important notes
|
|
239
|
+
* - Cleanup happens on every hit, not on a fixed schedule.
|
|
240
|
+
* - The window continuously moves forward in time.
|
|
241
|
+
* - Memory usage is naturally bounded by time, and optionally by `maxMemory`.
|
|
242
|
+
*
|
|
108
243
|
* @param {string} userId
|
|
109
244
|
*/
|
|
110
245
|
hit(userId: string): void;
|
|
111
246
|
/**
|
|
112
|
-
*
|
|
247
|
+
* Checks whether a user is currently rate limited.
|
|
248
|
+
*
|
|
249
|
+
* ### Evaluation process
|
|
250
|
+
* When `interval` is defined:
|
|
251
|
+
* 1. The cutoff time is calculated as:
|
|
252
|
+
*
|
|
253
|
+
* `Date.now() - interval`
|
|
254
|
+
*
|
|
255
|
+
* 2. Only hits newer than this cutoff are counted.
|
|
256
|
+
* 3. If `maxHits` is defined:
|
|
257
|
+
* - The group is limited when the count exceeds `maxHits`.
|
|
258
|
+
* 4. If `maxHits` is not defined:
|
|
259
|
+
* - Any hit within the window causes a limited state.
|
|
260
|
+
*
|
|
261
|
+
* This guarantees consistent behavior regardless of when hits occur.
|
|
262
|
+
*
|
|
113
263
|
* @param {string} userId
|
|
114
264
|
* @returns {boolean}
|
|
115
265
|
*/
|
|
@@ -168,7 +318,11 @@ declare class TinyRateLimiter {
|
|
|
168
318
|
*/
|
|
169
319
|
getAllUserMappings(): Record<string, string>;
|
|
170
320
|
/**
|
|
171
|
-
*
|
|
321
|
+
* Returns the configured sliding window size in milliseconds.
|
|
322
|
+
*
|
|
323
|
+
* This value represents how far back in time hits are considered
|
|
324
|
+
* valid for rate limiting decisions.
|
|
325
|
+
*
|
|
172
326
|
* @returns {number}
|
|
173
327
|
*/
|
|
174
328
|
getInterval(): number;
|