@zucker-framework/rate-limiter 1.0.0

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/dist/index.mjs ADDED
@@ -0,0 +1,542 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/rate-limiter.service.ts
5
+ import { Injectable, Logger, HttpException, HttpStatus } from "@nestjs/common";
6
+ import { RateLimiterMemory, RateLimiterRedis } from "rate-limiter-flexible";
7
+ import { BusinessException, NotFoundException } from "@zucker-framework/core";
8
+ import { createHmac } from "crypto";
9
+ function _ts_decorate(decorators, target, key, desc) {
10
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
11
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
12
+ r = Reflect.decorate(decorators, target, key, desc);
13
+ } else {
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ if (d = decorators[i]) {
16
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
17
+ }
18
+ }
19
+ }
20
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
21
+ }
22
+ __name(_ts_decorate, "_ts_decorate");
23
+ function _ts_metadata(metadataKey, metadataValue) {
24
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
25
+ return Reflect.metadata(metadataKey, metadataValue);
26
+ }
27
+ }
28
+ __name(_ts_metadata, "_ts_metadata");
29
+ function deriveRateLimitKey(key, secret) {
30
+ if (!secret) return key;
31
+ return `v1:${createHmac("sha256", secret).update(key).digest("hex")}`;
32
+ }
33
+ __name(deriveRateLimitKey, "deriveRateLimitKey");
34
+ var TooManyRequestsException = class extends HttpException {
35
+ static {
36
+ __name(this, "TooManyRequestsException");
37
+ }
38
+ retryAfter;
39
+ constructor(message, retryAfter) {
40
+ super({
41
+ statusCode: HttpStatus.TOO_MANY_REQUESTS,
42
+ message,
43
+ retryAfter
44
+ }, HttpStatus.TOO_MANY_REQUESTS);
45
+ this.retryAfter = retryAfter;
46
+ }
47
+ };
48
+ var RateLimiterService = class _RateLimiterService {
49
+ static {
50
+ __name(this, "RateLimiterService");
51
+ }
52
+ options;
53
+ logger = new Logger(_RateLimiterService.name);
54
+ limiters = /* @__PURE__ */ new Map();
55
+ useRedis = false;
56
+ constructor(options) {
57
+ this.options = options;
58
+ }
59
+ async onModuleInit() {
60
+ if (this.options.redisClient) {
61
+ this.useRedis = true;
62
+ this.logger.log("RateLimiterService \u4F7F\u7528 Redis \u5206\u5E03\u5F0F\u9650\u6D41");
63
+ }
64
+ for (const [name, config] of Object.entries(this.options.limiters)) {
65
+ if (config.points <= 0 || config.duration <= 0) {
66
+ this.logger.warn(`\u9650\u6D41\u5668 '${name}' \u914D\u7F6E\u65E0\u6548 (points=${config.points}, duration=${config.duration})\uFF0C\u5DF2\u8DF3\u8FC7`);
67
+ continue;
68
+ }
69
+ const opts = {
70
+ points: config.points,
71
+ duration: config.duration,
72
+ blockDuration: config.blockDuration ?? config.duration
73
+ };
74
+ if (this.useRedis) {
75
+ this.limiters.set(name, new RateLimiterRedis({
76
+ ...opts,
77
+ storeClient: this.options.redisClient,
78
+ keyPrefix: config.keyPrefix ?? `rl:${name}:`
79
+ }));
80
+ } else {
81
+ this.limiters.set(name, new RateLimiterMemory(opts));
82
+ }
83
+ }
84
+ this.logger.log(`RateLimiterService \u521D\u59CB\u5316\u5B8C\u6210 (${this.useRedis ? "Redis" : "Memory"}), ${this.limiters.size} \u4E2A\u9650\u6D41\u5668`);
85
+ }
86
+ onModuleDestroy() {
87
+ this.limiters.clear();
88
+ this.logger.log("RateLimiterService \u5DF2\u6E05\u7406");
89
+ }
90
+ /**
91
+ * Consume points from a rate limiter.
92
+ * Throws TooManyRequestsException (429) when limit is exceeded.
93
+ */
94
+ async consume(limiterName, key, pointsToConsume = 1, errorMessage = "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5") {
95
+ const limiter = this.getLimiter(limiterName);
96
+ const storageKey = this.toStorageKey(key);
97
+ if (pointsToConsume <= 0 || !Number.isFinite(pointsToConsume)) {
98
+ throw new BusinessException(`\u6D88\u8D39\u70B9\u6570\u65E0\u6548: ${pointsToConsume}`, "INVALID_POINTS", 400);
99
+ }
100
+ try {
101
+ const res = await limiter.consume(storageKey, pointsToConsume);
102
+ return {
103
+ remaining: res.remainingPoints,
104
+ msBeforeNext: res.msBeforeNext,
105
+ consumedPoints: res.consumedPoints
106
+ };
107
+ } catch (error) {
108
+ if (error && typeof error === "object" && "msBeforeNext" in error) {
109
+ const res = error;
110
+ const retryAfter = Math.ceil(res.msBeforeNext / 1e3);
111
+ this.logger.warn(`\u9650\u6D41\u89E6\u53D1: limiter=${limiterName}, retryAfter=${retryAfter}s`);
112
+ throw new TooManyRequestsException(errorMessage, retryAfter);
113
+ }
114
+ throw error;
115
+ }
116
+ }
117
+ /**
118
+ * Apply a penalty by consuming extra points, extending the block time.
119
+ * Useful for failed login attempts, abuse detection, etc.
120
+ */
121
+ async penalty(limiterName, key, points = 1) {
122
+ const limiter = this.getLimiter(limiterName);
123
+ await limiter.penalty(this.toStorageKey(key), points);
124
+ this.logger.log(`\u9650\u6D41\u60E9\u7F5A: limiter=${limiterName}, points=${points}`);
125
+ }
126
+ /**
127
+ * Reward by reducing consumed points (opposite of penalty).
128
+ */
129
+ async reward(limiterName, key, points = 1) {
130
+ const limiter = this.getLimiter(limiterName);
131
+ await limiter.reward(this.toStorageKey(key), points);
132
+ }
133
+ async getRemaining(limiterName, key) {
134
+ const limiter = this.getLimiter(limiterName);
135
+ const res = await limiter.get(this.toStorageKey(key));
136
+ if (!res) {
137
+ const config = this.options.limiters[limiterName];
138
+ return {
139
+ remaining: config.points,
140
+ resetAt: new Date(Date.now() + config.duration * 1e3)
141
+ };
142
+ }
143
+ return {
144
+ remaining: res.remainingPoints,
145
+ resetAt: new Date(Date.now() + res.msBeforeNext)
146
+ };
147
+ }
148
+ async reset(limiterName, key) {
149
+ const limiter = this.getLimiter(limiterName);
150
+ await limiter.delete(this.toStorageKey(key));
151
+ }
152
+ /**
153
+ * Convenience method for public API rate limiting.
154
+ * Consumes 1 point from the named limiter using the given key.
155
+ * Throws TooManyRequestsException (429) when limit is exceeded.
156
+ *
157
+ * @param key - Unique identifier for the consumer (e.g., IP address)
158
+ * @param limiterName - Name of the registered rate limiter
159
+ */
160
+ async checkPublicApiLimit(key, limiterName) {
161
+ await this.consume(limiterName, key, 1, "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5");
162
+ }
163
+ /**
164
+ * Check AI operation rate limit for a user.
165
+ * Falls back gracefully if the limiter doesn't exist (AI ops are best-effort limited).
166
+ *
167
+ * @param userId - User identifier
168
+ * @param operation - Human-readable description of the AI operation
169
+ * @param points - Points to consume (default: 1)
170
+ */
171
+ async checkAILimit(userId, operation, points = 1) {
172
+ const limiterName = "ai-default";
173
+ if (!this.has(limiterName)) return;
174
+ await this.consume(limiterName, userId, points, `AI \u64CD\u4F5C"${operation}"\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5`);
175
+ }
176
+ /** Check if a limiter exists by name */
177
+ has(limiterName) {
178
+ return this.limiters.has(limiterName);
179
+ }
180
+ /** Get all registered limiter names */
181
+ getLimiterNames() {
182
+ return [
183
+ ...this.limiters.keys()
184
+ ];
185
+ }
186
+ isDistributed() {
187
+ return this.useRedis;
188
+ }
189
+ /** Get a limiter by name, throwing if not found */
190
+ getLimiter(limiterName) {
191
+ const limiter = this.limiters.get(limiterName);
192
+ if (!limiter) {
193
+ throw new NotFoundException(`\u9650\u6D41\u5668 '${limiterName}' \u4E0D\u5B58\u5728 (\u53EF\u7528: ${this.getLimiterNames().join(", ")})`);
194
+ }
195
+ return limiter;
196
+ }
197
+ toStorageKey(key) {
198
+ return deriveRateLimitKey(key, this.options.keyHashSecret);
199
+ }
200
+ };
201
+ RateLimiterService = _ts_decorate([
202
+ Injectable(),
203
+ _ts_metadata("design:type", Function),
204
+ _ts_metadata("design:paramtypes", [
205
+ typeof RateLimiterModuleOptions === "undefined" ? Object : RateLimiterModuleOptions
206
+ ])
207
+ ], RateLimiterService);
208
+
209
+ // src/rate-limiter.module.ts
210
+ import { Module, Global } from "@nestjs/common";
211
+ import { APP_GUARD } from "@nestjs/core";
212
+
213
+ // src/guards/rate-limit.guard.ts
214
+ import { Injectable as Injectable2, Inject, Optional } from "@nestjs/common";
215
+ import { Reflector } from "@nestjs/core";
216
+
217
+ // src/decorators/rate-limit.decorator.ts
218
+ import { SetMetadata } from "@nestjs/common";
219
+ var RATE_LIMIT_KEY = "ZUCKER_RATE_LIMIT";
220
+ function RateLimit(options) {
221
+ const opts = typeof options === "string" ? {
222
+ limiter: options
223
+ } : options;
224
+ return SetMetadata(RATE_LIMIT_KEY, opts);
225
+ }
226
+ __name(RateLimit, "RateLimit");
227
+
228
+ // src/guards/rate-limit.guard.ts
229
+ function _ts_decorate2(decorators, target, key, desc) {
230
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
231
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
232
+ r = Reflect.decorate(decorators, target, key, desc);
233
+ } else {
234
+ for (var i = decorators.length - 1; i >= 0; i--) {
235
+ if (d = decorators[i]) {
236
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
237
+ }
238
+ }
239
+ }
240
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
241
+ }
242
+ __name(_ts_decorate2, "_ts_decorate");
243
+ function _ts_metadata2(metadataKey, metadataValue) {
244
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
245
+ return Reflect.metadata(metadataKey, metadataValue);
246
+ }
247
+ }
248
+ __name(_ts_metadata2, "_ts_metadata");
249
+ function _ts_param(paramIndex, decorator) {
250
+ return function(target, key) {
251
+ decorator(target, key, paramIndex);
252
+ };
253
+ }
254
+ __name(_ts_param, "_ts_param");
255
+ var RateLimitGuard = class {
256
+ static {
257
+ __name(this, "RateLimitGuard");
258
+ }
259
+ reflector;
260
+ rateLimiterService;
261
+ constructor(reflector, rateLimiterService) {
262
+ this.reflector = reflector;
263
+ this.rateLimiterService = rateLimiterService;
264
+ }
265
+ async canActivate(context) {
266
+ const options = this.reflector.getAllAndOverride(RATE_LIMIT_KEY, [
267
+ context.getHandler(),
268
+ context.getClass()
269
+ ]);
270
+ if (!options || !this.rateLimiterService) return true;
271
+ const request = context.switchToHttp().getRequest();
272
+ const key = this.extractKey(request, options);
273
+ try {
274
+ await this.rateLimiterService.consume(options.limiter, key);
275
+ return true;
276
+ } catch (err) {
277
+ if (err instanceof TooManyRequestsException) {
278
+ throw new TooManyRequestsException(options.message ?? "Too many requests", err.retryAfter);
279
+ }
280
+ throw err;
281
+ }
282
+ }
283
+ extractKey(request, options) {
284
+ const networkKey = request.ip ?? request.socket?.remoteAddress ?? "unknown-client";
285
+ switch (options.keyExtractor) {
286
+ case "#userId":
287
+ return request.user?.id ?? networkKey;
288
+ case "#username":
289
+ return request.user?.username ?? networkKey;
290
+ default:
291
+ return networkKey;
292
+ }
293
+ }
294
+ };
295
+ RateLimitGuard = _ts_decorate2([
296
+ Injectable2(),
297
+ _ts_param(1, Inject("RATE_LIMITER_SERVICE")),
298
+ _ts_param(1, Optional()),
299
+ _ts_metadata2("design:type", Function),
300
+ _ts_metadata2("design:paramtypes", [
301
+ typeof Reflector === "undefined" ? Object : Reflector,
302
+ Object
303
+ ])
304
+ ], RateLimitGuard);
305
+
306
+ // src/rate-limiter.module.ts
307
+ function _ts_decorate3(decorators, target, key, desc) {
308
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
309
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
310
+ r = Reflect.decorate(decorators, target, key, desc);
311
+ } else {
312
+ for (var i = decorators.length - 1; i >= 0; i--) {
313
+ if (d = decorators[i]) {
314
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
315
+ }
316
+ }
317
+ }
318
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
319
+ }
320
+ __name(_ts_decorate3, "_ts_decorate");
321
+ var RATE_LIMITER_MODULE_OPTIONS = /* @__PURE__ */ Symbol("RATE_LIMITER_MODULE_OPTIONS");
322
+ var ZuckerRateLimiterModule = class _ZuckerRateLimiterModule {
323
+ static {
324
+ __name(this, "ZuckerRateLimiterModule");
325
+ }
326
+ static forRoot(options) {
327
+ const providers = [
328
+ {
329
+ provide: RateLimiterService,
330
+ useFactory: /* @__PURE__ */ __name(() => new RateLimiterService(options), "useFactory")
331
+ },
332
+ {
333
+ provide: "RATE_LIMITER_SERVICE",
334
+ useExisting: RateLimiterService
335
+ },
336
+ RateLimitGuard
337
+ ];
338
+ if (options.globalGuard !== false) {
339
+ providers.push({
340
+ provide: APP_GUARD,
341
+ useExisting: RateLimitGuard
342
+ });
343
+ }
344
+ return {
345
+ module: _ZuckerRateLimiterModule,
346
+ global: true,
347
+ providers,
348
+ exports: [
349
+ RateLimiterService,
350
+ RateLimitGuard,
351
+ "RATE_LIMITER_SERVICE"
352
+ ]
353
+ };
354
+ }
355
+ static forRootAsync(options) {
356
+ const asyncProviders = this.createAsyncProviders(options);
357
+ const providers = [
358
+ ...asyncProviders,
359
+ {
360
+ provide: RateLimiterService,
361
+ useFactory: /* @__PURE__ */ __name((opts) => new RateLimiterService(opts), "useFactory"),
362
+ inject: [
363
+ RATE_LIMITER_MODULE_OPTIONS
364
+ ]
365
+ },
366
+ {
367
+ provide: "RATE_LIMITER_SERVICE",
368
+ useExisting: RateLimiterService
369
+ },
370
+ RateLimitGuard
371
+ ];
372
+ if (options.globalGuard !== false) {
373
+ providers.push({
374
+ provide: APP_GUARD,
375
+ useExisting: RateLimitGuard
376
+ });
377
+ }
378
+ return {
379
+ module: _ZuckerRateLimiterModule,
380
+ global: true,
381
+ imports: options.imports ?? [],
382
+ providers,
383
+ exports: [
384
+ RateLimiterService,
385
+ RateLimitGuard,
386
+ "RATE_LIMITER_SERVICE"
387
+ ]
388
+ };
389
+ }
390
+ static createAsyncProviders(options) {
391
+ if (options.useFactory) {
392
+ return [
393
+ {
394
+ provide: RATE_LIMITER_MODULE_OPTIONS,
395
+ useFactory: options.useFactory,
396
+ inject: options.inject ?? []
397
+ }
398
+ ];
399
+ }
400
+ const optionsProvider = {
401
+ provide: RATE_LIMITER_MODULE_OPTIONS,
402
+ useFactory: /* @__PURE__ */ __name(async (factory) => factory.createRateLimiterOptions(), "useFactory"),
403
+ inject: [
404
+ options.useClass ?? options.useExisting
405
+ ]
406
+ };
407
+ if (options.useClass) {
408
+ return [
409
+ {
410
+ provide: options.useClass,
411
+ useClass: options.useClass
412
+ },
413
+ optionsProvider
414
+ ];
415
+ }
416
+ return [
417
+ optionsProvider
418
+ ];
419
+ }
420
+ };
421
+ ZuckerRateLimiterModule = _ts_decorate3([
422
+ Global(),
423
+ Module({})
424
+ ], ZuckerRateLimiterModule);
425
+
426
+ // src/dynamic-limiter.ts
427
+ import { Injectable as Injectable3, Logger as Logger2 } from "@nestjs/common";
428
+ function _ts_decorate4(decorators, target, key, desc) {
429
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
430
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
431
+ r = Reflect.decorate(decorators, target, key, desc);
432
+ } else {
433
+ for (var i = decorators.length - 1; i >= 0; i--) {
434
+ if (d = decorators[i]) {
435
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
436
+ }
437
+ }
438
+ }
439
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
440
+ }
441
+ __name(_ts_decorate4, "_ts_decorate");
442
+ var DynamicLimiterManager = class _DynamicLimiterManager {
443
+ static {
444
+ __name(this, "DynamicLimiterManager");
445
+ }
446
+ logger = new Logger2(_DynamicLimiterManager.name);
447
+ rules = /* @__PURE__ */ new Map();
448
+ listeners = [];
449
+ /** 添加或更新限流规则 */
450
+ setRule(rule) {
451
+ rule.createdAt = rule.createdAt ?? Date.now();
452
+ this.rules.set(rule.id, rule);
453
+ this.notifyListeners();
454
+ this.logger.log(`Dynamic limiter rule set: ${rule.id} (${rule.points}/${rule.duration}s)`);
455
+ }
456
+ /** 删除限流规则 */
457
+ removeRule(id) {
458
+ const removed = this.rules.delete(id);
459
+ if (removed) {
460
+ this.notifyListeners();
461
+ this.logger.log(`Dynamic limiter rule removed: ${id}`);
462
+ }
463
+ return removed;
464
+ }
465
+ /** 获取所有规则(按优先级排序) */
466
+ getRules() {
467
+ return Array.from(this.rules.values()).filter((r) => r.enabled).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
468
+ }
469
+ /** 根据路径和角色匹配限流规则 */
470
+ matchRule(path, roles) {
471
+ for (const rule of this.getRules()) {
472
+ if (rule.pathPattern) {
473
+ const regex = this.globToRegex(rule.pathPattern);
474
+ if (!regex.test(path)) continue;
475
+ }
476
+ if (rule.roles?.length) {
477
+ if (!roles?.some((r) => rule.roles.includes(r))) continue;
478
+ }
479
+ return rule;
480
+ }
481
+ return void 0;
482
+ }
483
+ /** 批量导入规则 */
484
+ importRules(rules) {
485
+ for (const rule of rules) {
486
+ this.rules.set(rule.id, {
487
+ ...rule,
488
+ createdAt: rule.createdAt ?? Date.now()
489
+ });
490
+ }
491
+ this.notifyListeners();
492
+ this.logger.log(`Imported ${rules.length} dynamic limiter rules`);
493
+ }
494
+ /** 导出所有规则 */
495
+ exportRules() {
496
+ return Array.from(this.rules.values());
497
+ }
498
+ /** 监听规则变更 */
499
+ onRulesChanged(listener) {
500
+ this.listeners.push(listener);
501
+ return {
502
+ unsubscribe: /* @__PURE__ */ __name(() => {
503
+ const idx = this.listeners.indexOf(listener);
504
+ if (idx >= 0) this.listeners.splice(idx, 1);
505
+ }, "unsubscribe")
506
+ };
507
+ }
508
+ /** 启用/禁用规则 */
509
+ toggleRule(id, enabled) {
510
+ const rule = this.rules.get(id);
511
+ if (!rule) return false;
512
+ rule.enabled = enabled;
513
+ this.notifyListeners();
514
+ return true;
515
+ }
516
+ notifyListeners() {
517
+ const rules = this.getRules();
518
+ for (const listener of this.listeners) {
519
+ try {
520
+ listener(rules);
521
+ } catch {
522
+ }
523
+ }
524
+ }
525
+ globToRegex(glob) {
526
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
527
+ return new RegExp(`^${escaped}$`);
528
+ }
529
+ };
530
+ DynamicLimiterManager = _ts_decorate4([
531
+ Injectable3()
532
+ ], DynamicLimiterManager);
533
+ export {
534
+ DynamicLimiterManager,
535
+ RATE_LIMIT_KEY,
536
+ RateLimit,
537
+ RateLimitGuard,
538
+ RateLimiterService,
539
+ TooManyRequestsException,
540
+ ZuckerRateLimiterModule,
541
+ deriveRateLimitKey
542
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@zucker-framework/rate-limiter",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.mjs",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "LICENSE",
10
+ "NOTICE",
11
+ "THIRD_PARTY_NOTICES"
12
+ ],
13
+ "dependencies": {
14
+ "@zucker-framework/core": "1.0.0",
15
+ "rate-limiter-flexible": "^11.2.0"
16
+ },
17
+ "peerDependencies": {
18
+ "@nestjs/common": "^12.0.0",
19
+ "@nestjs/core": "^12.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.19.43"
23
+ },
24
+ "license": "Apache-2.0",
25
+ "author": "Lind",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/Lind-pro/zucker-framework.git",
29
+ "directory": "packages/rate-limiter"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org/"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup src/index.ts --format cjs,esm --dts --external @nestjs/common --external @nestjs/core --external @zucker-framework/core --external rate-limiter-flexible",
37
+ "clean": "rm -rf dist",
38
+ "lint": "eslint src/"
39
+ }
40
+ }