limitly 1.0.2 → 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.
Files changed (118) hide show
  1. package/README.md +471 -22
  2. package/dist/algorithms/concurrency.d.ts +11 -0
  3. package/dist/algorithms/concurrency.d.ts.map +1 -0
  4. package/dist/algorithms/concurrency.js +19 -0
  5. package/dist/algorithms/concurrency.js.map +1 -0
  6. package/dist/algorithms/factory.d.ts +3 -2
  7. package/dist/algorithms/factory.d.ts.map +1 -1
  8. package/dist/algorithms/factory.js +9 -3
  9. package/dist/algorithms/factory.js.map +1 -1
  10. package/dist/algorithms/gcra.d.ts +10 -0
  11. package/dist/algorithms/gcra.d.ts.map +1 -0
  12. package/dist/algorithms/gcra.js +15 -0
  13. package/dist/algorithms/gcra.js.map +1 -0
  14. package/dist/algorithms/sliding-window.d.ts +4 -4
  15. package/dist/algorithms/sliding-window.d.ts.map +1 -1
  16. package/dist/algorithms/sliding-window.js +3 -23
  17. package/dist/algorithms/sliding-window.js.map +1 -1
  18. package/dist/algorithms/token-bucket.d.ts +4 -4
  19. package/dist/algorithms/token-bucket.d.ts.map +1 -1
  20. package/dist/algorithms/token-bucket.js +3 -20
  21. package/dist/algorithms/token-bucket.js.map +1 -1
  22. package/dist/index.d.ts +19 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +46 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/limiter.d.ts +23 -7
  27. package/dist/limiter.d.ts.map +1 -1
  28. package/dist/limiter.js +86 -16
  29. package/dist/limiter.js.map +1 -1
  30. package/dist/middleware/bun.d.ts +9 -0
  31. package/dist/middleware/bun.d.ts.map +1 -0
  32. package/dist/middleware/bun.js +107 -0
  33. package/dist/middleware/bun.js.map +1 -0
  34. package/dist/middleware/express.d.ts.map +1 -1
  35. package/dist/middleware/express.js +30 -14
  36. package/dist/middleware/express.js.map +1 -1
  37. package/dist/middleware/fastify.d.ts +3 -3
  38. package/dist/middleware/fastify.d.ts.map +1 -1
  39. package/dist/middleware/fastify.js +34 -17
  40. package/dist/middleware/fastify.js.map +1 -1
  41. package/dist/middleware/hono.d.ts +5 -0
  42. package/dist/middleware/hono.d.ts.map +1 -0
  43. package/dist/middleware/hono.js +71 -0
  44. package/dist/middleware/hono.js.map +1 -0
  45. package/dist/middleware/koa.d.ts +5 -0
  46. package/dist/middleware/koa.d.ts.map +1 -0
  47. package/dist/middleware/koa.js +65 -0
  48. package/dist/middleware/koa.js.map +1 -0
  49. package/dist/middleware/nest.d.ts +14 -0
  50. package/dist/middleware/nest.d.ts.map +1 -0
  51. package/dist/middleware/nest.js +112 -0
  52. package/dist/middleware/nest.js.map +1 -0
  53. package/dist/observability/index.d.ts +4 -0
  54. package/dist/observability/index.d.ts.map +1 -0
  55. package/dist/observability/index.js +10 -0
  56. package/dist/observability/index.js.map +1 -0
  57. package/dist/observability/opentelemetry.d.ts +28 -0
  58. package/dist/observability/opentelemetry.d.ts.map +1 -0
  59. package/dist/observability/opentelemetry.js +85 -0
  60. package/dist/observability/opentelemetry.js.map +1 -0
  61. package/dist/observability/prometheus.d.ts +27 -0
  62. package/dist/observability/prometheus.d.ts.map +1 -0
  63. package/dist/observability/prometheus.js +88 -0
  64. package/dist/observability/prometheus.js.map +1 -0
  65. package/dist/observability/types.d.ts +9 -0
  66. package/dist/observability/types.d.ts.map +1 -0
  67. package/dist/observability/types.js +3 -0
  68. package/dist/observability/types.js.map +1 -0
  69. package/dist/prometheus.d.ts +3 -0
  70. package/dist/prometheus.d.ts.map +1 -0
  71. package/dist/prometheus.js +9 -0
  72. package/dist/prometheus.js.map +1 -0
  73. package/dist/scripts/concurrencyAcquire.lua +34 -0
  74. package/dist/scripts/concurrencyRelease.lua +9 -0
  75. package/dist/scripts/gcra.lua +38 -0
  76. package/dist/stores/factory.d.ts +5 -0
  77. package/dist/stores/factory.d.ts.map +1 -0
  78. package/dist/stores/factory.js +40 -0
  79. package/dist/stores/factory.js.map +1 -0
  80. package/dist/stores/memcached-store.d.ts +16 -0
  81. package/dist/stores/memcached-store.d.ts.map +1 -0
  82. package/dist/stores/memcached-store.js +211 -0
  83. package/dist/stores/memcached-store.js.map +1 -0
  84. package/dist/stores/redis-store.d.ts +19 -0
  85. package/dist/stores/redis-store.d.ts.map +1 -0
  86. package/dist/stores/redis-store.js +97 -0
  87. package/dist/stores/redis-store.js.map +1 -0
  88. package/dist/stores/types.d.ts +11 -0
  89. package/dist/stores/types.d.ts.map +1 -0
  90. package/dist/stores/types.js +3 -0
  91. package/dist/stores/types.js.map +1 -0
  92. package/dist/types/index.d.ts +94 -5
  93. package/dist/types/index.d.ts.map +1 -1
  94. package/dist/utils/defaults.d.ts +8 -0
  95. package/dist/utils/defaults.d.ts.map +1 -0
  96. package/dist/utils/defaults.js +100 -0
  97. package/dist/utils/defaults.js.map +1 -0
  98. package/dist/utils/limit-execution.d.ts +40 -0
  99. package/dist/utils/limit-execution.d.ts.map +1 -0
  100. package/dist/utils/limit-execution.js +50 -0
  101. package/dist/utils/limit-execution.js.map +1 -0
  102. package/dist/utils/memcached.d.ts +12 -0
  103. package/dist/utils/memcached.d.ts.map +1 -0
  104. package/dist/utils/memcached.js +103 -0
  105. package/dist/utils/memcached.js.map +1 -0
  106. package/dist/utils/metrics.d.ts +21 -0
  107. package/dist/utils/metrics.d.ts.map +1 -0
  108. package/dist/utils/metrics.js +99 -0
  109. package/dist/utils/metrics.js.map +1 -0
  110. package/dist/utils/redis.d.ts +5 -1
  111. package/dist/utils/redis.d.ts.map +1 -1
  112. package/dist/utils/redis.js +38 -3
  113. package/dist/utils/redis.js.map +1 -1
  114. package/dist/utils/scripts.d.ts +16 -2
  115. package/dist/utils/scripts.d.ts.map +1 -1
  116. package/dist/utils/scripts.js +79 -33
  117. package/dist/utils/scripts.js.map +1 -1
  118. package/package.json +86 -3
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_TRACER_NAME = exports.DEFAULT_METER_NAME = void 0;
4
+ exports.createOpenTelemetryMetricsHook = createOpenTelemetryMetricsHook;
5
+ exports.createOpenTelemetryTracer = createOpenTelemetryTracer;
6
+ exports.createOpenTelemetryInstrumentation = createOpenTelemetryInstrumentation;
7
+ const api_1 = require("@opentelemetry/api");
8
+ exports.DEFAULT_METER_NAME = "limitly";
9
+ exports.DEFAULT_TRACER_NAME = "limitly";
10
+ const instrumentCache = new WeakMap();
11
+ function getMetricInstruments(meter, meterName) {
12
+ const cached = instrumentCache.get(meter);
13
+ if (cached) {
14
+ return cached;
15
+ }
16
+ const instruments = {
17
+ checksTotal: meter.createCounter(`${meterName}.check.total`, {
18
+ description: "Total number of rate limit checks",
19
+ }),
20
+ checkDuration: meter.createHistogram(`${meterName}.check.duration`, {
21
+ description: "Duration of rate limit checks",
22
+ unit: "ms",
23
+ }),
24
+ };
25
+ instrumentCache.set(meter, instruments);
26
+ return instruments;
27
+ }
28
+ function getBaseMetricAttributes(event, includeKey) {
29
+ const attributes = {
30
+ "limitly.algorithm": event.algorithm,
31
+ "limitly.outcome": event.type,
32
+ };
33
+ if (event.store) {
34
+ attributes["limitly.store"] = event.store;
35
+ }
36
+ if (includeKey) {
37
+ attributes["limitly.key"] = event.key;
38
+ }
39
+ return attributes;
40
+ }
41
+ function createOpenTelemetryMetricsHook(options = {}) {
42
+ const meterName = options.meterName ?? exports.DEFAULT_METER_NAME;
43
+ const meter = options.meter ?? api_1.metrics.getMeter(meterName);
44
+ const includeKey = options.includeKey ?? false;
45
+ const instruments = getMetricInstruments(meter, meterName);
46
+ return (event) => {
47
+ const attributes = getBaseMetricAttributes(event, includeKey);
48
+ instruments.checksTotal.add(1, attributes);
49
+ instruments.checkDuration.record(event.durationMs, attributes);
50
+ };
51
+ }
52
+ function createOpenTelemetryTracer(options = {}) {
53
+ const tracerName = options.tracerName ?? exports.DEFAULT_TRACER_NAME;
54
+ const otelTracer = options.tracer ?? api_1.trace.getTracer(tracerName);
55
+ return {
56
+ startSpan(name, attributes) {
57
+ const span = otelTracer.startSpan(name, { attributes }, api_1.context.active());
58
+ return {
59
+ setAttribute(key, value) {
60
+ span.setAttribute(key, value);
61
+ },
62
+ setStatus(ok, message) {
63
+ if (ok) {
64
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
65
+ return;
66
+ }
67
+ span.setStatus({
68
+ code: api_1.SpanStatusCode.ERROR,
69
+ message,
70
+ });
71
+ },
72
+ end() {
73
+ span.end();
74
+ },
75
+ };
76
+ },
77
+ };
78
+ }
79
+ function createOpenTelemetryInstrumentation(options = {}) {
80
+ return {
81
+ tracer: createOpenTelemetryTracer(options),
82
+ onMetrics: createOpenTelemetryMetricsHook(options),
83
+ };
84
+ }
85
+ //# sourceMappingURL=opentelemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opentelemetry.js","sourceRoot":"","sources":["../../src/observability/opentelemetry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { Registry, type RegistryContentType } from "prom-client";
2
+ import type { RateLimitMetricsHook } from "../types";
3
+ export declare const DEFAULT_PROMETHEUS_PREFIX = "limitly";
4
+ export interface PrometheusExporterOptions {
5
+ /** Prometheus registry. Defaults to a new `Registry`. */
6
+ register?: Registry;
7
+ /** Metric name prefix. Defaults to `"limitly"`. */
8
+ prefix?: string;
9
+ /** Include the rate limit key as a label. Defaults to `false` (high cardinality). */
10
+ includeKey?: boolean;
11
+ /** Histogram buckets for check duration in seconds. */
12
+ durationBuckets?: number[];
13
+ }
14
+ export interface PrometheusExporter {
15
+ register: Registry;
16
+ onMetrics: RateLimitMetricsHook;
17
+ contentType: RegistryContentType;
18
+ getMetrics: () => Promise<string>;
19
+ }
20
+ export declare function createPrometheusMetricsHook(options?: PrometheusExporterOptions): RateLimitMetricsHook;
21
+ export declare function createPrometheusExporter(options?: PrometheusExporterOptions): PrometheusExporter;
22
+ export type PrometheusHandler = (req: unknown, res: {
23
+ setHeader(name: string, value: string): void;
24
+ end(body: string): void;
25
+ }) => Promise<void>;
26
+ export declare function createPrometheusHandler(exporter: PrometheusExporter): PrometheusHandler;
27
+ //# sourceMappingURL=prometheus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.d.ts","sourceRoot":"","sources":["../../src/observability/prometheus.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,QAAQ,EACR,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAyB,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAE5E,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD,MAAM,WAAW,yBAAyB;IACxC,yDAAyD;IACzD,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,oBAAoB,CAAC;IAChC,WAAW,EAAE,mBAAmB,CAAC;IACjC,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CACnC;AA4ED,wBAAgB,2BAA2B,CACzC,OAAO,GAAE,yBAA8B,GACtC,oBAAoB,CAYtB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,yBAA8B,GACtC,kBAAkB,CAUpB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,KACE,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,kBAAkB,GAC3B,iBAAiB,CAMnB"}
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_PROMETHEUS_PREFIX = void 0;
4
+ exports.createPrometheusMetricsHook = createPrometheusMetricsHook;
5
+ exports.createPrometheusExporter = createPrometheusExporter;
6
+ exports.createPrometheusHandler = createPrometheusHandler;
7
+ const prom_client_1 = require("prom-client");
8
+ exports.DEFAULT_PROMETHEUS_PREFIX = "limitly";
9
+ const metricCache = new WeakMap();
10
+ const DEFAULT_DURATION_BUCKETS = [
11
+ 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1,
12
+ ];
13
+ function getMetricSet(register, prefix, durationBuckets, includeKey) {
14
+ const cacheKey = `${prefix}:${includeKey ? "key" : "nokey"}`;
15
+ let byCacheKey = metricCache.get(register);
16
+ if (!byCacheKey) {
17
+ byCacheKey = new Map();
18
+ metricCache.set(register, byCacheKey);
19
+ }
20
+ const cached = byCacheKey.get(cacheKey);
21
+ if (cached) {
22
+ return cached;
23
+ }
24
+ const labelNames = includeKey
25
+ ? ["algorithm", "outcome", "store", "key"]
26
+ : ["algorithm", "outcome", "store"];
27
+ const metrics = {
28
+ checksTotal: new prom_client_1.Counter({
29
+ name: `${prefix}_check_total`,
30
+ help: "Total number of rate limit checks",
31
+ labelNames: [...labelNames],
32
+ registers: [register],
33
+ }),
34
+ checkDuration: new prom_client_1.Histogram({
35
+ name: `${prefix}_check_duration_seconds`,
36
+ help: "Duration of rate limit checks in seconds",
37
+ labelNames: [...labelNames],
38
+ buckets: durationBuckets,
39
+ registers: [register],
40
+ }),
41
+ };
42
+ byCacheKey.set(cacheKey, metrics);
43
+ return metrics;
44
+ }
45
+ function sanitizeLabel(value) {
46
+ return value.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 128);
47
+ }
48
+ function getLabels(event, includeKey) {
49
+ const labels = {
50
+ algorithm: event.algorithm,
51
+ outcome: event.type,
52
+ store: event.store ?? "unknown",
53
+ };
54
+ if (includeKey) {
55
+ labels.key = sanitizeLabel(event.key);
56
+ }
57
+ return labels;
58
+ }
59
+ function createPrometheusMetricsHook(options = {}) {
60
+ const register = options.register ?? new prom_client_1.Registry();
61
+ const prefix = options.prefix ?? exports.DEFAULT_PROMETHEUS_PREFIX;
62
+ const includeKey = options.includeKey ?? false;
63
+ const durationBuckets = options.durationBuckets ?? DEFAULT_DURATION_BUCKETS;
64
+ const metrics = getMetricSet(register, prefix, durationBuckets, includeKey);
65
+ return (event) => {
66
+ const labels = getLabels(event, includeKey);
67
+ metrics.checksTotal.inc(labels);
68
+ metrics.checkDuration.observe(labels, event.durationMs / 1000);
69
+ };
70
+ }
71
+ function createPrometheusExporter(options = {}) {
72
+ const register = options.register ?? new prom_client_1.Registry();
73
+ const onMetrics = createPrometheusMetricsHook({ ...options, register });
74
+ return {
75
+ register,
76
+ onMetrics,
77
+ contentType: register.contentType,
78
+ getMetrics: () => register.metrics(),
79
+ };
80
+ }
81
+ function createPrometheusHandler(exporter) {
82
+ return async (_req, res) => {
83
+ const body = await exporter.getMetrics();
84
+ res.setHeader("Content-Type", exporter.contentType);
85
+ res.end(body);
86
+ };
87
+ }
88
+ //# sourceMappingURL=prometheus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.js","sourceRoot":"","sources":["../../src/observability/prometheus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface RateLimitSpan {
2
+ setAttribute(key: string, value: string | number | boolean): void;
3
+ setStatus(ok: boolean, message?: string): void;
4
+ end(): void;
5
+ }
6
+ export interface RateLimitTracer {
7
+ startSpan(name: string, attributes?: Record<string, string | number | boolean>): RateLimitSpan;
8
+ }
9
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/observability/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IAClE,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,GAAG,IAAI,IAAI,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GACrD,aAAa,CAAC;CAClB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/observability/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export { createPrometheusExporter, createPrometheusHandler, createPrometheusMetricsHook, DEFAULT_PROMETHEUS_PREFIX, } from "./observability/prometheus";
2
+ export type { PrometheusExporter, PrometheusExporterOptions, PrometheusHandler, } from "./observability/prometheus";
3
+ //# sourceMappingURL=prometheus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.d.ts","sourceRoot":"","sources":["../src/prometheus.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_PROMETHEUS_PREFIX = exports.createPrometheusMetricsHook = exports.createPrometheusHandler = exports.createPrometheusExporter = void 0;
4
+ var prometheus_1 = require("./observability/prometheus");
5
+ Object.defineProperty(exports, "createPrometheusExporter", { enumerable: true, get: function () { return prometheus_1.createPrometheusExporter; } });
6
+ Object.defineProperty(exports, "createPrometheusHandler", { enumerable: true, get: function () { return prometheus_1.createPrometheusHandler; } });
7
+ Object.defineProperty(exports, "createPrometheusMetricsHook", { enumerable: true, get: function () { return prometheus_1.createPrometheusMetricsHook; } });
8
+ Object.defineProperty(exports, "DEFAULT_PROMETHEUS_PREFIX", { enumerable: true, get: function () { return prometheus_1.DEFAULT_PROMETHEUS_PREFIX; } });
9
+ //# sourceMappingURL=prometheus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.js","sourceRoot":"","sources":["../src/prometheus.ts"],"names":[],"mappings":";;;AAAA,yDAKoC;AAJlC,sHAAA,wBAAwB,OAAA;AACxB,qHAAA,uBAAuB,OAAA;AACvB,yHAAA,2BAA2B,OAAA;AAC3B,uHAAA,yBAAyB,OAAA"}
@@ -0,0 +1,34 @@
1
+ -- Concurrency limiter using a lease-based sorted set
2
+ -- KEYS[1] = concurrency key
3
+ -- ARGV[1] = limit
4
+ -- ARGV[2] = ttl (seconds)
5
+ -- ARGV[3] = slot id
6
+ -- ARGV[4] = current timestamp (milliseconds)
7
+
8
+ local key = KEYS[1]
9
+ local limit = tonumber(ARGV[1])
10
+ local ttl = tonumber(ARGV[2])
11
+ local slot_id = ARGV[3]
12
+ local now = tonumber(ARGV[4])
13
+ local expires_at = now + (ttl * 1000)
14
+
15
+ redis.call('ZREMRANGEBYSCORE', key, '-inf', now)
16
+ local current = redis.call('ZCARD', key)
17
+
18
+ if current < limit then
19
+ redis.call('ZADD', key, expires_at, slot_id)
20
+ redis.call('PEXPIRE', key, ttl * 1000)
21
+ local remaining = limit - current - 1
22
+ local reset = math.ceil(expires_at / 1000)
23
+ return {1, limit, remaining, reset, 0, slot_id}
24
+ else
25
+ local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')
26
+ local reset
27
+ if #oldest > 0 then
28
+ reset = math.ceil(tonumber(oldest[2]) / 1000)
29
+ else
30
+ reset = math.ceil(expires_at / 1000)
31
+ end
32
+ local retry_after = math.max(1, reset - math.ceil(now / 1000))
33
+ return {0, limit, 0, reset, retry_after, ''}
34
+ end
@@ -0,0 +1,9 @@
1
+ -- Release a concurrency slot
2
+ -- KEYS[1] = concurrency key
3
+ -- ARGV[1] = slot id
4
+
5
+ local key = KEYS[1]
6
+ local slot_id = ARGV[1]
7
+
8
+ redis.call('ZREM', key, slot_id)
9
+ return {1}
@@ -0,0 +1,38 @@
1
+ -- GCRA (Generic Cell Rate Algorithm) rate limiter
2
+ -- KEYS[1] = rate limit key
3
+ -- ARGV[1] = limit
4
+ -- ARGV[2] = window (seconds)
5
+ -- ARGV[3] = current timestamp (milliseconds)
6
+
7
+ local key = KEYS[1]
8
+ local limit = tonumber(ARGV[1])
9
+ local window = tonumber(ARGV[2])
10
+ local now = tonumber(ARGV[3])
11
+
12
+ local emission_interval = (window * 1000) / limit
13
+ local burst_tolerance = window * 1000
14
+
15
+ local tat_raw = redis.call('GET', key)
16
+ local tat = tat_raw and tonumber(tat_raw) or now
17
+ local earliest = tat - burst_tolerance
18
+
19
+ if now < earliest then
20
+ local retry_after = math.max(1, math.ceil((earliest - now) / 1000))
21
+ local reset = math.ceil(tat / 1000)
22
+ return {0, limit, 0, reset, retry_after}
23
+ end
24
+
25
+ local new_tat = math.max(tat, now) + emission_interval
26
+ local ttl_ms = math.ceil(burst_tolerance + emission_interval)
27
+ redis.call('SET', key, new_tat, 'PX', ttl_ms)
28
+
29
+ local remaining = math.floor((new_tat - now + burst_tolerance - emission_interval) / emission_interval)
30
+ if remaining < 0 then
31
+ remaining = 0
32
+ end
33
+ if remaining > limit - 1 then
34
+ remaining = limit - 1
35
+ end
36
+
37
+ local reset = math.ceil(new_tat / 1000)
38
+ return {1, limit, remaining, reset, 0}
@@ -0,0 +1,5 @@
1
+ import type { RedisLimitOptions } from "../types";
2
+ import type { RateLimitStore, StoreType } from "./types";
3
+ export declare function resolveStoreType(options: RedisLimitOptions): StoreType;
4
+ export declare function createStore(options: RedisLimitOptions): RateLimitStore;
5
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/stores/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKlD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAIzD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAQtE;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAgCtE"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveStoreType = resolveStoreType;
4
+ exports.createStore = createStore;
5
+ const memcached_1 = require("../utils/memcached");
6
+ const redis_1 = require("../utils/redis");
7
+ const memcached_store_1 = require("./memcached-store");
8
+ const redis_store_1 = require("./redis-store");
9
+ const REDIS_COMPATIBLE_STORES = ["redis", "valkey", "dragonfly"];
10
+ function resolveStoreType(options) {
11
+ if (options.store) {
12
+ return options.store;
13
+ }
14
+ if (options.memcached) {
15
+ return "memcached";
16
+ }
17
+ return "redis";
18
+ }
19
+ function createStore(options) {
20
+ const storeType = resolveStoreType(options);
21
+ const keyPrefix = options.keyPrefix ?? redis_1.DEFAULT_KEY_PREFIX;
22
+ if (storeType === "memcached") {
23
+ if (!options.memcached) {
24
+ throw new Error('Memcached configuration is required when store is "memcached"');
25
+ }
26
+ return new memcached_store_1.MemcachedStore((0, memcached_1.createMemcachedClient)(options.memcached), keyPrefix);
27
+ }
28
+ if (!options.redis) {
29
+ throw new Error(`Redis configuration is required when store is "${storeType}"`);
30
+ }
31
+ if (!REDIS_COMPATIBLE_STORES.includes(storeType)) {
32
+ throw new Error(`Unsupported store type: ${storeType}`);
33
+ }
34
+ const client = (0, redis_1.createRedisClient)(options.redis);
35
+ return new redis_store_1.RedisStore(client, keyPrefix, storeType, {
36
+ hashTag: options.hashTag,
37
+ warmupScripts: options.warmupScripts,
38
+ });
39
+ }
40
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/stores/factory.ts"],"names":[],"mappings":";;AASA,4CAQC;AAED,kCAgCC;AAlDD,kDAA2D;AAC3D,0CAAuE;AACvE,uDAAmD;AACnD,+CAA2C;AAG3C,MAAM,uBAAuB,GAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AAE9E,SAAgB,gBAAgB,CAAC,OAA0B;IACzD,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,KAAK,CAAC;IACvB,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAgB,WAAW,CAAC,OAA0B;IACpD,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,0BAAkB,CAAC;IAE1D,IAAI,SAAS,KAAK,WAAW,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,+DAA+D,CAChE,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,gCAAc,CACvB,IAAA,iCAAqB,EAAC,OAAO,CAAC,SAAS,CAAC,EACxC,SAAS,CACV,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,kDAAkD,SAAS,GAAG,CAC/D,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,2BAA2B,SAAS,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,yBAAiB,EAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAEhD,OAAO,IAAI,wBAAU,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE;QAClD,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { RateLimitResult } from "../types";
2
+ import type { MemcachedClient } from "../types";
3
+ import type { RateLimitStore } from "./types";
4
+ export declare class MemcachedStore implements RateLimitStore {
5
+ readonly type: "memcached";
6
+ private readonly client;
7
+ private readonly keyPrefix;
8
+ constructor(client: MemcachedClient, keyPrefix: string);
9
+ getClient(): MemcachedClient;
10
+ slidingWindow(key: string, limit: number, window: number): Promise<RateLimitResult>;
11
+ tokenBucket(key: string, capacity: number, refillRate: number): Promise<RateLimitResult>;
12
+ gcra(key: string, limit: number, window: number): Promise<RateLimitResult>;
13
+ concurrencyAcquire(key: string, limit: number, ttl: number): Promise<RateLimitResult>;
14
+ concurrencyRelease(key: string, _slotId: string): Promise<void>;
15
+ }
16
+ //# sourceMappingURL=memcached-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memcached-store.d.ts","sourceRoot":"","sources":["../../src/stores/memcached-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAUhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAI9C,qBAAa,cAAe,YAAW,cAAc;IACnD,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM;IAKtD,SAAS,IAAI,eAAe;IAItB,aAAa,CACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC;IA4CrB,WAAW,CACf,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,CAAC;IAmGrB,IAAI,CACR,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC;IAoErB,kBAAkB,CACtB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,eAAe,CAAC;IA4BrB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA2BtE"}
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemcachedStore = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const redis_1 = require("../utils/redis");
6
+ const memcached_1 = require("../utils/memcached");
7
+ const CAS_RETRIES = 5;
8
+ class MemcachedStore {
9
+ constructor(client, keyPrefix) {
10
+ this.type = "memcached";
11
+ this.client = client;
12
+ this.keyPrefix = keyPrefix;
13
+ }
14
+ getClient() {
15
+ return this.client;
16
+ }
17
+ async slidingWindow(key, limit, window) {
18
+ const baseKey = (0, redis_1.buildKey)(this.keyPrefix, `sw:${key}`);
19
+ const now = Date.now();
20
+ const windowMs = window * 1000;
21
+ const currentWindow = Math.floor(now / windowMs);
22
+ const previousWindow = currentWindow - 1;
23
+ const elapsedRatio = (now % windowMs) / windowMs;
24
+ const currentKey = `${baseKey}:${currentWindow}`;
25
+ const previousKey = `${baseKey}:${previousWindow}`;
26
+ await (0, memcached_1.memcachedAdd)(this.client, currentKey, "0", window * 2);
27
+ const currentCount = await (0, memcached_1.memcachedIncr)(this.client, currentKey, 1);
28
+ const previousRaw = await (0, memcached_1.memcachedGet)(this.client, previousKey);
29
+ const previousCount = previousRaw ? Number(previousRaw) : 0;
30
+ const weightedCount = previousCount * (1 - elapsedRatio) + currentCount;
31
+ const reset = Math.ceil((now + windowMs) / 1000);
32
+ if (weightedCount <= limit) {
33
+ return {
34
+ allowed: true,
35
+ limit,
36
+ remaining: Math.max(0, Math.floor(limit - weightedCount)),
37
+ reset,
38
+ };
39
+ }
40
+ const retryAfter = Math.max(1, Math.ceil((1 - elapsedRatio) * window));
41
+ return {
42
+ allowed: false,
43
+ limit,
44
+ remaining: 0,
45
+ reset,
46
+ retryAfter,
47
+ };
48
+ }
49
+ async tokenBucket(key, capacity, refillRate) {
50
+ const cacheKey = (0, redis_1.buildKey)(this.keyPrefix, `tb:${key}`);
51
+ const now = Date.now();
52
+ const ttl = Math.ceil(capacity / refillRate) + 1;
53
+ for (let attempt = 0; attempt < CAS_RETRIES; attempt++) {
54
+ const existing = await (0, memcached_1.memcachedGets)(this.client, cacheKey);
55
+ let tokens;
56
+ let lastRefill;
57
+ if (!existing) {
58
+ tokens = capacity;
59
+ lastRefill = now;
60
+ }
61
+ else {
62
+ const parsed = JSON.parse(existing.value);
63
+ tokens = parsed.tokens;
64
+ lastRefill = parsed.lastRefill;
65
+ }
66
+ const elapsed = (now - lastRefill) / 1000;
67
+ tokens = Math.min(capacity, tokens + elapsed * refillRate);
68
+ lastRefill = now;
69
+ if (tokens < 1) {
70
+ const retryAfter = Math.max(1, Math.ceil((1 - tokens) / refillRate));
71
+ const reset = Math.ceil(now / 1000) + retryAfter;
72
+ if (!existing) {
73
+ await (0, memcached_1.memcachedAdd)(this.client, cacheKey, JSON.stringify({ tokens, lastRefill }), ttl);
74
+ }
75
+ else {
76
+ await (0, memcached_1.memcachedCas)(this.client, cacheKey, JSON.stringify({ tokens, lastRefill }), existing.cas, ttl);
77
+ }
78
+ return {
79
+ allowed: false,
80
+ limit: capacity,
81
+ remaining: 0,
82
+ reset,
83
+ retryAfter,
84
+ };
85
+ }
86
+ tokens -= 1;
87
+ const payload = JSON.stringify({ tokens, lastRefill });
88
+ if (!existing) {
89
+ try {
90
+ await (0, memcached_1.memcachedAdd)(this.client, cacheKey, payload, ttl);
91
+ return {
92
+ allowed: true,
93
+ limit: capacity,
94
+ remaining: Math.floor(tokens),
95
+ reset: Math.ceil(now / 1000) +
96
+ Math.ceil((capacity - tokens) / refillRate),
97
+ };
98
+ }
99
+ catch {
100
+ continue;
101
+ }
102
+ }
103
+ const updated = await (0, memcached_1.memcachedCas)(this.client, cacheKey, payload, existing.cas, ttl);
104
+ if (updated) {
105
+ return {
106
+ allowed: true,
107
+ limit: capacity,
108
+ remaining: Math.floor(tokens),
109
+ reset: Math.ceil(now / 1000) +
110
+ Math.ceil((capacity - tokens) / refillRate),
111
+ };
112
+ }
113
+ }
114
+ throw new Error("Token bucket CAS retries exhausted");
115
+ }
116
+ async gcra(key, limit, window) {
117
+ const cacheKey = (0, redis_1.buildKey)(this.keyPrefix, `gcra:${key}`);
118
+ const now = Date.now();
119
+ const emissionInterval = (window * 1000) / limit;
120
+ const burstTolerance = window * 1000;
121
+ const ttl = Math.ceil(window) + 1;
122
+ for (let attempt = 0; attempt < CAS_RETRIES; attempt++) {
123
+ const existing = await (0, memcached_1.memcachedGets)(this.client, cacheKey);
124
+ const tat = existing ? Number(existing.value) : now;
125
+ const earliest = tat - burstTolerance;
126
+ if (now < earliest) {
127
+ const retryAfter = Math.max(1, Math.ceil((earliest - now) / 1000));
128
+ const reset = Math.ceil(tat / 1000);
129
+ return {
130
+ allowed: false,
131
+ limit,
132
+ remaining: 0,
133
+ reset,
134
+ retryAfter,
135
+ };
136
+ }
137
+ const newTat = Math.max(tat, now) + emissionInterval;
138
+ let remaining = Math.floor((newTat - now + burstTolerance - emissionInterval) / emissionInterval);
139
+ remaining = Math.max(0, Math.min(limit - 1, remaining));
140
+ const reset = Math.ceil(newTat / 1000);
141
+ const payload = String(newTat);
142
+ if (!existing) {
143
+ try {
144
+ await (0, memcached_1.memcachedAdd)(this.client, cacheKey, payload, ttl);
145
+ return {
146
+ allowed: true,
147
+ limit,
148
+ remaining,
149
+ reset,
150
+ };
151
+ }
152
+ catch {
153
+ continue;
154
+ }
155
+ }
156
+ const updated = await (0, memcached_1.memcachedCas)(this.client, cacheKey, payload, existing.cas, ttl);
157
+ if (updated) {
158
+ return {
159
+ allowed: true,
160
+ limit,
161
+ remaining,
162
+ reset,
163
+ };
164
+ }
165
+ }
166
+ throw new Error("GCRA CAS retries exhausted");
167
+ }
168
+ async concurrencyAcquire(key, limit, ttl) {
169
+ const counterKey = (0, redis_1.buildKey)(this.keyPrefix, `cc:${key}`);
170
+ const slotId = (0, crypto_1.randomUUID)();
171
+ await (0, memcached_1.memcachedAdd)(this.client, counterKey, "0", ttl);
172
+ const count = await (0, memcached_1.memcachedIncr)(this.client, counterKey, 1);
173
+ const reset = Math.ceil(Date.now() / 1000) + ttl;
174
+ if (count > limit) {
175
+ await (0, memcached_1.memcachedDecr)(this.client, counterKey, 1);
176
+ return {
177
+ allowed: false,
178
+ limit,
179
+ remaining: 0,
180
+ reset,
181
+ retryAfter: ttl,
182
+ };
183
+ }
184
+ return {
185
+ allowed: true,
186
+ limit,
187
+ remaining: Math.max(0, limit - count),
188
+ reset,
189
+ slotId,
190
+ };
191
+ }
192
+ async concurrencyRelease(key, _slotId) {
193
+ const counterKey = (0, redis_1.buildKey)(this.keyPrefix, `cc:${key}`);
194
+ for (let attempt = 0; attempt < CAS_RETRIES; attempt++) {
195
+ const current = await (0, memcached_1.memcachedGets)(this.client, counterKey);
196
+ if (!current) {
197
+ return;
198
+ }
199
+ const value = Number(current.value);
200
+ if (value <= 0) {
201
+ return;
202
+ }
203
+ const updated = await (0, memcached_1.memcachedCas)(this.client, counterKey, String(value - 1), current.cas, 300);
204
+ if (updated) {
205
+ return;
206
+ }
207
+ }
208
+ }
209
+ }
210
+ exports.MemcachedStore = MemcachedStore;
211
+ //# sourceMappingURL=memcached-store.js.map