@commandkit/ratelimit 0.0.0-dev.20260317060555

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +801 -0
  3. package/dist/api.d.ts +79 -0
  4. package/dist/api.js +266 -0
  5. package/dist/augmentation.d.ts +11 -0
  6. package/dist/augmentation.js +8 -0
  7. package/dist/configure.d.ts +28 -0
  8. package/dist/configure.js +85 -0
  9. package/dist/constants.d.ts +17 -0
  10. package/dist/constants.js +21 -0
  11. package/dist/directive/use-ratelimit-directive.d.ts +22 -0
  12. package/dist/directive/use-ratelimit-directive.js +38 -0
  13. package/dist/directive/use-ratelimit.d.ts +14 -0
  14. package/dist/directive/use-ratelimit.js +169 -0
  15. package/dist/engine/RateLimitEngine.d.ts +48 -0
  16. package/dist/engine/RateLimitEngine.js +137 -0
  17. package/dist/engine/algorithms/fixed-window.d.ts +44 -0
  18. package/dist/engine/algorithms/fixed-window.js +198 -0
  19. package/dist/engine/algorithms/leaky-bucket.d.ts +48 -0
  20. package/dist/engine/algorithms/leaky-bucket.js +119 -0
  21. package/dist/engine/algorithms/sliding-window.d.ts +45 -0
  22. package/dist/engine/algorithms/sliding-window.js +127 -0
  23. package/dist/engine/algorithms/token-bucket.d.ts +47 -0
  24. package/dist/engine/algorithms/token-bucket.js +118 -0
  25. package/dist/engine/violations.d.ts +55 -0
  26. package/dist/engine/violations.js +106 -0
  27. package/dist/errors.d.ts +21 -0
  28. package/dist/errors.js +28 -0
  29. package/dist/index.d.ts +31 -0
  30. package/dist/index.js +53 -0
  31. package/dist/plugin.d.ts +140 -0
  32. package/dist/plugin.js +796 -0
  33. package/dist/providers/fallback.d.ts +7 -0
  34. package/dist/providers/fallback.js +11 -0
  35. package/dist/providers/memory.d.ts +6 -0
  36. package/dist/providers/memory.js +11 -0
  37. package/dist/providers/redis.d.ts +7 -0
  38. package/dist/providers/redis.js +11 -0
  39. package/dist/runtime.d.ts +45 -0
  40. package/dist/runtime.js +67 -0
  41. package/dist/storage/fallback.d.ts +180 -0
  42. package/dist/storage/fallback.js +261 -0
  43. package/dist/storage/memory.d.ts +146 -0
  44. package/dist/storage/memory.js +304 -0
  45. package/dist/storage/redis.d.ts +130 -0
  46. package/dist/storage/redis.js +243 -0
  47. package/dist/types.d.ts +296 -0
  48. package/dist/types.js +40 -0
  49. package/dist/utils/config.d.ts +34 -0
  50. package/dist/utils/config.js +105 -0
  51. package/dist/utils/keys.d.ts +102 -0
  52. package/dist/utils/keys.js +304 -0
  53. package/dist/utils/locking.d.ts +17 -0
  54. package/dist/utils/locking.js +60 -0
  55. package/dist/utils/time.d.ts +23 -0
  56. package/dist/utils/time.js +72 -0
  57. package/package.json +65 -0
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ /**
3
+ * Redis storage.
4
+ *
5
+ * Uses Lua scripts for atomic fixed/sliding window operations.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.RedisRateLimitStorage = void 0;
12
+ const ioredis_1 = __importDefault(require("ioredis"));
13
+ const FIXED_WINDOW_SCRIPT = /* lua */ `
14
+ local key = KEYS[1]
15
+ local window = tonumber(ARGV[1])
16
+ local count = redis.call('INCR', key)
17
+ local ttl = redis.call('PTTL', key)
18
+ if ttl < 0 then
19
+ redis.call('PEXPIRE', key, window)
20
+ ttl = window
21
+ end
22
+ return {count, ttl}
23
+ `;
24
+ const SLIDING_WINDOW_SCRIPT = /* lua */ `
25
+ local key = KEYS[1]
26
+ local limit = tonumber(ARGV[1])
27
+ local window = tonumber(ARGV[2])
28
+ local now = tonumber(ARGV[3])
29
+ local member = ARGV[4]
30
+
31
+ redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
32
+ local count = redis.call('ZCARD', key)
33
+
34
+ if count >= limit then
35
+ local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')
36
+ local resetAt = now + window
37
+ if oldest[2] then
38
+ resetAt = tonumber(oldest[2]) + window
39
+ end
40
+ return {0, count, resetAt}
41
+ end
42
+
43
+ redis.call('ZADD', key, now, member)
44
+ redis.call('PEXPIRE', key, window)
45
+ count = count + 1
46
+ local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')
47
+ local resetAt = now + window
48
+ if oldest[2] then
49
+ resetAt = tonumber(oldest[2]) + window
50
+ end
51
+ return {1, count, resetAt}
52
+ `;
53
+ /**
54
+ * Redis-backed storage with Lua scripts for atomic window operations.
55
+ *
56
+ * @implements RateLimitStorage
57
+ */
58
+ class RedisRateLimitStorage {
59
+ constructor(redis) {
60
+ this.redis = redis instanceof ioredis_1.default ? redis : new ioredis_1.default(redis ?? {});
61
+ }
62
+ /**
63
+ * Read a value from Redis and JSON-decode it.
64
+ *
65
+ * @param key - Storage key to read.
66
+ * @returns Parsed value or null when absent.
67
+ */
68
+ async get(key) {
69
+ const value = await this.redis.get(key);
70
+ if (value == null)
71
+ return null;
72
+ return JSON.parse(value);
73
+ }
74
+ /**
75
+ * Store a value in Redis with optional TTL.
76
+ *
77
+ * @param key - Storage key to write.
78
+ * @param value - Value to serialize and store.
79
+ * @param ttlMs - Optional TTL in milliseconds.
80
+ * @returns Resolves when the value is stored.
81
+ */
82
+ async set(key, value, ttlMs) {
83
+ const payload = JSON.stringify(value);
84
+ if (typeof ttlMs === 'number') {
85
+ await this.redis.set(key, payload, 'PX', ttlMs);
86
+ return;
87
+ }
88
+ await this.redis.set(key, payload);
89
+ }
90
+ /**
91
+ * Delete a key from Redis.
92
+ *
93
+ * @param key - Storage key to delete.
94
+ * @returns Resolves when the key is removed.
95
+ */
96
+ async delete(key) {
97
+ await this.redis.del(key);
98
+ }
99
+ /**
100
+ * Read the TTL for a key when present.
101
+ *
102
+ * @param key - Storage key to inspect.
103
+ * @returns Remaining TTL in ms or null when no TTL is set.
104
+ */
105
+ async ttl(key) {
106
+ const ttl = await this.redis.pttl(key);
107
+ if (ttl < 0)
108
+ return null;
109
+ return ttl;
110
+ }
111
+ /**
112
+ * Update the TTL for an existing key.
113
+ *
114
+ * @param key - Storage key to update.
115
+ * @param ttlMs - TTL in milliseconds.
116
+ * @returns Resolves after the TTL is updated.
117
+ */
118
+ async expire(key, ttlMs) {
119
+ await this.redis.pexpire(key, ttlMs);
120
+ }
121
+ /**
122
+ * Add a member to a sorted set with the given score.
123
+ *
124
+ * @param key - Sorted-set key.
125
+ * @param score - Score to associate with the member.
126
+ * @param member - Member identifier.
127
+ * @returns Resolves when the member is added.
128
+ */
129
+ async zAdd(key, score, member) {
130
+ await this.redis.zadd(key, score.toString(), member);
131
+ }
132
+ /**
133
+ * Remove sorted-set members with scores in the given range.
134
+ *
135
+ * @param key - Sorted-set key.
136
+ * @param min - Minimum score (inclusive).
137
+ * @param max - Maximum score (inclusive).
138
+ * @returns Resolves when the range is removed.
139
+ */
140
+ async zRemRangeByScore(key, min, max) {
141
+ await this.redis.zremrangebyscore(key, min.toString(), max.toString());
142
+ }
143
+ /**
144
+ * Count members in a sorted set.
145
+ *
146
+ * @param key - Sorted-set key.
147
+ * @returns Number of members in the set.
148
+ */
149
+ async zCard(key) {
150
+ return Number(await this.redis.zcard(key));
151
+ }
152
+ /**
153
+ * Read sorted-set members in a score range.
154
+ *
155
+ * @param key - Sorted-set key.
156
+ * @param min - Minimum score (inclusive).
157
+ * @param max - Maximum score (inclusive).
158
+ * @returns Ordered members in the score range.
159
+ */
160
+ async zRangeByScore(key, min, max) {
161
+ return this.redis.zrangebyscore(key, min.toString(), max.toString());
162
+ }
163
+ /**
164
+ * Atomically consume a fixed-window counter via Lua.
165
+ *
166
+ * @param key - Storage key to consume.
167
+ * @param _limit - Limit (unused by the script).
168
+ * @param windowMs - Window size in milliseconds.
169
+ * @param _nowMs - Current time (unused by the script).
170
+ * @returns Fixed-window consume result.
171
+ */
172
+ async consumeFixedWindow(key, _limit, windowMs, _nowMs) {
173
+ const result = (await this.redis.eval(FIXED_WINDOW_SCRIPT, 1, key, windowMs.toString()));
174
+ return {
175
+ count: Number(result[0]),
176
+ ttlMs: Number(result[1]),
177
+ };
178
+ }
179
+ /**
180
+ * Atomically consume a sliding-window log via Lua.
181
+ *
182
+ * @param key - Storage key to consume.
183
+ * @param limit - Request limit for the window.
184
+ * @param windowMs - Window size in milliseconds.
185
+ * @param nowMs - Current timestamp in milliseconds.
186
+ * @param member - Member identifier for this request.
187
+ * @returns Sliding-window consume result.
188
+ */
189
+ async consumeSlidingWindowLog(key, limit, windowMs, nowMs, member) {
190
+ const result = (await this.redis.eval(SLIDING_WINDOW_SCRIPT, 1, key, limit.toString(), windowMs.toString(), nowMs.toString(), member));
191
+ return {
192
+ allowed: Number(result[0]) === 1,
193
+ count: Number(result[1]),
194
+ resetAt: Number(result[2]),
195
+ };
196
+ }
197
+ /**
198
+ * Delete keys with the given prefix.
199
+ *
200
+ * @param prefix - Prefix to match.
201
+ * @returns Resolves after matching keys are deleted.
202
+ */
203
+ async deleteByPrefix(prefix) {
204
+ await this.deleteByPattern(`${prefix}*`);
205
+ }
206
+ /**
207
+ * Delete keys matching a glob pattern using SCAN to avoid blocking Redis.
208
+ *
209
+ * @param pattern - Glob pattern to match keys against.
210
+ * @returns Resolves after matching keys are deleted.
211
+ */
212
+ async deleteByPattern(pattern) {
213
+ let cursor = '0';
214
+ do {
215
+ const [nextCursor, keys] = (await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', '100'));
216
+ if (keys.length) {
217
+ await this.redis.del(...keys);
218
+ }
219
+ cursor = nextCursor;
220
+ } while (cursor !== '0');
221
+ }
222
+ /**
223
+ * List keys that match a prefix using SCAN.
224
+ *
225
+ * @param prefix - Prefix to match.
226
+ * @returns Matching keys.
227
+ */
228
+ async keysByPrefix(prefix) {
229
+ const pattern = `${prefix}*`;
230
+ const collected = new Set();
231
+ let cursor = '0';
232
+ do {
233
+ const [nextCursor, keys] = (await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', '100'));
234
+ for (const key of keys) {
235
+ collected.add(key);
236
+ }
237
+ cursor = nextCursor;
238
+ } while (cursor !== '0');
239
+ return Array.from(collected);
240
+ }
241
+ }
242
+ exports.RedisRateLimitStorage = RedisRateLimitStorage;
243
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVkaXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc3RvcmFnZS9yZWRpcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7R0FJRzs7Ozs7O0FBRUgsc0RBQW1EO0FBT25ELE1BQU0sbUJBQW1CLEdBQUcsU0FBUyxDQUFDOzs7Ozs7Ozs7O0NBVXJDLENBQUM7QUFFRixNQUFNLHFCQUFxQixHQUFHLFNBQVMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztDQTRCdkMsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxNQUFhLHFCQUFxQjtJQUdoQyxZQUFtQixLQUE0QjtRQUM3QyxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssWUFBWSxpQkFBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksaUJBQUssQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDLENBQUM7SUFDdkUsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsS0FBSyxDQUFDLEdBQUcsQ0FBYyxHQUFXO1FBQ2hDLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDeEMsSUFBSSxLQUFLLElBQUksSUFBSTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBQy9CLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQU0sQ0FBQztJQUNoQyxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxHQUFHLENBQWMsR0FBVyxFQUFFLEtBQVEsRUFBRSxLQUFjO1FBQzFELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdEMsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUM5QixNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLEtBQUss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
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Rate limit type contracts.
3
+ *
4
+ * Shared config and result shapes for the plugin, engine, storage, and helpers.
5
+ * Keeping them in one place reduces drift between runtime behavior and docs.
6
+ */
7
+ import type { Interaction, Message } from 'discord.js';
8
+ import type { Context } from 'commandkit';
9
+ import type { LoadedCommand } from 'commandkit';
10
+ /**
11
+ * Scopes used to build rate limit keys and apply per-scope limits.
12
+ */
13
+ export declare const RATE_LIMIT_SCOPES: readonly ["user", "guild", "channel", "global", "user-guild", "custom"];
14
+ /**
15
+ * Literal union of supported key scopes.
16
+ */
17
+ export type RateLimitScope = (typeof RATE_LIMIT_SCOPES)[number];
18
+ /**
19
+ * Scopes eligible for temporary exemptions stored in rate limit storage.
20
+ */
21
+ export declare const RATE_LIMIT_EXEMPTION_SCOPES: readonly ["user", "guild", "role", "channel", "category"];
22
+ /**
23
+ * Literal union of exemption scopes.
24
+ */
25
+ export type RateLimitExemptionScope = (typeof RATE_LIMIT_EXEMPTION_SCOPES)[number];
26
+ /**
27
+ * Algorithm identifiers used to select the limiter implementation.
28
+ */
29
+ export declare const RATE_LIMIT_ALGORITHMS: readonly ["fixed-window", "sliding-window", "token-bucket", "leaky-bucket"];
30
+ /**
31
+ * Literal union of algorithm identifiers.
32
+ */
33
+ export type RateLimitAlgorithmType = (typeof RATE_LIMIT_ALGORITHMS)[number];
34
+ /**
35
+ * Duration input accepted by configs: milliseconds or a duration string.
36
+ */
37
+ export type DurationLike = number | string;
38
+ /**
39
+ * Queue behavior for delayed retries after a limit is hit.
40
+ */
41
+ export interface RateLimitQueueOptions {
42
+ enabled?: boolean;
43
+ maxSize?: number;
44
+ timeout?: DurationLike;
45
+ deferInteraction?: boolean;
46
+ ephemeral?: boolean;
47
+ concurrency?: number;
48
+ }
49
+ /**
50
+ * Strategy for choosing among matching role-based overrides.
51
+ */
52
+ export type RateLimitRoleLimitStrategy = 'highest' | 'lowest' | 'first';
53
+ /**
54
+ * Result for a single limiter/window evaluation used for aggregation.
55
+ */
56
+ export interface RateLimitResult {
57
+ key: string;
58
+ scope: RateLimitScope;
59
+ algorithm: RateLimitAlgorithmType;
60
+ windowId?: string;
61
+ limited: boolean;
62
+ remaining: number;
63
+ resetAt: number;
64
+ retryAfter: number;
65
+ limit: number;
66
+ }
67
+ /**
68
+ * Contract for rate limit algorithms used by the engine.
69
+ */
70
+ export interface RateLimitAlgorithm {
71
+ readonly type: RateLimitAlgorithmType;
72
+ consume(key: string): Promise<RateLimitResult>;
73
+ reset(key: string): Promise<void>;
74
+ }
75
+ /**
76
+ * Storage result for fixed-window atomic consumes.
77
+ */
78
+ export interface FixedWindowConsumeResult {
79
+ count: number;
80
+ ttlMs: number;
81
+ }
82
+ /**
83
+ * Storage result for sliding-window log consumes.
84
+ */
85
+ export interface SlidingWindowConsumeResult {
86
+ allowed: boolean;
87
+ count: number;
88
+ resetAt: number;
89
+ }
90
+ /**
91
+ * Storage contract for rate limit state, with optional optimization hooks.
92
+ */
93
+ export interface RateLimitStorage {
94
+ get<T = unknown>(key: string): Promise<T | null>;
95
+ set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void>;
96
+ delete(key: string): Promise<void>;
97
+ incr?(key: string, ttlMs: number): Promise<FixedWindowConsumeResult>;
98
+ ttl?(key: string): Promise<number | null>;
99
+ expire?(key: string, ttlMs: number): Promise<void>;
100
+ zAdd?(key: string, score: number, member: string): Promise<void>;
101
+ zRemRangeByScore?(key: string, min: number, max: number): Promise<void>;
102
+ zCard?(key: string): Promise<number>;
103
+ zRangeByScore?(key: string, min: number, max: number): Promise<string[]>;
104
+ consumeFixedWindow?(key: string, limit: number, windowMs: number, nowMs: number): Promise<FixedWindowConsumeResult>;
105
+ consumeSlidingWindowLog?(key: string, limit: number, windowMs: number, nowMs: number, member: string): Promise<SlidingWindowConsumeResult>;
106
+ deleteByPrefix?(prefix: string): Promise<void>;
107
+ deleteByPattern?(pattern: string): Promise<void>;
108
+ keysByPrefix?(prefix: string): Promise<string[]>;
109
+ }
110
+ /**
111
+ * Storage configuration: direct instance or `{ driver }` wrapper for parity.
112
+ */
113
+ export type RateLimitStorageConfig = RateLimitStorage | {
114
+ driver: RateLimitStorage;
115
+ };
116
+ /**
117
+ * Escalation settings for repeated violations.
118
+ */
119
+ export interface ViolationOptions {
120
+ escalate?: boolean;
121
+ maxViolations?: number;
122
+ escalationMultiplier?: number;
123
+ resetAfter?: DurationLike;
124
+ }
125
+ /**
126
+ * Per-window overrides when a limiter defines multiple windows.
127
+ */
128
+ export interface RateLimitWindowConfig {
129
+ id?: string;
130
+ maxRequests?: number;
131
+ interval?: DurationLike;
132
+ algorithm?: RateLimitAlgorithmType;
133
+ burst?: number;
134
+ refillRate?: number;
135
+ leakRate?: number;
136
+ violations?: ViolationOptions;
137
+ }
138
+ /**
139
+ * Custom key builder for the `custom` scope.
140
+ */
141
+ export type RateLimitKeyResolver = (ctx: Context, command: LoadedCommand, source: Interaction | Message) => string;
142
+ /**
143
+ * Core limiter configuration used by plugin and directives.
144
+ */
145
+ export interface RateLimitLimiterConfig {
146
+ maxRequests?: number;
147
+ interval?: DurationLike;
148
+ scope?: RateLimitScope | RateLimitScope[];
149
+ algorithm?: RateLimitAlgorithmType;
150
+ burst?: number;
151
+ refillRate?: number;
152
+ leakRate?: number;
153
+ keyResolver?: RateLimitKeyResolver;
154
+ keyPrefix?: string;
155
+ storage?: RateLimitStorageConfig;
156
+ violations?: ViolationOptions;
157
+ queue?: RateLimitQueueOptions;
158
+ windows?: RateLimitWindowConfig[];
159
+ roleLimits?: Record<string, RateLimitLimiterConfig>;
160
+ roleLimitStrategy?: RateLimitRoleLimitStrategy;
161
+ }
162
+ /**
163
+ * Per-command override stored in CommandKit metadata.
164
+ */
165
+ export interface RateLimitCommandConfig extends RateLimitLimiterConfig {
166
+ limiter?: string;
167
+ }
168
+ /**
169
+ * Permanent allowlist rules for rate limiting.
170
+ */
171
+ export interface RateLimitBypassOptions {
172
+ userIds?: string[];
173
+ roleIds?: string[];
174
+ guildIds?: string[];
175
+ check?: (source: Interaction | Message) => boolean | Promise<boolean>;
176
+ }
177
+ /**
178
+ * Parameters for granting a temporary exemption.
179
+ */
180
+ export interface RateLimitExemptionGrantParams {
181
+ scope: RateLimitExemptionScope;
182
+ id: string;
183
+ duration: DurationLike;
184
+ keyPrefix?: string;
185
+ }
186
+ /**
187
+ * Parameters for revoking a temporary exemption.
188
+ */
189
+ export interface RateLimitExemptionRevokeParams {
190
+ scope: RateLimitExemptionScope;
191
+ id: string;
192
+ keyPrefix?: string;
193
+ }
194
+ /**
195
+ * Filters for listing temporary exemptions.
196
+ */
197
+ export interface RateLimitExemptionListParams {
198
+ scope?: RateLimitExemptionScope;
199
+ id?: string;
200
+ keyPrefix?: string;
201
+ limit?: number;
202
+ }
203
+ /**
204
+ * Listed exemption entry with key and expiry info.
205
+ */
206
+ export interface RateLimitExemptionInfo {
207
+ key: string;
208
+ scope: RateLimitExemptionScope;
209
+ id: string;
210
+ expiresInMs: number | null;
211
+ }
212
+ /**
213
+ * Hook payload for rate limit lifecycle callbacks.
214
+ */
215
+ export interface RateLimitHookContext {
216
+ key: string;
217
+ result: RateLimitResult;
218
+ source: Interaction | Message;
219
+ }
220
+ /**
221
+ * Optional lifecycle hooks used by the plugin to surface rate limit events.
222
+ */
223
+ export interface RateLimitHooks {
224
+ onRateLimited?: (info: RateLimitHookContext) => void | Promise<void>;
225
+ onAllowed?: (info: RateLimitHookContext) => void | Promise<void>;
226
+ onReset?: (key: string) => void | Promise<void>;
227
+ onViolation?: (key: string, count: number) => void | Promise<void>;
228
+ onStorageError?: (error: unknown, fallbackUsed: boolean) => void | Promise<void>;
229
+ }
230
+ /**
231
+ * Override for responding when a command is rate-limited.
232
+ */
233
+ export type RateLimitResponseHandler = (ctx: Context, info: RateLimitStoreValue) => Promise<void> | void;
234
+ /**
235
+ * Runtime plugin options consumed by RateLimitPlugin.
236
+ * Configure these via configureRatelimit().
237
+ */
238
+ export interface RateLimitPluginOptions {
239
+ defaultLimiter?: RateLimitLimiterConfig;
240
+ limiters?: Record<string, RateLimitLimiterConfig>;
241
+ storage?: RateLimitStorageConfig;
242
+ keyPrefix?: string;
243
+ keyResolver?: RateLimitKeyResolver;
244
+ bypass?: RateLimitBypassOptions;
245
+ hooks?: RateLimitHooks;
246
+ onRateLimited?: RateLimitResponseHandler;
247
+ queue?: RateLimitQueueOptions;
248
+ roleLimits?: Record<string, RateLimitLimiterConfig>;
249
+ roleLimitStrategy?: RateLimitRoleLimitStrategy;
250
+ /**
251
+ * Whether to initialize the default in-memory storage if no storage is configured.
252
+ *
253
+ * @default true
254
+ */
255
+ initializeDefaultStorage?: boolean;
256
+ /**
257
+ * Alias for initializeDefaultStorage, aligned with other packages.
258
+ *
259
+ * @default true
260
+ */
261
+ initializeDefaultDriver?: boolean;
262
+ }
263
+ /**
264
+ * Aggregate results stored on the environment store for downstream handlers.
265
+ */
266
+ export interface RateLimitStoreValue {
267
+ limited: boolean;
268
+ remaining: number;
269
+ resetAt: number;
270
+ retryAfter: number;
271
+ results: RateLimitResult[];
272
+ }
273
+ /**
274
+ * Limiter configuration after defaults are applied.
275
+ */
276
+ export interface ResolvedLimiterConfig {
277
+ maxRequests: number;
278
+ intervalMs: number;
279
+ algorithm: RateLimitAlgorithmType;
280
+ scope: RateLimitScope;
281
+ burst: number;
282
+ refillRate: number;
283
+ leakRate: number;
284
+ violations?: ViolationOptions;
285
+ windowId?: string;
286
+ }
287
+ /**
288
+ * Active runtime context shared with APIs and directives.
289
+ */
290
+ export interface RateLimitRuntimeContext {
291
+ storage: RateLimitStorage;
292
+ keyPrefix?: string;
293
+ defaultLimiter: RateLimitLimiterConfig;
294
+ limiters?: Record<string, RateLimitLimiterConfig>;
295
+ hooks?: RateLimitHooks;
296
+ }
package/dist/types.js ADDED
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * Rate limit type contracts.
4
+ *
5
+ * Shared config and result shapes for the plugin, engine, storage, and helpers.
6
+ * Keeping them in one place reduces drift between runtime behavior and docs.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.RATE_LIMIT_ALGORITHMS = exports.RATE_LIMIT_EXEMPTION_SCOPES = exports.RATE_LIMIT_SCOPES = void 0;
10
+ /**
11
+ * Scopes used to build rate limit keys and apply per-scope limits.
12
+ */
13
+ exports.RATE_LIMIT_SCOPES = [
14
+ 'user',
15
+ 'guild',
16
+ 'channel',
17
+ 'global',
18
+ 'user-guild',
19
+ 'custom',
20
+ ];
21
+ /**
22
+ * Scopes eligible for temporary exemptions stored in rate limit storage.
23
+ */
24
+ exports.RATE_LIMIT_EXEMPTION_SCOPES = [
25
+ 'user',
26
+ 'guild',
27
+ 'role',
28
+ 'channel',
29
+ 'category',
30
+ ];
31
+ /**
32
+ * Algorithm identifiers used to select the limiter implementation.
33
+ */
34
+ exports.RATE_LIMIT_ALGORITHMS = [
35
+ 'fixed-window',
36
+ 'sliding-window',
37
+ 'token-bucket',
38
+ 'leaky-bucket',
39
+ ];
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7OztHQUtHOzs7QUFNSDs7R0FFRztBQUNVLFFBQUEsaUJBQWlCLEdBQUc7SUFDL0IsTUFBTTtJQUNOLE9BQU87SUFDUCxTQUFTO0lBQ1QsUUFBUTtJQUNSLFlBQVk7SUFDWixRQUFRO0NBQ0EsQ0FBQztBQU9YOztHQUVHO0FBQ1UsUUFBQSwyQkFBMkIsR0FBRztJQUN6QyxNQUFNO0lBQ04sT0FBTztJQUNQLE1BQU07SUFDTixTQUFTO0lBQ1QsVUFBVTtDQUNGLENBQUM7QUFRWDs7R0FFRztBQUNVLFFBQUEscUJBQXFCLEdBQUc7SUFDbkMsY0FBYztJQUNkLGdCQUFnQjtJQUNoQixjQUFjO0lBQ2QsY0FBYztDQUNOLENBQUMifQ==
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Limiter config resolution.
3
+ *
4
+ * Applies defaults and merges overrides into concrete limiter settings
5
+ * used by the engine and plugin.
6
+ */
7
+ import type { RateLimitLimiterConfig, RateLimitScope, ResolvedLimiterConfig } from '../types';
8
+ /**
9
+ * Default limiter used when no explicit configuration is provided.
10
+ */
11
+ export declare const DEFAULT_LIMITER: RateLimitLimiterConfig;
12
+ /**
13
+ * Merge limiter configs; later values override earlier ones for layering.
14
+ *
15
+ * @param configs - Limiter configs ordered from lowest to highest priority.
16
+ * @returns Merged limiter config with later overrides applied.
17
+ */
18
+ export declare function mergeLimiterConfigs(...configs: Array<RateLimitLimiterConfig | undefined>): RateLimitLimiterConfig;
19
+ /**
20
+ * Resolve a limiter config for a single scope with defaults applied.
21
+ *
22
+ * @param config - Base limiter configuration.
23
+ * @param scope - Scope to resolve for the limiter.
24
+ * @returns Resolved limiter config with defaults and derived values.
25
+ */
26
+ export declare function resolveLimiterConfig(config: RateLimitLimiterConfig, scope: RateLimitScope): ResolvedLimiterConfig;
27
+ /**
28
+ * Resolve limiter configs for a scope across all configured windows.
29
+ *
30
+ * @param config - Base limiter configuration that may include windows.
31
+ * @param scope - Scope to resolve for the limiter.
32
+ * @returns Resolved limiter configs for each window (or a single config).
33
+ */
34
+ export declare function resolveLimiterConfigs(config: RateLimitLimiterConfig, scope: RateLimitScope): ResolvedLimiterConfig[];