@joint-ops/hitlimit 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  - **Blazing Fast** - 400,000+ ops/sec with memory store, ~7% HTTP overhead
18
18
  - **Zero Config** - Works out of the box with sensible defaults
19
- - **Tiny Footprint** - Only ~5KB minified, no bloat
19
+ - **Tiny Footprint** - Only ~7KB core, zero runtime dependencies
20
20
  - **Framework Agnostic** - Express, NestJS, Fastify, native HTTP
21
21
  - **Multiple Stores** - Memory, Redis, SQLite for distributed systems
22
22
  - **TypeScript First** - Full type safety and IntelliSense support
@@ -38,11 +38,11 @@ hitlimit is designed for speed. Here's how it performs:
38
38
 
39
39
  ### vs Competitors
40
40
 
41
- | Library | Memory (ops/s) | Bundle Size |
42
- |---------|----------------|-------------|
43
- | **hitlimit** | **400,000** | **~5KB** |
44
- | rate-limiter-flexible | 250,000 | ~45KB |
45
- | express-rate-limit | 180,000 | ~15KB |
41
+ | Library | Memory 10K IPs (ops/s) | Bundle Size |
42
+ |---------|------------------------|-------------|
43
+ | **hitlimit** | **2,320,000** | **~7KB** |
44
+ | rate-limiter-flexible | 1,630,000 | ~155KB |
45
+ | express-rate-limit | 1,220,000 | ~66KB |
46
46
 
47
47
  ### HTTP Overhead
48
48
 
@@ -1,3 +1,11 @@
1
1
  import type { HitLimitResult, ResolvedConfig } from '@joint-ops/hitlimit-types';
2
+ export interface FastResult {
3
+ allowed: boolean;
4
+ limit: number;
5
+ remaining: number;
6
+ resetIn: number;
7
+ resetAt: number;
8
+ }
9
+ export declare function checkLimitFast<TRequest>(config: ResolvedConfig<TRequest>, req: TRequest): Promise<FastResult>;
2
10
  export declare function checkLimit<TRequest>(config: ResolvedConfig<TRequest>, req: TRequest): Promise<HitLimitResult>;
3
11
  //# sourceMappingURL=limiter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"limiter.d.ts","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAK7F,wBAAsB,UAAU,CAAC,QAAQ,EACvC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,cAAc,CAAC,CAiDzB"}
1
+ {"version":3,"file":"limiter.d.ts","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAM7F,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAGD,wBAAsB,cAAc,CAAC,QAAQ,EAC3C,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,UAAU,CAAC,CA+BrB;AAGD,wBAAsB,UAAU,CAAC,QAAQ,EACvC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,cAAc,CAAC,CAiDzB"}
@@ -1,9 +1,38 @@
1
- import { parseWindow, hashKey } from './utils.js';
1
+ import { parseWindow } from './utils.js';
2
2
  import { buildHeaders } from './headers.js';
3
3
  import { buildBody } from './response.js';
4
+ // Optimized check for tiered limits - returns minimal object
5
+ export async function checkLimitFast(config, req) {
6
+ const key = await config.key(req);
7
+ let limit = config.limit;
8
+ let windowMs = config.windowMs;
9
+ if (config.tier && config.tiers) {
10
+ const tierName = await config.tier(req);
11
+ const tierConfig = config.tiers[tierName];
12
+ if (tierConfig) {
13
+ limit = tierConfig.limit;
14
+ if (tierConfig.window) {
15
+ windowMs = parseWindow(tierConfig.window);
16
+ }
17
+ }
18
+ }
19
+ if (limit === Infinity) {
20
+ return { allowed: true, limit, remaining: Infinity, resetIn: 0, resetAt: 0 };
21
+ }
22
+ const result = await config.store.hit(key, windowMs, limit);
23
+ const now = Date.now();
24
+ return {
25
+ allowed: result.count <= limit,
26
+ limit,
27
+ remaining: Math.max(0, limit - result.count),
28
+ resetIn: Math.max(0, Math.ceil((result.resetAt - now) / 1000)),
29
+ resetAt: result.resetAt
30
+ };
31
+ }
32
+ // Original full check - used for testing and backwards compatibility
4
33
  export async function checkLimit(config, req) {
5
- const rawKey = await config.key(req);
6
- const key = hashKey(rawKey);
34
+ // Use raw key directly - no hashing needed for rate limit keys
35
+ const key = await config.key(req);
7
36
  let limit = config.limit;
8
37
  let windowMs = config.windowMs;
9
38
  let tierName;
@@ -20,7 +49,7 @@ export async function checkLimit(config, req) {
20
49
  if (limit === Infinity) {
21
50
  return {
22
51
  allowed: true,
23
- info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key: rawKey, tier: tierName },
52
+ info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName },
24
53
  headers: {},
25
54
  body: {}
26
55
  };
@@ -35,7 +64,7 @@ export async function checkLimit(config, req) {
35
64
  remaining,
36
65
  resetIn,
37
66
  resetAt: result.resetAt,
38
- key: rawKey,
67
+ key,
39
68
  tier: tierName
40
69
  };
41
70
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"limiter.js","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAgC,EAChC,GAAa;IAEb,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACpC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE3B,IAAI,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;IACxB,IAAI,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAA;IAC9B,IAAI,QAA4B,CAAA;IAEhC,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACjC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QACzC,IAAI,UAAU,EAAE,CAAC;YACf,KAAK,GAAG,UAAU,CAAC,KAAK,CAAA;YACxB,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;gBACtB,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;YAC3C,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE;YACzF,OAAO,EAAE,EAAE;YACX,IAAI,EAAE,EAAE;SACT,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;IACrE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACnD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,IAAI,KAAK,CAAA;IAErC,MAAM,IAAI,GAAiB;QACzB,KAAK;QACL,SAAS;QACT,OAAO;QACP,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,QAAQ;KACf,CAAA;IAED,OAAO;QACL,OAAO;QACP,IAAI;QACJ,OAAO,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;QACpD,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC;KACtD,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"limiter.js","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,2 @@
1
1
  export declare function parseWindow(window: string | number): number;
2
- export declare function hashKey(key: string): string;
3
2
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"AASA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAO3D;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAO3D"}
@@ -1,4 +1,3 @@
1
- import { createHash } from 'crypto';
2
1
  const UNITS = {
3
2
  s: 1000,
4
3
  m: 60 * 1000,
@@ -13,7 +12,4 @@ export function parseWindow(window) {
13
12
  throw new Error(`Invalid window format: ${window}`);
14
13
  return parseInt(match[1]) * UNITS[match[2]];
15
14
  }
16
- export function hashKey(key) {
17
- return createHash('sha256').update(key).digest('hex').slice(0, 16);
18
- }
19
15
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAEnC,MAAM,KAAK,GAA2B;IACpC,CAAC,EAAE,IAAI;IACP,CAAC,EAAE,EAAE,GAAG,IAAI;IACZ,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACjB,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;CACvB,CAAA;AAED,MAAM,UAAU,WAAW,CAAC,MAAuB;IACjD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAA;IAE7C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAA;IAC9C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,EAAE,CAAC,CAAA;IAE/D,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AACpE,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAA2B;IACpC,CAAC,EAAE,IAAI;IACP,CAAC,EAAE,EAAE,GAAG,IAAI;IACZ,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACjB,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;CACvB,CAAA;AAED,MAAM,UAAU,WAAW,CAAC,MAAuB;IACjD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAA;IAE7C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAA;IAC9C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,EAAE,CAAC,CAAA;IAE/D,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -3,5 +3,6 @@ import type { HitLimitOptions } from '@joint-ops/hitlimit-types';
3
3
  export type { HitLimitOptions, HitLimitInfo, HitLimitResult, HitLimitStore, StoreResult, TierConfig, HeadersConfig, ResolvedConfig, KeyGenerator, TierResolver, SkipFunction, StoreErrorHandler, ResponseFormatter, ResponseConfig } from '@joint-ops/hitlimit-types';
4
4
  export { DEFAULT_LIMIT, DEFAULT_WINDOW, DEFAULT_WINDOW_MS, DEFAULT_MESSAGE } from '@joint-ops/hitlimit-types';
5
5
  export { memoryStore } from './stores/memory.js';
6
+ export { checkLimit } from './core/limiter.js';
6
7
  export declare function hitlimit(options?: HitLimitOptions<Request>): (req: Request, res: Response, next: NextFunction) => Promise<void>;
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAKhE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AACrQ,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC7G,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAMhD,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAe,CAAC,OAAO,CAAM,IAI/C,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBA8B9D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAmD,MAAM,2BAA2B,CAAA;AAIjH,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AACrQ,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC7G,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAiB9C,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAe,CAAC,OAAO,CAAM,SAgBxC,OAAO,OAAO,QAAQ,QAAQ,YAAY,mBA+GhE"}
package/dist/index.js CHANGED
@@ -1,28 +1,120 @@
1
1
  import { resolveConfig } from './core/config.js';
2
- import { checkLimit } from './core/limiter.js';
3
2
  import { memoryStore } from './stores/memory.js';
4
3
  export { DEFAULT_LIMIT, DEFAULT_WINDOW, DEFAULT_WINDOW_MS, DEFAULT_MESSAGE } from '@joint-ops/hitlimit-types';
5
4
  export { memoryStore } from './stores/memory.js';
5
+ export { checkLimit } from './core/limiter.js';
6
6
  function getDefaultKey(req) {
7
7
  return req.ip || req.socket?.remoteAddress || 'unknown';
8
8
  }
9
+ // Inline response builder for performance
10
+ function buildResponseBody(response, info) {
11
+ if (typeof response === 'function') {
12
+ return response(info);
13
+ }
14
+ return { ...response, limit: info.limit, remaining: info.remaining, resetIn: info.resetIn };
15
+ }
9
16
  export function hitlimit(options = {}) {
10
17
  const store = options.store ?? memoryStore();
11
18
  const config = resolveConfig(options, store, getDefaultKey);
19
+ // Pre-compute flags
20
+ const hasSkip = !!config.skip;
21
+ const hasTiers = !!(config.tier && config.tiers);
22
+ const standardHeaders = config.headers.standard;
23
+ const legacyHeaders = config.headers.legacy;
24
+ const retryAfterHeader = config.headers.retryAfter;
25
+ const limit = config.limit;
26
+ const windowMs = config.windowMs;
27
+ const responseConfig = config.response;
28
+ // Fast path: no skip, no tiers (most common case ~80%)
29
+ if (!hasSkip && !hasTiers) {
30
+ return async (req, res, next) => {
31
+ try {
32
+ const key = await config.key(req);
33
+ const result = await config.store.hit(key, windowMs, limit);
34
+ const allowed = result.count <= limit;
35
+ const remaining = Math.max(0, limit - result.count);
36
+ const resetIn = Math.ceil((result.resetAt - Date.now()) / 1000);
37
+ // Set headers directly
38
+ if (standardHeaders) {
39
+ res.setHeader('RateLimit-Limit', limit);
40
+ res.setHeader('RateLimit-Remaining', remaining);
41
+ res.setHeader('RateLimit-Reset', resetIn);
42
+ }
43
+ if (legacyHeaders) {
44
+ res.setHeader('X-RateLimit-Limit', limit);
45
+ res.setHeader('X-RateLimit-Remaining', remaining);
46
+ res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetAt / 1000));
47
+ }
48
+ if (!allowed) {
49
+ if (retryAfterHeader) {
50
+ res.setHeader('Retry-After', resetIn);
51
+ }
52
+ const body = buildResponseBody(responseConfig, {
53
+ limit, remaining: 0, resetIn, resetAt: result.resetAt, key
54
+ });
55
+ res.status(429).json(body);
56
+ return;
57
+ }
58
+ next();
59
+ }
60
+ catch (error) {
61
+ const action = await config.onStoreError(error, req);
62
+ if (action === 'deny') {
63
+ res.status(429).json({ hitlimit: true, message: 'Rate limit error' });
64
+ return;
65
+ }
66
+ next();
67
+ }
68
+ };
69
+ }
70
+ // Full path: with skip and/or tiers
12
71
  return async (req, res, next) => {
13
- if (config.skip) {
72
+ if (hasSkip) {
14
73
  const shouldSkip = await config.skip(req);
15
74
  if (shouldSkip) {
16
75
  return next();
17
76
  }
18
77
  }
19
78
  try {
20
- const result = await checkLimit(config, req);
21
- Object.entries(result.headers).forEach(([key, value]) => {
22
- res.setHeader(key, value);
23
- });
24
- if (!result.allowed) {
25
- res.status(429).json(result.body);
79
+ const key = await config.key(req);
80
+ let effectiveLimit = limit;
81
+ let effectiveWindowMs = windowMs;
82
+ let tierName;
83
+ if (hasTiers) {
84
+ tierName = await config.tier(req);
85
+ const tierConfig = config.tiers[tierName];
86
+ if (tierConfig) {
87
+ effectiveLimit = tierConfig.limit;
88
+ if (tierConfig.window) {
89
+ effectiveWindowMs = parseWindow(tierConfig.window);
90
+ }
91
+ }
92
+ }
93
+ if (effectiveLimit === Infinity) {
94
+ return next();
95
+ }
96
+ const result = await config.store.hit(key, effectiveWindowMs, effectiveLimit);
97
+ const allowed = result.count <= effectiveLimit;
98
+ const remaining = Math.max(0, effectiveLimit - result.count);
99
+ const resetIn = Math.ceil((result.resetAt - Date.now()) / 1000);
100
+ if (standardHeaders) {
101
+ res.setHeader('RateLimit-Limit', effectiveLimit);
102
+ res.setHeader('RateLimit-Remaining', remaining);
103
+ res.setHeader('RateLimit-Reset', resetIn);
104
+ }
105
+ if (legacyHeaders) {
106
+ res.setHeader('X-RateLimit-Limit', effectiveLimit);
107
+ res.setHeader('X-RateLimit-Remaining', remaining);
108
+ res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetAt / 1000));
109
+ }
110
+ if (!allowed) {
111
+ if (retryAfterHeader) {
112
+ res.setHeader('Retry-After', resetIn);
113
+ }
114
+ const body = buildResponseBody(responseConfig, {
115
+ limit: effectiveLimit, remaining: 0, resetIn, resetAt: result.resetAt, key, tier: tierName
116
+ });
117
+ res.status(429).json(body);
26
118
  return;
27
119
  }
28
120
  next();
@@ -37,4 +129,21 @@ export function hitlimit(options = {}) {
37
129
  }
38
130
  };
39
131
  }
132
+ function parseWindow(window) {
133
+ if (typeof window === 'number')
134
+ return window;
135
+ const match = window.match(/^(\d+)(ms|s|m|h|d)$/);
136
+ if (!match)
137
+ return 60000;
138
+ const value = parseInt(match[1], 10);
139
+ const unit = match[2];
140
+ switch (unit) {
141
+ case 'ms': return value;
142
+ case 's': return value * 1000;
143
+ case 'm': return value * 60 * 1000;
144
+ case 'h': return value * 60 * 60 * 1000;
145
+ case 'd': return value * 24 * 60 * 60 * 1000;
146
+ default: return 60000;
147
+ }
148
+ }
40
149
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC7G,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,SAAS,aAAa,CAAC,GAAY;IACjC,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,SAAS,CAAA;AACzD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,UAAoC,EAAE;IAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,WAAW,EAAE,CAAA;IAC5C,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC,CAAA;IAE3D,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACzC,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,IAAI,EAAE,CAAA;YACf,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;YAE5C,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;gBACtD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YAC3B,CAAC,CAAC,CAAA;YAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBACjC,OAAM;YACR,CAAC;YAED,IAAI,EAAE,CAAA;QACR,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,KAAc,EAAE,GAAG,CAAC,CAAA;YAC7D,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAA;gBACrE,OAAM;YACR,CAAC;YACD,IAAI,EAAE,CAAA;QACR,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AAgD3E,wBAAgB,WAAW,IAAI,aAAa,CAE3C"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AA+D3E,wBAAgB,WAAW,IAAI,aAAa,CAE3C"}
@@ -1,34 +1,38 @@
1
1
  class MemoryStore {
2
2
  hits = new Map();
3
- cleanupTimer;
4
- constructor() {
5
- this.cleanupTimer = setInterval(() => this.cleanup(), 60000);
6
- }
7
3
  hit(key, windowMs, _limit) {
8
- const now = Date.now();
9
4
  const entry = this.hits.get(key);
10
- if (!entry || entry.resetAt <= now) {
11
- const resetAt = now + windowMs;
12
- this.hits.set(key, { count: 1, resetAt });
13
- return { count: 1, resetAt };
5
+ if (entry !== undefined) {
6
+ // Entry exists and is guaranteed valid (setTimeout hasn't fired yet)
7
+ // Hot path: just increment, no expiration check needed!
8
+ entry.count++;
9
+ return { count: entry.count, resetAt: entry.resetAt };
14
10
  }
15
- entry.count++;
16
- return { count: entry.count, resetAt: entry.resetAt };
11
+ // New key - create entry with cleanup timeout
12
+ const now = Date.now();
13
+ const resetAt = now + windowMs;
14
+ const timeoutId = setTimeout(() => {
15
+ this.hits.delete(key);
16
+ }, windowMs);
17
+ // Don't keep process alive
18
+ if (typeof timeoutId.unref === 'function') {
19
+ timeoutId.unref();
20
+ }
21
+ this.hits.set(key, { count: 1, resetAt, timeoutId });
22
+ return { count: 1, resetAt };
17
23
  }
18
24
  reset(key) {
19
- this.hits.delete(key);
25
+ const entry = this.hits.get(key);
26
+ if (entry) {
27
+ clearTimeout(entry.timeoutId);
28
+ this.hits.delete(key);
29
+ }
20
30
  }
21
31
  shutdown() {
22
- clearInterval(this.cleanupTimer);
23
- this.hits.clear();
24
- }
25
- cleanup() {
26
- const now = Date.now();
27
- for (const [key, entry] of this.hits) {
28
- if (entry.resetAt <= now) {
29
- this.hits.delete(key);
30
- }
32
+ for (const [, entry] of this.hits) {
33
+ clearTimeout(entry.timeoutId);
31
34
  }
35
+ this.hits.clear();
32
36
  }
33
37
  }
34
38
  export function memoryStore() {
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW;IACP,IAAI,GAAG,IAAI,GAAG,EAAiB,CAAA;IAC/B,YAAY,CAAgC;IAEpD;QACE,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAA;IAC9D,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAc;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEhC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,GAAG,GAAG,QAAQ,CAAA;YAC9B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;YACzC,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAA;QAC9B,CAAC;QAED,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;IACvD,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACvB,CAAC;IAED,QAAQ;QACN,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAChC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;IACnB,CAAC;IAEO,OAAO;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACrC,IAAI,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,WAAW,EAAE,CAAA;AAC1B,CAAC"}
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW;IACE,IAAI,GAAuB,IAAI,GAAG,EAAE,CAAA;IAErD,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAc;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEhC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,qEAAqE;YACrE,wDAAwD;YACxD,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;QACvD,CAAC;QAED,8CAA8C;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,OAAO,GAAG,GAAG,GAAG,QAAQ,CAAA;QAE9B,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC,EAAE,QAAQ,CAAC,CAAA;QAEZ,2BAA2B;QAC3B,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC1C,SAAS,CAAC,KAAK,EAAE,CAAA;QACnB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QACpD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,GAAW;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;YAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC;IACH,CAAC;IAED,QAAQ;QACN,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAClC,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;IACnB,CAAC;CACF;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,WAAW,EAAE,CAAA;AAC1B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joint-ops/hitlimit",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Fast rate limiting middleware for Express, NestJS & Node.js - API throttling, brute force protection, request limiting",
5
5
  "author": {
6
6
  "name": "Shayan M Hussain",
@@ -119,7 +119,7 @@
119
119
  "test:watch": "vitest"
120
120
  },
121
121
  "dependencies": {
122
- "@joint-ops/hitlimit-types": "workspace:*"
122
+ "@joint-ops/hitlimit-types": "1.0.3"
123
123
  },
124
124
  "peerDependencies": {
125
125
  "@nestjs/common": ">=8.0.0",