limitly 2.0.0 → 3.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/README.md +210 -40
- package/dist/algorithms/concurrency.d.ts +11 -0
- package/dist/algorithms/concurrency.d.ts.map +1 -0
- package/dist/algorithms/concurrency.js +19 -0
- package/dist/algorithms/concurrency.js.map +1 -0
- package/dist/algorithms/factory.d.ts.map +1 -1
- package/dist/algorithms/factory.js +6 -0
- package/dist/algorithms/factory.js.map +1 -1
- package/dist/algorithms/gcra.d.ts +10 -0
- package/dist/algorithms/gcra.d.ts.map +1 -0
- package/dist/algorithms/gcra.js +15 -0
- package/dist/algorithms/gcra.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/limiter.d.ts +4 -0
- package/dist/limiter.d.ts.map +1 -1
- package/dist/limiter.js +21 -2
- package/dist/limiter.js.map +1 -1
- package/dist/middleware/bun.d.ts.map +1 -1
- package/dist/middleware/bun.js +21 -10
- package/dist/middleware/bun.js.map +1 -1
- package/dist/middleware/express.d.ts.map +1 -1
- package/dist/middleware/express.js +23 -13
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/fastify.d.ts.map +1 -1
- package/dist/middleware/fastify.js +19 -8
- package/dist/middleware/fastify.js.map +1 -1
- package/dist/middleware/hono.d.ts.map +1 -1
- package/dist/middleware/hono.js +26 -12
- package/dist/middleware/hono.js.map +1 -1
- package/dist/middleware/koa.d.ts.map +1 -1
- package/dist/middleware/koa.js +24 -13
- package/dist/middleware/koa.js.map +1 -1
- package/dist/middleware/nest.d.ts.map +1 -1
- package/dist/middleware/nest.js +22 -12
- package/dist/middleware/nest.js.map +1 -1
- package/dist/observability/index.d.ts +4 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +10 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/opentelemetry.d.ts +28 -0
- package/dist/observability/opentelemetry.d.ts.map +1 -0
- package/dist/observability/opentelemetry.js +85 -0
- package/dist/observability/opentelemetry.js.map +1 -0
- package/dist/observability/prometheus.d.ts +27 -0
- package/dist/observability/prometheus.d.ts.map +1 -0
- package/dist/observability/prometheus.js +88 -0
- package/dist/observability/prometheus.js.map +1 -0
- package/dist/observability/types.d.ts +9 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/observability/types.js +3 -0
- package/dist/observability/types.js.map +1 -0
- package/dist/prometheus.d.ts +3 -0
- package/dist/prometheus.d.ts.map +1 -0
- package/dist/prometheus.js +9 -0
- package/dist/prometheus.js.map +1 -0
- package/dist/scripts/concurrencyAcquire.lua +34 -0
- package/dist/scripts/concurrencyRelease.lua +9 -0
- package/dist/scripts/gcra.lua +38 -0
- package/dist/stores/factory.d.ts.map +1 -1
- package/dist/stores/factory.js +5 -1
- package/dist/stores/factory.js.map +1 -1
- package/dist/stores/memcached-store.d.ts +3 -0
- package/dist/stores/memcached-store.d.ts.map +1 -1
- package/dist/stores/memcached-store.js +94 -0
- package/dist/stores/memcached-store.js.map +1 -1
- package/dist/stores/redis-store.d.ts +8 -1
- package/dist/stores/redis-store.d.ts.map +1 -1
- package/dist/stores/redis-store.js +46 -3
- package/dist/stores/redis-store.js.map +1 -1
- package/dist/stores/types.d.ts +3 -0
- package/dist/stores/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +33 -5
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/defaults.d.ts +3 -1
- package/dist/utils/defaults.d.ts.map +1 -1
- package/dist/utils/defaults.js +43 -2
- package/dist/utils/defaults.js.map +1 -1
- package/dist/utils/limit-execution.d.ts +40 -0
- package/dist/utils/limit-execution.d.ts.map +1 -0
- package/dist/utils/limit-execution.js +50 -0
- package/dist/utils/limit-execution.js.map +1 -0
- package/dist/utils/memcached.d.ts +1 -0
- package/dist/utils/memcached.d.ts.map +1 -1
- package/dist/utils/memcached.js +12 -0
- package/dist/utils/memcached.js.map +1 -1
- package/dist/utils/metrics.d.ts.map +1 -1
- package/dist/utils/metrics.js +41 -2
- package/dist/utils/metrics.js.map +1 -1
- package/dist/utils/redis.d.ts +4 -1
- package/dist/utils/redis.d.ts.map +1 -1
- package/dist/utils/redis.js +37 -4
- package/dist/utils/redis.js.map +1 -1
- package/dist/utils/scripts.d.ts +16 -2
- package/dist/utils/scripts.d.ts.map +1 -1
- package/dist/utils/scripts.js +79 -33
- package/dist/utils/scripts.js.map +1 -1
- package/package.json +29 -3
package/README.md
CHANGED
|
@@ -12,6 +12,12 @@ npm install limitly ioredis
|
|
|
12
12
|
# Memcached backend
|
|
13
13
|
npm install limitly memcached
|
|
14
14
|
|
|
15
|
+
# OpenTelemetry
|
|
16
|
+
npm install limitly ioredis @opentelemetry/api
|
|
17
|
+
|
|
18
|
+
# Prometheus
|
|
19
|
+
npm install limitly ioredis prom-client
|
|
20
|
+
|
|
15
21
|
# NestJS
|
|
16
22
|
npm install limitly ioredis @nestjs/common @nestjs/core
|
|
17
23
|
```
|
|
@@ -39,14 +45,7 @@ const app = express();
|
|
|
39
45
|
const redis = new Redis();
|
|
40
46
|
const limiter = createLimiter({ redis });
|
|
41
47
|
|
|
42
|
-
app.use(
|
|
43
|
-
limiter.middleware({
|
|
44
|
-
algorithm: "sliding-window",
|
|
45
|
-
limit: 100,
|
|
46
|
-
window: 60,
|
|
47
|
-
key: (req) => req.ip,
|
|
48
|
-
}),
|
|
49
|
-
);
|
|
48
|
+
app.use(limiter.middleware({ key: (req) => req.ip }));
|
|
50
49
|
```
|
|
51
50
|
|
|
52
51
|
### Fastify
|
|
@@ -77,15 +76,7 @@ import { createLimiter } from "limitly";
|
|
|
77
76
|
const app = new Hono();
|
|
78
77
|
const limiter = createLimiter({ redis: new Redis() });
|
|
79
78
|
|
|
80
|
-
app.use(
|
|
81
|
-
"*",
|
|
82
|
-
limiter.honoMiddleware({
|
|
83
|
-
algorithm: "sliding-window",
|
|
84
|
-
limit: 100,
|
|
85
|
-
window: 60,
|
|
86
|
-
key: (c) => c.req.header("x-api-key"),
|
|
87
|
-
})
|
|
88
|
-
);
|
|
79
|
+
app.use("*", limiter.honoMiddleware({ key: (c) => c.req.header("x-api-key") }));
|
|
89
80
|
```
|
|
90
81
|
|
|
91
82
|
### Koa
|
|
@@ -98,14 +89,7 @@ import { createLimiter } from "limitly";
|
|
|
98
89
|
const app = new Koa();
|
|
99
90
|
const limiter = createLimiter({ redis: new Redis() });
|
|
100
91
|
|
|
101
|
-
app.use(
|
|
102
|
-
limiter.koaMiddleware({
|
|
103
|
-
algorithm: "sliding-window",
|
|
104
|
-
limit: 100,
|
|
105
|
-
window: 60,
|
|
106
|
-
key: (ctx) => ctx.ip,
|
|
107
|
-
})
|
|
108
|
-
);
|
|
92
|
+
app.use(limiter.koaMiddleware({ key: (ctx) => ctx.ip }));
|
|
109
93
|
```
|
|
110
94
|
|
|
111
95
|
### Bun
|
|
@@ -117,9 +101,6 @@ import { composeBunHandler, createLimiter } from "limitly";
|
|
|
117
101
|
const limiter = createLimiter({ redis: new Redis() });
|
|
118
102
|
|
|
119
103
|
const rateLimit = limiter.bunMiddleware({
|
|
120
|
-
algorithm: "sliding-window",
|
|
121
|
-
limit: 100,
|
|
122
|
-
window: 60,
|
|
123
104
|
key: (req) => req.headers.get("x-api-key"),
|
|
124
105
|
});
|
|
125
106
|
|
|
@@ -142,7 +123,6 @@ import { RateLimit } from "limitly/nest";
|
|
|
142
123
|
|
|
143
124
|
const limiter = createLimiter({ redis: new Redis() });
|
|
144
125
|
const NestGuard = limiter.nestGuard({
|
|
145
|
-
algorithm: "sliding-window",
|
|
146
126
|
limit: 100,
|
|
147
127
|
window: 60,
|
|
148
128
|
key: (req) => req.ip,
|
|
@@ -163,7 +143,7 @@ import { RateLimit } from "limitly/nest";
|
|
|
163
143
|
@Controller("api")
|
|
164
144
|
export class ApiController {
|
|
165
145
|
@Get()
|
|
166
|
-
@RateLimit({
|
|
146
|
+
@RateLimit({ limit: 10, window: 60 })
|
|
167
147
|
findAll() {
|
|
168
148
|
return { ok: true };
|
|
169
149
|
}
|
|
@@ -191,7 +171,7 @@ export class AppModule {}
|
|
|
191
171
|
|
|
192
172
|
## Default Algorithm
|
|
193
173
|
|
|
194
|
-
If you omit algorithm options, limitly uses **
|
|
174
|
+
If you omit algorithm options, limitly uses **GCRA** with `limit: 100` and `window: 60`:
|
|
195
175
|
|
|
196
176
|
```typescript
|
|
197
177
|
const limiter = createLimiter({ redis: new Redis() });
|
|
@@ -201,7 +181,7 @@ app.use(limiter.middleware({ key: (req) => req.ip }));
|
|
|
201
181
|
// equivalent to:
|
|
202
182
|
app.use(
|
|
203
183
|
limiter.middleware({
|
|
204
|
-
algorithm: "
|
|
184
|
+
algorithm: "gcra",
|
|
205
185
|
limit: 100,
|
|
206
186
|
window: 60,
|
|
207
187
|
key: (req) => req.ip,
|
|
@@ -226,7 +206,6 @@ Use `limiter.check()` outside middleware — useful for login guards, background
|
|
|
226
206
|
|
|
227
207
|
```typescript
|
|
228
208
|
const result = await limiter.check(req.ip ?? "unknown", {
|
|
229
|
-
algorithm: "sliding-window",
|
|
230
209
|
limit: 5,
|
|
231
210
|
window: 10,
|
|
232
211
|
});
|
|
@@ -243,6 +222,20 @@ if (!result.allowed) {
|
|
|
243
222
|
|
|
244
223
|
## Algorithms
|
|
245
224
|
|
|
225
|
+
### GCRA (default)
|
|
226
|
+
|
|
227
|
+
Generic Cell Rate Algorithm — smooth rate limiting with controlled bursts. Uses a single TAT (theoretical arrival time) per key, so it's memory-efficient compared to sliding window:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
limiter.middleware({
|
|
231
|
+
limit: 100,
|
|
232
|
+
window: 60, // seconds — algorithm defaults to gcra
|
|
233
|
+
key: (req) => req.ip,
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
GCRA enforces an average rate of `limit / window` while allowing short bursts up to `limit`. Ideal when you want token-bucket-like behavior with predictable storage costs.
|
|
238
|
+
|
|
246
239
|
### Sliding Window
|
|
247
240
|
|
|
248
241
|
Uses Redis Sorted Sets (or Memcached counters) for rate limiting over a rolling time window.
|
|
@@ -267,6 +260,45 @@ limiter.middleware({
|
|
|
267
260
|
});
|
|
268
261
|
```
|
|
269
262
|
|
|
263
|
+
### Concurrency
|
|
264
|
+
|
|
265
|
+
Limits simultaneous in-flight requests per key. Slots are acquired on entry and released when the response finishes (middleware handles this automatically):
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
limiter.middleware({
|
|
269
|
+
algorithm: "concurrency",
|
|
270
|
+
limit: 10, // max concurrent requests
|
|
271
|
+
ttl: 300, // lease TTL in seconds for stale slot cleanup
|
|
272
|
+
key: (req) => req.ip,
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
For manual acquire/release outside middleware:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
const acquired = await limiter.acquire("job-42", {
|
|
280
|
+
algorithm: "concurrency",
|
|
281
|
+
limit: 5,
|
|
282
|
+
ttl: 120,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (!acquired.allowed) {
|
|
286
|
+
throw new Error("Too many concurrent jobs");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
await runJob();
|
|
291
|
+
} finally {
|
|
292
|
+
await limiter.release("job-42", acquired.slotId!, {
|
|
293
|
+
algorithm: "concurrency",
|
|
294
|
+
limit: 5,
|
|
295
|
+
ttl: 120,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Storage keys use the `cc:` prefix: `{keyPrefix}:cc:{id}`.
|
|
301
|
+
|
|
270
302
|
## Storage Backends
|
|
271
303
|
|
|
272
304
|
limitly supports Redis, Valkey, DragonflyDB (Redis-compatible), and Memcached.
|
|
@@ -287,13 +319,35 @@ createLimiter({ store: "valkey", redis: "redis://localhost:6379" });
|
|
|
287
319
|
// DragonflyDB
|
|
288
320
|
createLimiter({ store: "dragonfly", redis: { host: "localhost", port: 6379 } });
|
|
289
321
|
|
|
290
|
-
// Cluster
|
|
322
|
+
// Cluster (auto-pipelining, script warmup, master reads)
|
|
291
323
|
createLimiter({
|
|
292
324
|
store: "redis",
|
|
325
|
+
redis: {
|
|
326
|
+
nodes: [
|
|
327
|
+
{ host: "127.0.0.1", port: 7000 },
|
|
328
|
+
{ host: "127.0.0.1", port: 7001 },
|
|
329
|
+
],
|
|
330
|
+
options: {
|
|
331
|
+
redisOptions: { password: "secret" },
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Optional: pin all keys to one slot (use only when you need co-location)
|
|
337
|
+
createLimiter({
|
|
293
338
|
redis: { nodes: [{ host: "127.0.0.1", port: 7000 }] },
|
|
339
|
+
hashTag: "limitly",
|
|
294
340
|
});
|
|
295
341
|
```
|
|
296
342
|
|
|
343
|
+
Cluster optimizations built into limitly:
|
|
344
|
+
|
|
345
|
+
- **ioredis `defineCommand`** — Lua scripts are registered cluster-aware, avoiding per-node `NOSCRIPT` fallbacks
|
|
346
|
+
- **Script warmup** — scripts are preloaded on all master nodes at startup (disable with `warmupScripts: false`)
|
|
347
|
+
- **Auto-pipelining** — enabled by default for higher throughput under concurrent load
|
|
348
|
+
- **Hash tags** — optional `hashTag` for slot pinning; leave unset to spread keys across slots (recommended)
|
|
349
|
+
- **Master reads** — `scaleReads: "master"` by default so checks always hit the authoritative node
|
|
350
|
+
|
|
297
351
|
### Memcached
|
|
298
352
|
|
|
299
353
|
Memcached uses counter-based sliding window and CAS token bucket (no Lua required):
|
|
@@ -349,6 +403,8 @@ Key format:
|
|
|
349
403
|
```
|
|
350
404
|
{keyPrefix}:sw:{id} — sliding window
|
|
351
405
|
{keyPrefix}:tb:{id} — token bucket
|
|
406
|
+
{keyPrefix}:gcra:{id} — GCRA
|
|
407
|
+
{keyPrefix}:cc:{id} — concurrency
|
|
352
408
|
```
|
|
353
409
|
|
|
354
410
|
## Response Headers
|
|
@@ -368,9 +424,6 @@ Disable with `headers: false`.
|
|
|
368
424
|
|
|
369
425
|
```typescript
|
|
370
426
|
limiter.middleware({
|
|
371
|
-
algorithm: "sliding-window",
|
|
372
|
-
limit: 100,
|
|
373
|
-
window: 60,
|
|
374
427
|
onLimitReached(req, res) {
|
|
375
428
|
res.status(429).json({ code: "RATE_LIMITED" });
|
|
376
429
|
},
|
|
@@ -391,9 +444,6 @@ const limiter = createLimiter({
|
|
|
391
444
|
|
|
392
445
|
// Per-route override
|
|
393
446
|
limiter.middleware({
|
|
394
|
-
algorithm: "sliding-window",
|
|
395
|
-
limit: 100,
|
|
396
|
-
window: 60,
|
|
397
447
|
onMetrics: (event) => metrics.increment(`ratelimit.${event.type}`),
|
|
398
448
|
});
|
|
399
449
|
```
|
|
@@ -417,6 +467,123 @@ onMetrics: [logToConsole, sendToDatadog]
|
|
|
417
467
|
|
|
418
468
|
`limiter.check()` and all framework middleware use the same metrics pipeline.
|
|
419
469
|
|
|
470
|
+
## OpenTelemetry
|
|
471
|
+
|
|
472
|
+
Use the `limitly/otel` helpers to emit OTEL metrics and traces. Only `@opentelemetry/api` is required — bring your own SDK/exporter:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import Redis from "ioredis";
|
|
476
|
+
import { createLimiter } from "limitly";
|
|
477
|
+
import { createOpenTelemetryInstrumentation } from "limitly/otel";
|
|
478
|
+
|
|
479
|
+
const otel = createOpenTelemetryInstrumentation();
|
|
480
|
+
|
|
481
|
+
const limiter = createLimiter({
|
|
482
|
+
redis: new Redis(),
|
|
483
|
+
tracer: otel.tracer,
|
|
484
|
+
onMetrics: otel.onMetrics,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
app.use(limiter.middleware({ key: (req) => req.ip }));
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
`createOpenTelemetryInstrumentation()` returns:
|
|
491
|
+
|
|
492
|
+
- **`tracer`** — creates `limitly.check` spans with `limitly.algorithm`, `limitly.store`, `limitly.outcome`, `limitly.limit`, and `limitly.remaining` attributes
|
|
493
|
+
- **`onMetrics`** — records `limitly.check.total` (counter) and `limitly.check.duration` (histogram, ms)
|
|
494
|
+
|
|
495
|
+
Use the pieces independently when needed:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import {
|
|
499
|
+
createOpenTelemetryMetricsHook,
|
|
500
|
+
createOpenTelemetryTracer,
|
|
501
|
+
} from "limitly/otel";
|
|
502
|
+
|
|
503
|
+
const limiter = createLimiter({
|
|
504
|
+
redis: new Redis(),
|
|
505
|
+
tracer: createOpenTelemetryTracer(),
|
|
506
|
+
onMetrics: createOpenTelemetryMetricsHook({ includeKey: false }),
|
|
507
|
+
});
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Combine with custom hooks:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
onMetrics: [otel.onMetrics, customHook]
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Prometheus
|
|
517
|
+
|
|
518
|
+
Use `limitly/prometheus` to expose rate limit metrics for scraping:
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
import express from "express";
|
|
522
|
+
import Redis from "ioredis";
|
|
523
|
+
import { createLimiter } from "limitly";
|
|
524
|
+
import {
|
|
525
|
+
createPrometheusExporter,
|
|
526
|
+
createPrometheusHandler,
|
|
527
|
+
} from "limitly/prometheus";
|
|
528
|
+
|
|
529
|
+
const app = express();
|
|
530
|
+
const prometheus = createPrometheusExporter();
|
|
531
|
+
|
|
532
|
+
const limiter = createLimiter({
|
|
533
|
+
redis: new Redis(),
|
|
534
|
+
onMetrics: prometheus.onMetrics,
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
app.use(limiter.middleware({ key: (req) => req.ip }));
|
|
538
|
+
app.get("/metrics", createPrometheusHandler(prometheus));
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Metrics exposed:
|
|
542
|
+
|
|
543
|
+
| Metric | Type | Labels |
|
|
544
|
+
|--------|------|--------|
|
|
545
|
+
| `limitly_check_total` | Counter | `algorithm`, `outcome`, `store` |
|
|
546
|
+
| `limitly_check_duration_seconds` | Histogram | `algorithm`, `outcome`, `store` |
|
|
547
|
+
|
|
548
|
+
`outcome` is one of `allowed`, `blocked`, `error`, or `fail_open`.
|
|
549
|
+
|
|
550
|
+
Options:
|
|
551
|
+
|
|
552
|
+
| Option | Default | Description |
|
|
553
|
+
|--------|---------|-------------|
|
|
554
|
+
| `register` | new `Registry` | Prometheus registry |
|
|
555
|
+
| `prefix` | `"limitly"` | Metric name prefix |
|
|
556
|
+
| `includeKey` | `false` | Add rate limit key as a label (high cardinality) |
|
|
557
|
+
| `durationBuckets` | `[0.001, 0.005, 0.01, …, 1]` | Histogram buckets (seconds) |
|
|
558
|
+
|
|
559
|
+
Use a shared registry with your existing metrics:
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
import { Registry } from "prom-client";
|
|
563
|
+
|
|
564
|
+
const register = new Registry();
|
|
565
|
+
const prometheus = createPrometheusExporter({ register });
|
|
566
|
+
|
|
567
|
+
onMetrics: [prometheus.onMetrics, otherHook];
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Scrape manually without HTTP middleware:
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
const body = await prometheus.getMetrics();
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Use `createPrometheusMetricsHook` when you only need the `onMetrics` hook without the HTTP handler:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import { createPrometheusMetricsHook } from "limitly/prometheus";
|
|
580
|
+
|
|
581
|
+
const limiter = createLimiter({
|
|
582
|
+
redis: new Redis(),
|
|
583
|
+
onMetrics: createPrometheusMetricsHook({ prefix: "api" }),
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
420
587
|
## Fail Open / Closed
|
|
421
588
|
|
|
422
589
|
When Redis is unavailable:
|
|
@@ -436,9 +603,12 @@ Implement custom algorithms with the `RateLimitStrategy` interface:
|
|
|
436
603
|
```typescript
|
|
437
604
|
interface RateLimitStrategy {
|
|
438
605
|
consume(key: string): Promise<RateLimitResult>;
|
|
606
|
+
release?(key: string, slotId: string): Promise<void>;
|
|
439
607
|
}
|
|
440
608
|
```
|
|
441
609
|
|
|
610
|
+
`release` is optional — only needed for concurrency-style strategies that hold a slot until the response finishes.
|
|
611
|
+
|
|
442
612
|
## Performance
|
|
443
613
|
|
|
444
614
|
- Atomic operations via Lua scripts (`EVALSHA`)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ConcurrencyConfig, RateLimitResult, RateLimitStrategy } from "../types";
|
|
2
|
+
import type { RateLimitStore } from "../stores/types";
|
|
3
|
+
export declare class ConcurrencyStrategy implements RateLimitStrategy {
|
|
4
|
+
private readonly store;
|
|
5
|
+
private readonly limit;
|
|
6
|
+
private readonly ttl;
|
|
7
|
+
constructor(store: RateLimitStore, config: ConcurrencyConfig);
|
|
8
|
+
consume(key: string): Promise<RateLimitResult>;
|
|
9
|
+
release(key: string, slotId: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=concurrency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/algorithms/concurrency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,qBAAa,mBAAoB,YAAW,iBAAiB;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB;IAMtD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAI9C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConcurrencyStrategy = void 0;
|
|
4
|
+
const defaults_1 = require("../utils/defaults");
|
|
5
|
+
class ConcurrencyStrategy {
|
|
6
|
+
constructor(store, config) {
|
|
7
|
+
this.store = store;
|
|
8
|
+
this.limit = config.limit;
|
|
9
|
+
this.ttl = config.ttl ?? defaults_1.DEFAULT_CONCURRENCY.ttl;
|
|
10
|
+
}
|
|
11
|
+
async consume(key) {
|
|
12
|
+
return this.store.concurrencyAcquire(key, this.limit, this.ttl);
|
|
13
|
+
}
|
|
14
|
+
async release(key, slotId) {
|
|
15
|
+
await this.store.concurrencyRelease(key, slotId);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.ConcurrencyStrategy = ConcurrencyStrategy;
|
|
19
|
+
//# sourceMappingURL=concurrency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../src/algorithms/concurrency.ts"],"names":[],"mappings":";;;AAMA,gDAAwD;AAExD,MAAa,mBAAmB;IAK9B,YAAY,KAAqB,EAAE,MAAyB;QAC1D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,8BAAmB,CAAC,GAAI,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,MAAc;QACvC,MAAM,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;CACF;AAlBD,kDAkBC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/algorithms/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/algorithms/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAMtD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,eAAe,GACtB,iBAAiB,CAiBnB"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createStrategy = createStrategy;
|
|
4
|
+
const concurrency_1 = require("./concurrency");
|
|
5
|
+
const gcra_1 = require("./gcra");
|
|
4
6
|
const sliding_window_1 = require("./sliding-window");
|
|
5
7
|
const token_bucket_1 = require("./token-bucket");
|
|
6
8
|
function createStrategy(store, config) {
|
|
@@ -9,6 +11,10 @@ function createStrategy(store, config) {
|
|
|
9
11
|
return new sliding_window_1.SlidingWindowStrategy(store, config);
|
|
10
12
|
case "token-bucket":
|
|
11
13
|
return new token_bucket_1.TokenBucketStrategy(store, config);
|
|
14
|
+
case "concurrency":
|
|
15
|
+
return new concurrency_1.ConcurrencyStrategy(store, config);
|
|
16
|
+
case "gcra":
|
|
17
|
+
return new gcra_1.GcraStrategy(store, config);
|
|
12
18
|
default: {
|
|
13
19
|
const exhaustive = config;
|
|
14
20
|
throw new Error(`Unknown algorithm: ${exhaustive.algorithm}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/algorithms/factory.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/algorithms/factory.ts"],"names":[],"mappings":";;AAOA,wCAoBC;AAzBD,+CAAoD;AACpD,iCAAsC;AACtC,qDAAyD;AACzD,iDAAqD;AAErD,SAAgB,cAAc,CAC5B,KAAqB,EACrB,MAAuB;IAEvB,QAAQ,MAAM,CAAC,SAAS,EAAE,CAAC;QACzB,KAAK,gBAAgB;YACnB,OAAO,IAAI,sCAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAClD,KAAK,cAAc;YACjB,OAAO,IAAI,kCAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAChD,KAAK,aAAa;YAChB,OAAO,IAAI,iCAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAChD,KAAK,MAAM;YACT,OAAO,IAAI,mBAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzC,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAU,MAAM,CAAC;YACjC,MAAM,IAAI,KAAK,CACb,sBAAuB,UAA8B,CAAC,SAAS,EAAE,CAClE,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GcraConfig, RateLimitResult, RateLimitStrategy } from "../types";
|
|
2
|
+
import type { RateLimitStore } from "../stores/types";
|
|
3
|
+
export declare class GcraStrategy implements RateLimitStrategy {
|
|
4
|
+
private readonly store;
|
|
5
|
+
private readonly limit;
|
|
6
|
+
private readonly window;
|
|
7
|
+
constructor(store: RateLimitStore, config: GcraConfig);
|
|
8
|
+
consume(key: string): Promise<RateLimitResult>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=gcra.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcra.d.ts","sourceRoot":"","sources":["../../src/algorithms/gcra.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,qBAAa,YAAa,YAAW,iBAAiB;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU;IAM/C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAGrD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GcraStrategy = void 0;
|
|
4
|
+
class GcraStrategy {
|
|
5
|
+
constructor(store, config) {
|
|
6
|
+
this.store = store;
|
|
7
|
+
this.limit = config.limit;
|
|
8
|
+
this.window = config.window;
|
|
9
|
+
}
|
|
10
|
+
async consume(key) {
|
|
11
|
+
return this.store.gcra(key, this.limit, this.window);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.GcraStrategy = GcraStrategy;
|
|
15
|
+
//# sourceMappingURL=gcra.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcra.js","sourceRoot":"","sources":["../../src/algorithms/gcra.ts"],"names":[],"mappings":";;;AAGA,MAAa,YAAY;IAKvB,YAAY,KAAqB,EAAE,MAAkB;QACnD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACvD,CAAC;CACF;AAdD,oCAcC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,16 +7,20 @@ export { applyRateLimitHeaders, composeBunHandler, createBunMiddleware, jsonResp
|
|
|
7
7
|
export type { BunMiddleware, BunNext } from "./middleware/bun";
|
|
8
8
|
export { createNestGuard, limitlyNestModule, LimitlyModule, RateLimit, RATE_LIMIT_KEY, } from "./middleware/nest";
|
|
9
9
|
export type { NestRateLimitOptions } from "./middleware/nest";
|
|
10
|
+
export { ConcurrencyStrategy } from "./algorithms/concurrency";
|
|
11
|
+
export { GcraStrategy } from "./algorithms/gcra";
|
|
10
12
|
export { SlidingWindowStrategy } from "./algorithms/sliding-window";
|
|
11
13
|
export { TokenBucketStrategy } from "./algorithms/token-bucket";
|
|
12
14
|
export type { RateLimitStrategy } from "./algorithms/strategy";
|
|
13
|
-
export { DEFAULT_KEY_PREFIX } from "./utils/redis";
|
|
14
|
-
export {
|
|
15
|
+
export { DEFAULT_CLUSTER_OPTIONS, DEFAULT_KEY_PREFIX, buildKey, createRedisClient, isRedisCluster, } from "./utils/redis";
|
|
16
|
+
export { getClusterScriptDefinitions, registerRedisScripts, warmupRedisScripts, } from "./utils/scripts";
|
|
17
|
+
export { DEFAULT_CONCURRENCY, DEFAULT_GCRA, DEFAULT_SLIDING_WINDOW, DEFAULT_TOKEN_BUCKET, resolveAlgorithmConfig, resolveMiddlewareOptions, } from "./utils/defaults";
|
|
15
18
|
export { consumeRateLimit } from "./utils/metrics";
|
|
16
19
|
export type { RateLimitCheckOutcome } from "./utils/metrics";
|
|
20
|
+
export type { RateLimitSpan, RateLimitTracer } from "./observability/types";
|
|
17
21
|
export { createStore, resolveStoreType } from "./stores/factory";
|
|
18
22
|
export { RedisStore } from "./stores/redis-store";
|
|
19
23
|
export { MemcachedStore } from "./stores/memcached-store";
|
|
20
24
|
export type { RateLimitStore, StoreType } from "./stores/types";
|
|
21
|
-
export type { AlgorithmConfig, BaseMiddlewareOptions, MemcachedClient, MemcachedConfig, MemcachedOptions, MiddlewareOptions, MiddlewareOptionsInput, RateLimitHeaders, RateLimitMetricsEvent, RateLimitMetricsHook, RateLimitResult, RedisClient, RedisConfig, RedisLimitOptions, SlidingWindowConfig, TokenBucketConfig, } from "./types";
|
|
25
|
+
export type { AlgorithmConfig, ConcurrencyConfig, GcraConfig, BaseMiddlewareOptions, MemcachedClient, MemcachedConfig, MemcachedOptions, MiddlewareOptions, MiddlewareOptionsInput, RateLimitHeaders, RateLimitMetricsEvent, RateLimitMetricsHook, RateLimitResult, RedisClient, RedisClusterConfig, RedisConfig, RedisLimitOptions, SlidingWindowConfig, TokenBucketConfig, } from "./types";
|
|
22
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,QAAQ,EACR,iBAAiB,EACjB,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,2BAA2B,EAC3B,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChE,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MemcachedStore = exports.RedisStore = exports.resolveStoreType = exports.createStore = exports.consumeRateLimit = exports.resolveMiddlewareOptions = exports.resolveAlgorithmConfig = exports.DEFAULT_TOKEN_BUCKET = exports.DEFAULT_SLIDING_WINDOW = exports.DEFAULT_KEY_PREFIX = exports.TokenBucketStrategy = exports.SlidingWindowStrategy = exports.RATE_LIMIT_KEY = exports.RateLimit = exports.LimitlyModule = exports.limitlyNestModule = exports.createNestGuard = exports.jsonResponse = exports.createBunMiddleware = exports.composeBunHandler = exports.applyRateLimitHeaders = exports.createKoaMiddleware = exports.createHonoMiddleware = exports.redisLimitPlugin = exports.createFastifyPlugin = exports.createExpressMiddleware = exports.createLimiter = exports.RedisLimit = void 0;
|
|
3
|
+
exports.MemcachedStore = exports.RedisStore = exports.resolveStoreType = exports.createStore = exports.consumeRateLimit = exports.resolveMiddlewareOptions = exports.resolveAlgorithmConfig = exports.DEFAULT_TOKEN_BUCKET = exports.DEFAULT_SLIDING_WINDOW = exports.DEFAULT_GCRA = exports.DEFAULT_CONCURRENCY = exports.warmupRedisScripts = exports.registerRedisScripts = exports.getClusterScriptDefinitions = exports.isRedisCluster = exports.createRedisClient = exports.buildKey = exports.DEFAULT_KEY_PREFIX = exports.DEFAULT_CLUSTER_OPTIONS = exports.TokenBucketStrategy = exports.SlidingWindowStrategy = exports.GcraStrategy = exports.ConcurrencyStrategy = exports.RATE_LIMIT_KEY = exports.RateLimit = exports.LimitlyModule = exports.limitlyNestModule = exports.createNestGuard = exports.jsonResponse = exports.createBunMiddleware = exports.composeBunHandler = exports.applyRateLimitHeaders = exports.createKoaMiddleware = exports.createHonoMiddleware = exports.redisLimitPlugin = exports.createFastifyPlugin = exports.createExpressMiddleware = exports.createLimiter = exports.RedisLimit = void 0;
|
|
4
4
|
var limiter_1 = require("./limiter");
|
|
5
5
|
Object.defineProperty(exports, "RedisLimit", { enumerable: true, get: function () { return limiter_1.RedisLimit; } });
|
|
6
6
|
Object.defineProperty(exports, "createLimiter", { enumerable: true, get: function () { return limiter_1.createLimiter; } });
|
|
@@ -24,13 +24,27 @@ Object.defineProperty(exports, "limitlyNestModule", { enumerable: true, get: fun
|
|
|
24
24
|
Object.defineProperty(exports, "LimitlyModule", { enumerable: true, get: function () { return nest_1.LimitlyModule; } });
|
|
25
25
|
Object.defineProperty(exports, "RateLimit", { enumerable: true, get: function () { return nest_1.RateLimit; } });
|
|
26
26
|
Object.defineProperty(exports, "RATE_LIMIT_KEY", { enumerable: true, get: function () { return nest_1.RATE_LIMIT_KEY; } });
|
|
27
|
+
var concurrency_1 = require("./algorithms/concurrency");
|
|
28
|
+
Object.defineProperty(exports, "ConcurrencyStrategy", { enumerable: true, get: function () { return concurrency_1.ConcurrencyStrategy; } });
|
|
29
|
+
var gcra_1 = require("./algorithms/gcra");
|
|
30
|
+
Object.defineProperty(exports, "GcraStrategy", { enumerable: true, get: function () { return gcra_1.GcraStrategy; } });
|
|
27
31
|
var sliding_window_1 = require("./algorithms/sliding-window");
|
|
28
32
|
Object.defineProperty(exports, "SlidingWindowStrategy", { enumerable: true, get: function () { return sliding_window_1.SlidingWindowStrategy; } });
|
|
29
33
|
var token_bucket_1 = require("./algorithms/token-bucket");
|
|
30
34
|
Object.defineProperty(exports, "TokenBucketStrategy", { enumerable: true, get: function () { return token_bucket_1.TokenBucketStrategy; } });
|
|
31
35
|
var redis_1 = require("./utils/redis");
|
|
36
|
+
Object.defineProperty(exports, "DEFAULT_CLUSTER_OPTIONS", { enumerable: true, get: function () { return redis_1.DEFAULT_CLUSTER_OPTIONS; } });
|
|
32
37
|
Object.defineProperty(exports, "DEFAULT_KEY_PREFIX", { enumerable: true, get: function () { return redis_1.DEFAULT_KEY_PREFIX; } });
|
|
38
|
+
Object.defineProperty(exports, "buildKey", { enumerable: true, get: function () { return redis_1.buildKey; } });
|
|
39
|
+
Object.defineProperty(exports, "createRedisClient", { enumerable: true, get: function () { return redis_1.createRedisClient; } });
|
|
40
|
+
Object.defineProperty(exports, "isRedisCluster", { enumerable: true, get: function () { return redis_1.isRedisCluster; } });
|
|
41
|
+
var scripts_1 = require("./utils/scripts");
|
|
42
|
+
Object.defineProperty(exports, "getClusterScriptDefinitions", { enumerable: true, get: function () { return scripts_1.getClusterScriptDefinitions; } });
|
|
43
|
+
Object.defineProperty(exports, "registerRedisScripts", { enumerable: true, get: function () { return scripts_1.registerRedisScripts; } });
|
|
44
|
+
Object.defineProperty(exports, "warmupRedisScripts", { enumerable: true, get: function () { return scripts_1.warmupRedisScripts; } });
|
|
33
45
|
var defaults_1 = require("./utils/defaults");
|
|
46
|
+
Object.defineProperty(exports, "DEFAULT_CONCURRENCY", { enumerable: true, get: function () { return defaults_1.DEFAULT_CONCURRENCY; } });
|
|
47
|
+
Object.defineProperty(exports, "DEFAULT_GCRA", { enumerable: true, get: function () { return defaults_1.DEFAULT_GCRA; } });
|
|
34
48
|
Object.defineProperty(exports, "DEFAULT_SLIDING_WINDOW", { enumerable: true, get: function () { return defaults_1.DEFAULT_SLIDING_WINDOW; } });
|
|
35
49
|
Object.defineProperty(exports, "DEFAULT_TOKEN_BUCKET", { enumerable: true, get: function () { return defaults_1.DEFAULT_TOKEN_BUCKET; } });
|
|
36
50
|
Object.defineProperty(exports, "resolveAlgorithmConfig", { enumerable: true, get: function () { return defaults_1.resolveAlgorithmConfig; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAAsD;AAA7C,qGAAA,UAAU,OAAA;AAAE,wGAAA,aAAa,OAAA;AAClC,gDAA+D;AAAtD,kHAAA,uBAAuB,OAAA;AAChC,gDAA6E;AAApE,8GAAA,mBAAmB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAC9C,0CAAyD;AAAhD,4GAAA,oBAAoB,OAAA;AAC7B,wCAAuD;AAA9C,0GAAA,mBAAmB,OAAA;AAC5B,wCAK0B;AAJxB,4GAAA,qBAAqB,OAAA;AACrB,wGAAA,iBAAiB,OAAA;AACjB,0GAAA,mBAAmB,OAAA;AACnB,mGAAA,YAAY,OAAA;AAGd,0CAM2B;AALzB,uGAAA,eAAe,OAAA;AACf,yGAAA,iBAAiB,OAAA;AACjB,qGAAA,aAAa,OAAA;AACb,iGAAA,SAAS,OAAA;AACT,sGAAA,cAAc,OAAA;AAGhB,8DAAoE;AAA3D,uHAAA,qBAAqB,OAAA;AAC9B,0DAAgE;AAAvD,mHAAA,mBAAmB,OAAA;AAE5B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAAsD;AAA7C,qGAAA,UAAU,OAAA;AAAE,wGAAA,aAAa,OAAA;AAClC,gDAA+D;AAAtD,kHAAA,uBAAuB,OAAA;AAChC,gDAA6E;AAApE,8GAAA,mBAAmB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAC9C,0CAAyD;AAAhD,4GAAA,oBAAoB,OAAA;AAC7B,wCAAuD;AAA9C,0GAAA,mBAAmB,OAAA;AAC5B,wCAK0B;AAJxB,4GAAA,qBAAqB,OAAA;AACrB,wGAAA,iBAAiB,OAAA;AACjB,0GAAA,mBAAmB,OAAA;AACnB,mGAAA,YAAY,OAAA;AAGd,0CAM2B;AALzB,uGAAA,eAAe,OAAA;AACf,yGAAA,iBAAiB,OAAA;AACjB,qGAAA,aAAa,OAAA;AACb,iGAAA,SAAS,OAAA;AACT,sGAAA,cAAc,OAAA;AAGhB,wDAA+D;AAAtD,kHAAA,mBAAmB,OAAA;AAC5B,0CAAiD;AAAxC,oGAAA,YAAY,OAAA;AACrB,8DAAoE;AAA3D,uHAAA,qBAAqB,OAAA;AAC9B,0DAAgE;AAAvD,mHAAA,mBAAmB,OAAA;AAE5B,uCAMuB;AALrB,gHAAA,uBAAuB,OAAA;AACvB,2GAAA,kBAAkB,OAAA;AAClB,iGAAA,QAAQ,OAAA;AACR,0GAAA,iBAAiB,OAAA;AACjB,uGAAA,cAAc,OAAA;AAEhB,2CAIyB;AAHvB,sHAAA,2BAA2B,OAAA;AAC3B,+GAAA,oBAAoB,OAAA;AACpB,6GAAA,kBAAkB,OAAA;AAEpB,6CAO0B;AANxB,+GAAA,mBAAmB,OAAA;AACnB,wGAAA,YAAY,OAAA;AACZ,kHAAA,sBAAsB,OAAA;AACtB,gHAAA,oBAAoB,OAAA;AACpB,kHAAA,sBAAsB,OAAA;AACtB,oHAAA,wBAAwB,OAAA;AAE1B,2CAAmD;AAA1C,2GAAA,gBAAgB,OAAA;AAGzB,4CAAiE;AAAxD,sGAAA,WAAW,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AACtC,oDAAkD;AAAzC,yGAAA,UAAU,OAAA;AACnB,4DAA0D;AAAjD,iHAAA,cAAc,OAAA"}
|
package/dist/limiter.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export declare class RedisLimit {
|
|
|
15
15
|
getMemcached(): MemcachedClient;
|
|
16
16
|
createStrategy(config: AlgorithmConfig): RateLimitStrategy;
|
|
17
17
|
createStrategyFromOptions(options?: MiddlewareOptionsInput): RateLimitStrategy;
|
|
18
|
+
acquire(key: string, config?: MiddlewareOptionsInput, options?: {
|
|
19
|
+
failOpen?: boolean;
|
|
20
|
+
}): Promise<RateLimitResult>;
|
|
21
|
+
release(key: string, slotId: string, config?: MiddlewareOptionsInput): Promise<void>;
|
|
18
22
|
check(key: string, config?: MiddlewareOptionsInput, options?: {
|
|
19
23
|
failOpen?: boolean;
|
|
20
24
|
}): Promise<RateLimitResult>;
|
package/dist/limiter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limiter.d.ts","sourceRoot":"","sources":["../src/limiter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;gBAE5C,OAAO,EAAE,iBAAiB;
|
|
1
|
+
{"version":3,"file":"limiter.d.ts","sourceRoot":"","sources":["../src/limiter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;gBAE5C,OAAO,EAAE,iBAAiB;IAUtC,QAAQ,IAAI,cAAc;IAI1B,YAAY,IAAI,cAAc,CAAC,MAAM,CAAC;IAItC,iBAAiB,IAAI,sBAAsB;IAI3C,cAAc,CAAC,OAAO,GAAE,sBAA2B,GAAG,iBAAiB;IAIvE,QAAQ,IAAI,WAAW;IASvB,YAAY,IAAI,eAAe;IAS/B,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB;IAI1D,yBAAyB,CACvB,OAAO,GAAE,sBAA2B,GACnC,iBAAiB;IAOd,OAAO,CACX,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,sBAA2B,EACnC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/B,OAAO,CAAC,eAAe,CAAC;IAIrB,OAAO,CACX,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,sBAA2B,GAClC,OAAO,CAAC,IAAI,CAAC;IAgBV,KAAK,CACT,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,sBAA2B,EACnC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/B,OAAO,CAAC,eAAe,CAAC;IAwB3B,UAAU,CAAC,OAAO,GAAE,sBAA2B;IAI/C,cAAc,CAAC,OAAO,GAAE,sBAA2B;IAInD,aAAa,CAAC,OAAO,GAAE,sBAA2B;IAIlD,aAAa,CAAC,OAAO,GAAE,sBAA2B;IAIlD,IAAI,aAAa,IAAI,kBAAkB,CAAC,sBAAsB,CAAC,CAE9D;IAED,SAAS,CAAC,cAAc,GAAE,sBAA2B,GAAG,IAAI,CAAC,WAAW,CAAC;IAIzE,OAAO,CAAC,oBAAoB;CAe7B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAEpE;AAED,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC"}
|
package/dist/limiter.js
CHANGED
|
@@ -18,6 +18,7 @@ class RedisLimit {
|
|
|
18
18
|
this.failOpen = options.failOpen ?? true;
|
|
19
19
|
this.defaultOptions = {
|
|
20
20
|
onMetrics: options.onMetrics,
|
|
21
|
+
tracer: options.tracer,
|
|
21
22
|
...options.default,
|
|
22
23
|
};
|
|
23
24
|
}
|
|
@@ -51,9 +52,23 @@ class RedisLimit {
|
|
|
51
52
|
createStrategyFromOptions(options = {}) {
|
|
52
53
|
return (0, factory_1.createStrategy)(this.store, (0, defaults_1.resolveAlgorithmConfig)(options, this.defaultOptions));
|
|
53
54
|
}
|
|
55
|
+
async acquire(key, config = {}, options) {
|
|
56
|
+
return this.check(key, config, options);
|
|
57
|
+
}
|
|
58
|
+
async release(key, slotId, config = {}) {
|
|
59
|
+
const resolved = this.resolveOptions(config);
|
|
60
|
+
if (resolved.algorithm !== "concurrency") {
|
|
61
|
+
throw new Error('release() requires algorithm: "concurrency"');
|
|
62
|
+
}
|
|
63
|
+
const strategy = this.createStrategy((0, defaults_1.resolveAlgorithmConfig)(config, this.defaultOptions));
|
|
64
|
+
if (!strategy.release) {
|
|
65
|
+
throw new Error("Configured strategy does not support release()");
|
|
66
|
+
}
|
|
67
|
+
await strategy.release(key, slotId);
|
|
68
|
+
}
|
|
54
69
|
async check(key, config = {}, options) {
|
|
55
70
|
const resolved = this.resolveOptions(config);
|
|
56
|
-
const strategy = this.createStrategy(
|
|
71
|
+
const strategy = this.createStrategy((0, defaults_1.resolveAlgorithmConfig)(config, this.defaultOptions));
|
|
57
72
|
const shouldFailOpen = options?.failOpen ?? this.failOpen;
|
|
58
73
|
const outcome = await (0, metrics_1.consumeRateLimit)({
|
|
59
74
|
strategy,
|
|
@@ -89,7 +104,11 @@ class RedisLimit {
|
|
|
89
104
|
return (0, nest_1.createNestGuard)(this)(defaultOptions);
|
|
90
105
|
}
|
|
91
106
|
createFailOpenResult(config) {
|
|
92
|
-
const limit = config.algorithm === "sliding-window"
|
|
107
|
+
const limit = config.algorithm === "sliding-window" ||
|
|
108
|
+
config.algorithm === "concurrency" ||
|
|
109
|
+
config.algorithm === "gcra"
|
|
110
|
+
? config.limit
|
|
111
|
+
: config.capacity;
|
|
93
112
|
return {
|
|
94
113
|
allowed: true,
|
|
95
114
|
limit,
|
package/dist/limiter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limiter.js","sourceRoot":"","sources":["../src/limiter.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"limiter.js","sourceRoot":"","sources":["../src/limiter.ts"],"names":[],"mappings":"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"}
|