servcraft 0.1.0 → 0.1.3

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 (217) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/LICENSE +21 -0
  9. package/README.md +1102 -1
  10. package/dist/cli/index.cjs +2026 -2168
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +2026 -2168
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/index.cjs +595 -616
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +114 -52
  17. package/dist/index.d.ts +114 -52
  18. package/dist/index.js +595 -616
  19. package/dist/index.js.map +1 -1
  20. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  21. package/docs/DATABASE_MULTI_ORM.md +399 -0
  22. package/docs/PHASE1_BREAKDOWN.md +346 -0
  23. package/docs/PROGRESS.md +550 -0
  24. package/docs/modules/ANALYTICS.md +226 -0
  25. package/docs/modules/API-VERSIONING.md +252 -0
  26. package/docs/modules/AUDIT.md +192 -0
  27. package/docs/modules/AUTH.md +431 -0
  28. package/docs/modules/CACHE.md +346 -0
  29. package/docs/modules/EMAIL.md +254 -0
  30. package/docs/modules/FEATURE-FLAG.md +291 -0
  31. package/docs/modules/I18N.md +294 -0
  32. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  33. package/docs/modules/MFA.md +266 -0
  34. package/docs/modules/NOTIFICATION.md +311 -0
  35. package/docs/modules/OAUTH.md +237 -0
  36. package/docs/modules/PAYMENT.md +804 -0
  37. package/docs/modules/QUEUE.md +540 -0
  38. package/docs/modules/RATE-LIMIT.md +339 -0
  39. package/docs/modules/SEARCH.md +288 -0
  40. package/docs/modules/SECURITY.md +327 -0
  41. package/docs/modules/SESSION.md +382 -0
  42. package/docs/modules/SWAGGER.md +305 -0
  43. package/docs/modules/UPLOAD.md +296 -0
  44. package/docs/modules/USER.md +505 -0
  45. package/docs/modules/VALIDATION.md +294 -0
  46. package/docs/modules/WEBHOOK.md +270 -0
  47. package/docs/modules/WEBSOCKET.md +691 -0
  48. package/package.json +53 -38
  49. package/prisma/schema.prisma +395 -1
  50. package/src/cli/commands/add-module.ts +520 -87
  51. package/src/cli/commands/db.ts +3 -4
  52. package/src/cli/commands/docs.ts +256 -6
  53. package/src/cli/commands/generate.ts +12 -19
  54. package/src/cli/commands/init.ts +384 -214
  55. package/src/cli/index.ts +0 -4
  56. package/src/cli/templates/repository.ts +6 -1
  57. package/src/cli/templates/routes.ts +6 -21
  58. package/src/cli/utils/docs-generator.ts +6 -7
  59. package/src/cli/utils/env-manager.ts +717 -0
  60. package/src/cli/utils/field-parser.ts +16 -7
  61. package/src/cli/utils/interactive-prompt.ts +223 -0
  62. package/src/cli/utils/template-manager.ts +346 -0
  63. package/src/config/database.config.ts +183 -0
  64. package/src/config/env.ts +0 -10
  65. package/src/config/index.ts +0 -14
  66. package/src/core/server.ts +1 -1
  67. package/src/database/adapters/mongoose.adapter.ts +132 -0
  68. package/src/database/adapters/prisma.adapter.ts +118 -0
  69. package/src/database/connection.ts +190 -0
  70. package/src/database/interfaces/database.interface.ts +85 -0
  71. package/src/database/interfaces/index.ts +7 -0
  72. package/src/database/interfaces/repository.interface.ts +129 -0
  73. package/src/database/models/mongoose/index.ts +7 -0
  74. package/src/database/models/mongoose/payment.schema.ts +347 -0
  75. package/src/database/models/mongoose/user.schema.ts +154 -0
  76. package/src/database/prisma.ts +1 -4
  77. package/src/database/redis.ts +101 -0
  78. package/src/database/repositories/mongoose/index.ts +7 -0
  79. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  80. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  81. package/src/database/seed.ts +6 -1
  82. package/src/index.ts +9 -20
  83. package/src/middleware/security.ts +2 -6
  84. package/src/modules/analytics/analytics.routes.ts +80 -0
  85. package/src/modules/analytics/analytics.service.ts +364 -0
  86. package/src/modules/analytics/index.ts +18 -0
  87. package/src/modules/analytics/types.ts +180 -0
  88. package/src/modules/api-versioning/index.ts +15 -0
  89. package/src/modules/api-versioning/types.ts +86 -0
  90. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  91. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  92. package/src/modules/api-versioning/versioning.service.ts +189 -0
  93. package/src/modules/audit/audit.repository.ts +206 -0
  94. package/src/modules/audit/audit.service.ts +27 -59
  95. package/src/modules/auth/auth.controller.ts +2 -2
  96. package/src/modules/auth/auth.middleware.ts +3 -9
  97. package/src/modules/auth/auth.routes.ts +10 -107
  98. package/src/modules/auth/auth.service.ts +126 -23
  99. package/src/modules/auth/index.ts +3 -4
  100. package/src/modules/cache/cache.service.ts +367 -0
  101. package/src/modules/cache/index.ts +10 -0
  102. package/src/modules/cache/types.ts +44 -0
  103. package/src/modules/email/email.service.ts +3 -10
  104. package/src/modules/email/templates.ts +2 -8
  105. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  106. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  107. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  108. package/src/modules/feature-flag/index.ts +20 -0
  109. package/src/modules/feature-flag/types.ts +192 -0
  110. package/src/modules/i18n/i18n.middleware.ts +186 -0
  111. package/src/modules/i18n/i18n.routes.ts +191 -0
  112. package/src/modules/i18n/i18n.service.ts +456 -0
  113. package/src/modules/i18n/index.ts +18 -0
  114. package/src/modules/i18n/types.ts +118 -0
  115. package/src/modules/media-processing/index.ts +17 -0
  116. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  117. package/src/modules/media-processing/media-processing.service.ts +245 -0
  118. package/src/modules/media-processing/types.ts +156 -0
  119. package/src/modules/mfa/index.ts +20 -0
  120. package/src/modules/mfa/mfa.repository.ts +206 -0
  121. package/src/modules/mfa/mfa.routes.ts +595 -0
  122. package/src/modules/mfa/mfa.service.ts +572 -0
  123. package/src/modules/mfa/totp.ts +150 -0
  124. package/src/modules/mfa/types.ts +57 -0
  125. package/src/modules/notification/index.ts +20 -0
  126. package/src/modules/notification/notification.repository.ts +356 -0
  127. package/src/modules/notification/notification.service.ts +483 -0
  128. package/src/modules/notification/types.ts +119 -0
  129. package/src/modules/oauth/index.ts +20 -0
  130. package/src/modules/oauth/oauth.repository.ts +219 -0
  131. package/src/modules/oauth/oauth.routes.ts +446 -0
  132. package/src/modules/oauth/oauth.service.ts +293 -0
  133. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  134. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  135. package/src/modules/oauth/providers/github.provider.ts +248 -0
  136. package/src/modules/oauth/providers/google.provider.ts +189 -0
  137. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  138. package/src/modules/oauth/types.ts +94 -0
  139. package/src/modules/payment/index.ts +19 -0
  140. package/src/modules/payment/payment.repository.ts +733 -0
  141. package/src/modules/payment/payment.routes.ts +390 -0
  142. package/src/modules/payment/payment.service.ts +354 -0
  143. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  144. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  145. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  146. package/src/modules/payment/types.ts +140 -0
  147. package/src/modules/queue/cron.ts +438 -0
  148. package/src/modules/queue/index.ts +87 -0
  149. package/src/modules/queue/queue.routes.ts +600 -0
  150. package/src/modules/queue/queue.service.ts +842 -0
  151. package/src/modules/queue/types.ts +222 -0
  152. package/src/modules/queue/workers.ts +366 -0
  153. package/src/modules/rate-limit/index.ts +59 -0
  154. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  155. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  156. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  157. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  158. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  159. package/src/modules/rate-limit/types.ts +153 -0
  160. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  161. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  162. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  163. package/src/modules/search/index.ts +21 -0
  164. package/src/modules/search/search.service.ts +234 -0
  165. package/src/modules/search/types.ts +214 -0
  166. package/src/modules/security/index.ts +40 -0
  167. package/src/modules/security/sanitize.ts +223 -0
  168. package/src/modules/security/security-audit.service.ts +388 -0
  169. package/src/modules/security/security.middleware.ts +398 -0
  170. package/src/modules/session/index.ts +3 -0
  171. package/src/modules/session/session.repository.ts +159 -0
  172. package/src/modules/session/session.service.ts +340 -0
  173. package/src/modules/session/types.ts +38 -0
  174. package/src/modules/swagger/index.ts +7 -1
  175. package/src/modules/swagger/schema-builder.ts +16 -4
  176. package/src/modules/swagger/swagger.service.ts +9 -10
  177. package/src/modules/swagger/types.ts +0 -2
  178. package/src/modules/upload/index.ts +14 -0
  179. package/src/modules/upload/types.ts +83 -0
  180. package/src/modules/upload/upload.repository.ts +199 -0
  181. package/src/modules/upload/upload.routes.ts +311 -0
  182. package/src/modules/upload/upload.service.ts +448 -0
  183. package/src/modules/user/index.ts +3 -3
  184. package/src/modules/user/user.controller.ts +15 -9
  185. package/src/modules/user/user.repository.ts +237 -113
  186. package/src/modules/user/user.routes.ts +39 -164
  187. package/src/modules/user/user.service.ts +4 -3
  188. package/src/modules/validation/validator.ts +12 -17
  189. package/src/modules/webhook/index.ts +91 -0
  190. package/src/modules/webhook/retry.ts +196 -0
  191. package/src/modules/webhook/signature.ts +135 -0
  192. package/src/modules/webhook/types.ts +181 -0
  193. package/src/modules/webhook/webhook.repository.ts +358 -0
  194. package/src/modules/webhook/webhook.routes.ts +442 -0
  195. package/src/modules/webhook/webhook.service.ts +457 -0
  196. package/src/modules/websocket/features.ts +504 -0
  197. package/src/modules/websocket/index.ts +106 -0
  198. package/src/modules/websocket/middlewares.ts +298 -0
  199. package/src/modules/websocket/types.ts +181 -0
  200. package/src/modules/websocket/websocket.service.ts +692 -0
  201. package/src/utils/errors.ts +7 -0
  202. package/src/utils/pagination.ts +4 -1
  203. package/tests/helpers/db-check.ts +79 -0
  204. package/tests/integration/auth-redis.test.ts +94 -0
  205. package/tests/integration/cache-redis.test.ts +387 -0
  206. package/tests/integration/mongoose-repositories.test.ts +410 -0
  207. package/tests/integration/payment-prisma.test.ts +637 -0
  208. package/tests/integration/queue-bullmq.test.ts +417 -0
  209. package/tests/integration/user-prisma.test.ts +441 -0
  210. package/tests/integration/websocket-socketio.test.ts +552 -0
  211. package/tests/setup.ts +11 -9
  212. package/vitest.config.ts +3 -8
  213. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  216. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  217. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
@@ -0,0 +1,348 @@
1
+ import type {
2
+ RateLimitConfig,
3
+ RateLimitStore,
4
+ RateLimitResult,
5
+ RateLimitInfo,
6
+ RateLimitRequest,
7
+ } from './types.js';
8
+ import { MemoryStore } from './stores/memory.store.js';
9
+
10
+ /**
11
+ * Rate Limiting Service
12
+ * Supports multiple algorithms: fixed-window, sliding-window, token-bucket
13
+ */
14
+ export class RateLimitService {
15
+ private config: Required<RateLimitConfig>;
16
+ private store: RateLimitStore;
17
+
18
+ constructor(config: RateLimitConfig, store?: RateLimitStore) {
19
+ const defaultStore = new MemoryStore();
20
+ this.config = {
21
+ max: config.max,
22
+ windowMs: config.windowMs,
23
+ algorithm: config.algorithm || 'sliding-window',
24
+ whitelist: config.whitelist || [],
25
+ blacklist: config.blacklist || [],
26
+ keyGenerator: config.keyGenerator || ((req: RateLimitRequest): string => req.ip),
27
+ skip: config.skip || ((): boolean => false),
28
+ onLimitReached: config.onLimitReached || ((): void => {}),
29
+ store: (config.store || defaultStore) as RateLimitStore,
30
+ headers: config.headers !== false,
31
+ message: config.message || 'Too many requests',
32
+ statusCode: config.statusCode || 429,
33
+ customLimits: config.customLimits || {},
34
+ };
35
+
36
+ this.store = (store || defaultStore) as RateLimitStore;
37
+ }
38
+
39
+ /**
40
+ * Check if a request should be rate limited
41
+ */
42
+ async check(
43
+ key: string,
44
+ options?: { max?: number; windowMs?: number }
45
+ ): Promise<RateLimitResult> {
46
+ const max = options?.max || this.config.max;
47
+ const windowMs = options?.windowMs || this.config.windowMs;
48
+
49
+ // Check whitelist
50
+ if (this.config.whitelist.includes(key)) {
51
+ return {
52
+ allowed: true,
53
+ limit: max,
54
+ remaining: max,
55
+ resetAt: Date.now() + windowMs,
56
+ };
57
+ }
58
+
59
+ // Check blacklist
60
+ if (this.config.blacklist.includes(key)) {
61
+ return {
62
+ allowed: false,
63
+ limit: max,
64
+ remaining: 0,
65
+ resetAt: Date.now() + windowMs,
66
+ };
67
+ }
68
+
69
+ // Apply rate limiting algorithm
70
+ switch (this.config.algorithm) {
71
+ case 'fixed-window':
72
+ return await this.fixedWindow(key, max, windowMs);
73
+ case 'sliding-window':
74
+ return await this.slidingWindow(key, max, windowMs);
75
+ case 'token-bucket':
76
+ return await this.tokenBucket(key, max, windowMs);
77
+ default:
78
+ return await this.slidingWindow(key, max, windowMs);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Fixed Window algorithm
84
+ * Simple but can allow bursts at window boundaries
85
+ */
86
+ private async fixedWindow(key: string, max: number, windowMs: number): Promise<RateLimitResult> {
87
+ let entry = await this.store.get(key);
88
+ const now = Date.now();
89
+
90
+ // Create new window if none exists or if expired
91
+ if (!entry || !entry.resetAt || entry.resetAt <= now) {
92
+ entry = {
93
+ count: 1,
94
+ startTime: now,
95
+ resetAt: now + windowMs,
96
+ firstRequest: now,
97
+ lastRequest: now,
98
+ };
99
+ await this.store.set(key, entry);
100
+
101
+ return {
102
+ allowed: true,
103
+ limit: max,
104
+ remaining: max - 1,
105
+ resetAt: entry.resetAt ?? now + windowMs,
106
+ };
107
+ }
108
+
109
+ // Increment count
110
+ entry.count++;
111
+ entry.lastRequest = now;
112
+ await this.store.set(key, entry);
113
+
114
+ const allowed = entry.count <= max;
115
+ const resetAt = entry.resetAt ?? now + windowMs;
116
+
117
+ return {
118
+ allowed,
119
+ limit: max,
120
+ remaining: Math.max(0, max - entry.count),
121
+ resetAt,
122
+ retryAfter: allowed ? undefined : Math.ceil((resetAt - now) / 1000),
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Sliding Window algorithm
128
+ * More accurate, prevents bursts at boundaries
129
+ */
130
+ private async slidingWindow(
131
+ key: string,
132
+ max: number,
133
+ windowMs: number
134
+ ): Promise<RateLimitResult> {
135
+ let entry = await this.store.get(key);
136
+ const now = Date.now();
137
+
138
+ if (!entry) {
139
+ entry = {
140
+ count: 1,
141
+ startTime: now,
142
+ resetAt: now + windowMs,
143
+ firstRequest: now,
144
+ lastRequest: now,
145
+ };
146
+ await this.store.set(key, entry);
147
+
148
+ return {
149
+ allowed: true,
150
+ limit: max,
151
+ remaining: max - 1,
152
+ resetAt: entry.resetAt ?? now + windowMs,
153
+ };
154
+ }
155
+
156
+ // Calculate sliding window
157
+ const firstRequest = entry.firstRequest ?? entry.startTime;
158
+ const timeInWindow = now - firstRequest;
159
+ const windowProgress = timeInWindow / windowMs;
160
+
161
+ // If we're past the window, reset
162
+ if (windowProgress >= 1) {
163
+ entry = {
164
+ count: 1,
165
+ startTime: now,
166
+ resetAt: now + windowMs,
167
+ firstRequest: now,
168
+ lastRequest: now,
169
+ };
170
+ await this.store.set(key, entry);
171
+
172
+ return {
173
+ allowed: true,
174
+ limit: max,
175
+ remaining: max - 1,
176
+ resetAt: entry.resetAt ?? now + windowMs,
177
+ };
178
+ }
179
+
180
+ // Sliding window count
181
+ const weightedCount = entry.count * (1 - windowProgress);
182
+ const currentCount = Math.ceil(weightedCount) + 1;
183
+
184
+ entry.count = currentCount;
185
+ entry.lastRequest = now;
186
+ await this.store.set(key, entry);
187
+
188
+ const allowed = currentCount <= max;
189
+ const resetAt = entry.resetAt ?? now + windowMs;
190
+
191
+ return {
192
+ allowed,
193
+ limit: max,
194
+ remaining: Math.max(0, max - currentCount),
195
+ resetAt,
196
+ retryAfter: allowed ? undefined : Math.ceil((resetAt - now) / 1000),
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Token Bucket algorithm
202
+ * Allows small bursts but enforces average rate
203
+ */
204
+ private async tokenBucket(key: string, max: number, windowMs: number): Promise<RateLimitResult> {
205
+ let entry = await this.store.get(key);
206
+ const now = Date.now();
207
+
208
+ if (!entry) {
209
+ entry = {
210
+ count: max - 1, // Start with full bucket minus 1
211
+ startTime: now,
212
+ resetAt: now + windowMs,
213
+ firstRequest: now,
214
+ lastRequest: now,
215
+ };
216
+ await this.store.set(key, entry);
217
+
218
+ return {
219
+ allowed: true,
220
+ limit: max,
221
+ remaining: max - 1,
222
+ resetAt: entry.resetAt ?? now + windowMs,
223
+ };
224
+ }
225
+
226
+ // Refill tokens based on time passed
227
+ const lastRequest = entry.lastRequest ?? entry.startTime;
228
+ const timePassed = now - lastRequest;
229
+ const refillRate = max / windowMs;
230
+ const tokensToAdd = Math.floor(timePassed * refillRate);
231
+ const tokens = Math.min(max, entry.count + tokensToAdd);
232
+
233
+ // Try to consume a token
234
+ if (tokens >= 1) {
235
+ entry.count = tokens - 1;
236
+ entry.lastRequest = now;
237
+ entry.resetAt = now + windowMs;
238
+ await this.store.set(key, entry);
239
+
240
+ return {
241
+ allowed: true,
242
+ limit: max,
243
+ remaining: entry.count,
244
+ resetAt: entry.resetAt,
245
+ };
246
+ }
247
+
248
+ // Not enough tokens
249
+ const timeUntilToken = Math.ceil((1 - tokens) / refillRate / 1000);
250
+ const resetAt = entry.resetAt ?? now + windowMs;
251
+
252
+ return {
253
+ allowed: false,
254
+ limit: max,
255
+ remaining: 0,
256
+ resetAt,
257
+ retryAfter: timeUntilToken,
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Reset rate limit for a specific key
263
+ */
264
+ async reset(key: string): Promise<void> {
265
+ await this.store.reset(key);
266
+ }
267
+
268
+ /**
269
+ * Get current rate limit info for a key
270
+ */
271
+ async getInfo(key: string): Promise<RateLimitInfo | null> {
272
+ const entry = await this.store.get(key);
273
+ if (!entry) return null;
274
+
275
+ const max = this.config.max;
276
+ const remaining = Math.max(0, max - entry.count);
277
+
278
+ return {
279
+ limit: max,
280
+ remaining,
281
+ resetAt: entry.resetAt,
282
+ count: entry.count,
283
+ firstRequest: entry.firstRequest,
284
+ lastRequest: entry.lastRequest,
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Add IP to whitelist
290
+ */
291
+ addToWhitelist(ip: string): void {
292
+ if (!this.config.whitelist.includes(ip)) {
293
+ this.config.whitelist.push(ip);
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Remove IP from whitelist
299
+ */
300
+ removeFromWhitelist(ip: string): void {
301
+ const index = this.config.whitelist.indexOf(ip);
302
+ if (index > -1) {
303
+ this.config.whitelist.splice(index, 1);
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Add IP to blacklist
309
+ */
310
+ addToBlacklist(ip: string): void {
311
+ if (!this.config.blacklist.includes(ip)) {
312
+ this.config.blacklist.push(ip);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Remove IP from blacklist
318
+ */
319
+ removeFromBlacklist(ip: string): void {
320
+ const index = this.config.blacklist.indexOf(ip);
321
+ if (index > -1) {
322
+ this.config.blacklist.splice(index, 1);
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Get configuration
328
+ */
329
+ getConfig(): RateLimitConfig {
330
+ return { ...this.config };
331
+ }
332
+
333
+ /**
334
+ * Clear all rate limit data
335
+ */
336
+ async clear(): Promise<void> {
337
+ await this.store.clear();
338
+ }
339
+
340
+ /**
341
+ * Cleanup expired entries
342
+ */
343
+ async cleanup(): Promise<void> {
344
+ if (this.store.cleanup) {
345
+ await this.store.cleanup();
346
+ }
347
+ }
348
+ }
@@ -0,0 +1,165 @@
1
+ import type { RateLimitStore, RateLimitEntry } from '../types.js';
2
+
3
+ /**
4
+ * In-memory rate limit store
5
+ * Good for single-instance deployments
6
+ */
7
+ export class MemoryStore implements RateLimitStore {
8
+ private store = new Map<string, RateLimitEntry>();
9
+ private cleanupInterval: NodeJS.Timeout | null = null;
10
+
11
+ constructor(cleanupIntervalMs = 60000) {
12
+ // Periodic cleanup of expired entries
13
+ this.cleanupInterval = setInterval(() => void this.cleanup(), cleanupIntervalMs);
14
+ }
15
+
16
+ async get(key: string): Promise<RateLimitEntry | null> {
17
+ const entry = this.store.get(key);
18
+ if (!entry) return null;
19
+
20
+ // Check if entry has expired
21
+ const now = Date.now();
22
+ if (entry.startTime && now - entry.startTime > this.getTtl(key)) {
23
+ this.store.delete(key);
24
+ return null;
25
+ }
26
+
27
+ return entry;
28
+ }
29
+
30
+ async set(key: string, entry: RateLimitEntry, _ttlMs: number): Promise<void> {
31
+ this.store.set(key, entry);
32
+ }
33
+
34
+ async increment(key: string, windowMs: number): Promise<RateLimitEntry> {
35
+ const now = Date.now();
36
+ let entry = this.store.get(key);
37
+
38
+ if (!entry || now - entry.startTime >= windowMs) {
39
+ // Start new window
40
+ entry = {
41
+ count: 1,
42
+ startTime: now,
43
+ timestamps: [now],
44
+ };
45
+ } else {
46
+ // Increment existing window
47
+ entry.count++;
48
+ entry.timestamps = entry.timestamps || [];
49
+ entry.timestamps.push(now);
50
+ }
51
+
52
+ this.store.set(key, entry);
53
+ return entry;
54
+ }
55
+
56
+ async reset(key: string): Promise<void> {
57
+ this.store.delete(key);
58
+ }
59
+
60
+ async clear(): Promise<void> {
61
+ this.store.clear();
62
+ }
63
+
64
+ /**
65
+ * Sliding window increment
66
+ */
67
+ async slidingWindowIncrement(key: string, windowMs: number): Promise<RateLimitEntry> {
68
+ const now = Date.now();
69
+ let entry = this.store.get(key);
70
+
71
+ if (!entry) {
72
+ entry = {
73
+ count: 1,
74
+ startTime: now,
75
+ timestamps: [now],
76
+ };
77
+ } else {
78
+ // Remove expired timestamps
79
+ const windowStart = now - windowMs;
80
+ entry.timestamps = (entry.timestamps || []).filter((t) => t > windowStart);
81
+ entry.timestamps.push(now);
82
+ entry.count = entry.timestamps.length;
83
+ entry.startTime = entry.timestamps[0] || now;
84
+ }
85
+
86
+ this.store.set(key, entry);
87
+ return entry;
88
+ }
89
+
90
+ /**
91
+ * Token bucket increment
92
+ */
93
+ async tokenBucketIncrement(
94
+ key: string,
95
+ maxTokens: number,
96
+ refillRate: number,
97
+ refillIntervalMs: number
98
+ ): Promise<RateLimitEntry> {
99
+ const now = Date.now();
100
+ let entry = this.store.get(key);
101
+
102
+ if (!entry) {
103
+ entry = {
104
+ count: 1,
105
+ startTime: now,
106
+ tokens: maxTokens - 1,
107
+ lastRefill: now,
108
+ };
109
+ } else {
110
+ // Calculate tokens to add based on time passed
111
+ const timePassed = now - (entry.lastRefill || now);
112
+ const tokensToAdd = Math.floor(timePassed / refillIntervalMs) * refillRate;
113
+
114
+ entry.tokens = Math.min(maxTokens, (entry.tokens || 0) + tokensToAdd);
115
+ entry.lastRefill = now;
116
+
117
+ if (entry.tokens > 0) {
118
+ entry.tokens--;
119
+ entry.count++;
120
+ }
121
+ }
122
+
123
+ this.store.set(key, entry);
124
+ return entry;
125
+ }
126
+
127
+ /**
128
+ * Get all keys (for admin purposes)
129
+ */
130
+ getKeys(): string[] {
131
+ return Array.from(this.store.keys());
132
+ }
133
+
134
+ /**
135
+ * Get store size
136
+ */
137
+ getSize(): number {
138
+ return this.store.size;
139
+ }
140
+
141
+ /**
142
+ * Stop cleanup interval
143
+ */
144
+ destroy(): void {
145
+ if (this.cleanupInterval) {
146
+ clearInterval(this.cleanupInterval);
147
+ this.cleanupInterval = null;
148
+ }
149
+ }
150
+
151
+ async cleanup(): Promise<void> {
152
+ const now = Date.now();
153
+ for (const [key, entry] of this.store.entries()) {
154
+ // Default TTL of 1 hour for cleanup
155
+ if (now - entry.startTime > 3600000) {
156
+ this.store.delete(key);
157
+ }
158
+ }
159
+ }
160
+
161
+ private getTtl(_key: string): number {
162
+ // Default TTL, can be customized per key if needed
163
+ return 3600000; // 1 hour
164
+ }
165
+ }