@heyhru/server-plugin-rate-limit 0.2.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 ADDED
@@ -0,0 +1,60 @@
1
+ # @heyhru/server-plugin-rate-limit
2
+
3
+ Rate limiting Fastify plugin powered by `@fastify/rate-limit`. Supports Redis (shared across instances) and in-memory (single-process fallback) stores.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @heyhru/server-plugin-rate-limit
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import Fastify from "fastify";
15
+ import { rateLimitPlugin } from "@heyhru/server-plugin-rate-limit";
16
+
17
+ const app = Fastify();
18
+
19
+ // Global rate limit — 100 req/min per IP
20
+ await app.register(rateLimitPlugin, {
21
+ max: 100,
22
+ timeWindow: 60_000,
23
+ redis: app.redis, // optional, falls back to in-memory
24
+ });
25
+
26
+ // Per-route override
27
+ app.post("/auth/login", {
28
+ config: {
29
+ rateLimit: {
30
+ max: 5,
31
+ timeWindow: 60_000,
32
+ },
33
+ },
34
+ }, handler);
35
+ ```
36
+
37
+ ## API
38
+
39
+ ### `rateLimitPlugin`
40
+
41
+ Fastify plugin (wrapped with `fastify-plugin`).
42
+
43
+ **Options:**
44
+
45
+ | Option | Type | Default | Description |
46
+ | --------------- | ----------------- | ---------------- | ---------------------------------------------------- |
47
+ | `max` | `number` | `100` | Max requests per time window |
48
+ | `timeWindow` | `number \| string` | `60000` | Time window in ms or human-readable (e.g. `"1 minute"`) |
49
+ | `redis` | `Redis \| null` | `null` | ioredis instance for shared store; `null` = in-memory |
50
+ | `allowList` | `string[]` | `[]` | IPs excluded from rate limiting |
51
+ | `keyGenerator` | `(req) => string` | IP-based | Custom key generator function |
52
+
53
+ **Headers added to responses:**
54
+
55
+ - `x-ratelimit-limit` — max requests allowed
56
+ - `x-ratelimit-remaining` — remaining requests in window
57
+ - `x-ratelimit-reset` — seconds until window resets
58
+ - `retry-after` — seconds to wait (only on 429)
59
+
60
+ **Degradation:** When `redis` is `null` or unavailable, the plugin uses an in-memory LRU store automatically. `skipOnError: true` ensures Redis failures never block requests.
@@ -0,0 +1,3 @@
1
+ export { rateLimitPlugin } from "./rate-limit.js";
2
+ export type { RateLimitPluginOptions } from "./rate-limit.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ rateLimitPlugin: () => rateLimitPlugin
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/rate-limit.ts
38
+ var import_fastify_plugin = __toESM(require("fastify-plugin"));
39
+ var import_rate_limit = __toESM(require("@fastify/rate-limit"));
40
+ function defaultKeyGenerator(req) {
41
+ const forwarded = req.headers["x-forwarded-for"];
42
+ const forwardedIp = Array.isArray(forwarded) ? forwarded[0] : forwarded;
43
+ return forwardedIp?.split(",")[0]?.trim() || req.headers["x-real-ip"] || req.ip;
44
+ }
45
+ var plugin = async (fastify, opts) => {
46
+ await fastify.register(import_rate_limit.default, {
47
+ global: true,
48
+ max: opts.max ?? 100,
49
+ timeWindow: opts.timeWindow ?? 6e4,
50
+ redis: opts.redis ?? void 0,
51
+ allowList: opts.allowList ?? [],
52
+ keyGenerator: opts.keyGenerator ?? defaultKeyGenerator,
53
+ skipOnError: true,
54
+ enableDraftSpec: false,
55
+ errorResponseBuilder: () => ({
56
+ error: "Too many requests, please try again later"
57
+ }),
58
+ addHeadersOnExceeding: {
59
+ "x-ratelimit-limit": true,
60
+ "x-ratelimit-remaining": true,
61
+ "x-ratelimit-reset": true
62
+ },
63
+ addHeaders: {
64
+ "x-ratelimit-limit": true,
65
+ "x-ratelimit-remaining": true,
66
+ "x-ratelimit-reset": true,
67
+ "retry-after": true
68
+ }
69
+ });
70
+ };
71
+ var rateLimitPlugin = (0, import_fastify_plugin.default)(plugin, { name: "server-plugin-rate-limit" });
72
+ // Annotate the CommonJS export names for ESM import in node:
73
+ 0 && (module.exports = {
74
+ rateLimitPlugin
75
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,38 @@
1
+ // src/rate-limit.ts
2
+ import fp from "fastify-plugin";
3
+ import rateLimit from "@fastify/rate-limit";
4
+ function defaultKeyGenerator(req) {
5
+ const forwarded = req.headers["x-forwarded-for"];
6
+ const forwardedIp = Array.isArray(forwarded) ? forwarded[0] : forwarded;
7
+ return forwardedIp?.split(",")[0]?.trim() || req.headers["x-real-ip"] || req.ip;
8
+ }
9
+ var plugin = async (fastify, opts) => {
10
+ await fastify.register(rateLimit, {
11
+ global: true,
12
+ max: opts.max ?? 100,
13
+ timeWindow: opts.timeWindow ?? 6e4,
14
+ redis: opts.redis ?? void 0,
15
+ allowList: opts.allowList ?? [],
16
+ keyGenerator: opts.keyGenerator ?? defaultKeyGenerator,
17
+ skipOnError: true,
18
+ enableDraftSpec: false,
19
+ errorResponseBuilder: () => ({
20
+ error: "Too many requests, please try again later"
21
+ }),
22
+ addHeadersOnExceeding: {
23
+ "x-ratelimit-limit": true,
24
+ "x-ratelimit-remaining": true,
25
+ "x-ratelimit-reset": true
26
+ },
27
+ addHeaders: {
28
+ "x-ratelimit-limit": true,
29
+ "x-ratelimit-remaining": true,
30
+ "x-ratelimit-reset": true,
31
+ "retry-after": true
32
+ }
33
+ });
34
+ };
35
+ var rateLimitPlugin = fp(plugin, { name: "server-plugin-rate-limit" });
36
+ export {
37
+ rateLimitPlugin
38
+ };
@@ -0,0 +1,18 @@
1
+ import type { FastifyPluginAsync } from "fastify";
2
+ export interface RateLimitPluginOptions {
3
+ /** Max requests per time window (default: 100) */
4
+ max?: number;
5
+ /** Time window in ms or human-readable string (default: 60000) */
6
+ timeWindow?: number | string;
7
+ /** ioredis instance for shared store; null = in-memory fallback */
8
+ redis?: unknown | null;
9
+ /** IPs excluded from rate limiting */
10
+ allowList?: string[];
11
+ /** Custom key generator (default: IP-based with proxy header support) */
12
+ keyGenerator?: (req: {
13
+ headers: Record<string, string | string[] | undefined>;
14
+ ip: string;
15
+ }) => string;
16
+ }
17
+ export declare const rateLimitPlugin: FastifyPluginAsync<RateLimitPluginOptions>;
18
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,mEAAmE;IACnE,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;CACxG;AAqCD,eAAO,MAAM,eAAe,4CAAmD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@heyhru/server-plugin-rate-limit",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.2.0",
7
+ "description": "Rate limiting Fastify plugin powered by @fastify/rate-limit",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup && tsc --emitDeclarationOnly",
23
+ "dev": "tsup --watch",
24
+ "lint": "eslint src",
25
+ "test": "vitest run --passWithNoTests",
26
+ "clean": "rm -rf dist"
27
+ },
28
+ "dependencies": {
29
+ "@fastify/rate-limit": "^10.2.1",
30
+ "fastify-plugin": "5.1.0"
31
+ },
32
+ "peerDependencies": {
33
+ "fastify": "^5.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "tsup": "^8.5.1",
37
+ "typescript": "^6.0.2",
38
+ "vitest": "^4.1.4"
39
+ },
40
+ "gitHead": "7c589bd1a37ed2def055d4619e914748fe66a15a"
41
+ }