@infodb/revx 0.1.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.
@@ -0,0 +1,134 @@
1
+ import { createProxyMiddleware } from 'http-proxy-middleware';
2
+ export class LoadBalancer {
3
+ constructor(targets, strategy = 'round-robin', logger) {
4
+ this.targets = targets;
5
+ this.strategy = strategy;
6
+ this.logger = logger;
7
+ this.currentIndex = 0;
8
+ this.targetHealth = new Map();
9
+ targets.forEach(target => this.targetHealth.set(target, true));
10
+ }
11
+ getTarget(req) {
12
+ const healthyTargets = this.targets.filter(t => this.targetHealth.get(t));
13
+ if (healthyTargets.length === 0) {
14
+ this.logger.warning('All targets are unhealthy, using first target');
15
+ return this.targets[0];
16
+ }
17
+ switch (this.strategy) {
18
+ case 'random':
19
+ return healthyTargets[Math.floor(Math.random() * healthyTargets.length)];
20
+ case 'ip-hash':
21
+ if (req) {
22
+ const ip = req.ip || req.socket.remoteAddress || '';
23
+ const hash = this.hashCode(ip);
24
+ return healthyTargets[Math.abs(hash) % healthyTargets.length];
25
+ }
26
+ return healthyTargets[0];
27
+ case 'round-robin':
28
+ default:
29
+ const target = healthyTargets[this.currentIndex % healthyTargets.length];
30
+ this.currentIndex = (this.currentIndex + 1) % healthyTargets.length;
31
+ return target;
32
+ }
33
+ }
34
+ markUnhealthy(target) {
35
+ this.targetHealth.set(target, false);
36
+ this.logger.warning(`Target marked as unhealthy: ${target}`);
37
+ }
38
+ markHealthy(target) {
39
+ this.targetHealth.set(target, true);
40
+ this.logger.verbose(`Target marked as healthy: ${target}`);
41
+ }
42
+ hashCode(str) {
43
+ let hash = 0;
44
+ for (let i = 0; i < str.length; i++) {
45
+ const char = str.charCodeAt(i);
46
+ hash = ((hash << 5) - hash) + char;
47
+ hash = hash & hash;
48
+ }
49
+ return hash;
50
+ }
51
+ }
52
+ export class ProxyManager {
53
+ constructor(logger) {
54
+ this.logger = logger;
55
+ this.loadBalancers = new Map();
56
+ }
57
+ createProxyMiddleware(route) {
58
+ const options = {
59
+ changeOrigin: route.changeOrigin ?? true,
60
+ ws: route.ws ?? false,
61
+ pathRewrite: route.pathRewrite,
62
+ timeout: route.options?.timeout,
63
+ followRedirects: route.options?.followRedirects,
64
+ on: {
65
+ proxyReq: (proxyReq, req) => {
66
+ if (route.options?.headers) {
67
+ Object.entries(route.options.headers).forEach(([key, value]) => {
68
+ proxyReq.setHeader(key, value);
69
+ });
70
+ }
71
+ if (route.transform?.request?.headers?.add) {
72
+ Object.entries(route.transform.request.headers.add).forEach(([key, value]) => {
73
+ proxyReq.setHeader(key, value);
74
+ });
75
+ }
76
+ if (route.transform?.request?.headers?.remove) {
77
+ route.transform.request.headers.remove.forEach(header => {
78
+ proxyReq.removeHeader(header);
79
+ });
80
+ }
81
+ this.logger.verbose('Proxy request', {
82
+ method: req.method,
83
+ path: req.url,
84
+ target: proxyReq.getHeader('host')
85
+ });
86
+ },
87
+ proxyRes: (proxyRes, req, res) => {
88
+ if (route.transform?.response?.headers?.add) {
89
+ Object.entries(route.transform.response.headers.add).forEach(([key, value]) => {
90
+ proxyRes.headers[key.toLowerCase()] = value;
91
+ });
92
+ }
93
+ if (route.transform?.response?.headers?.remove) {
94
+ route.transform.response.headers.remove.forEach(header => {
95
+ delete proxyRes.headers[header.toLowerCase()];
96
+ });
97
+ }
98
+ this.logger.verbose('Proxy response', {
99
+ statusCode: proxyRes.statusCode,
100
+ path: req.url
101
+ });
102
+ },
103
+ error: (err, req, res) => {
104
+ this.logger.error(`Proxy error: ${err.message}`);
105
+ const serverRes = res;
106
+ if (!serverRes.headersSent) {
107
+ serverRes.writeHead(502, { 'Content-Type': 'application/json' });
108
+ }
109
+ serverRes.end(JSON.stringify({
110
+ error: 'Bad Gateway',
111
+ message: 'Failed to proxy request'
112
+ }));
113
+ }
114
+ }
115
+ };
116
+ if (route.targets && route.targets.length > 0) {
117
+ const balancer = new LoadBalancer(route.targets, route.strategy || 'round-robin', this.logger);
118
+ this.loadBalancers.set(route.path, balancer);
119
+ options.router = (req) => {
120
+ return balancer.getTarget(req);
121
+ };
122
+ this.logger.info(`Load balancer configured for ${route.path} with ${route.targets.length} targets (${route.strategy || 'round-robin'})`);
123
+ }
124
+ else if (route.target) {
125
+ options.target = route.target;
126
+ this.logger.info(`Proxy configured: ${route.path} -> ${route.target}`);
127
+ }
128
+ return createProxyMiddleware(options);
129
+ }
130
+ getLoadBalancer(path) {
131
+ return this.loadBalancers.get(path);
132
+ }
133
+ }
134
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/utils/proxy.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@infodb/revx",
3
+ "version": "0.1.0",
4
+ "description": "Reverse proxy CLI tool with YAML configuration",
5
+ "homepage": "https://github.com/tamuto/infodb-cli/",
6
+ "bugs": "https://github.com/tamuto/infodb-cli/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/tamuto/infodb-cli",
10
+ "directory": "revx"
11
+ },
12
+ "keywords": [
13
+ "reverse-proxy",
14
+ "proxy",
15
+ "cli",
16
+ "express",
17
+ "yaml",
18
+ "http-proxy"
19
+ ],
20
+ "author": "tamuto <tamuto@infodb.jp>",
21
+ "license": "MIT",
22
+ "main": "dist/index.js",
23
+ "type": "module",
24
+ "bin": {
25
+ "revx": "./bin/cli.js"
26
+ },
27
+ "files": [
28
+ "bin",
29
+ "dist",
30
+ "sample"
31
+ ],
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "dependencies": {
36
+ "chalk": "^5.6.2",
37
+ "commander": "^14.0.2",
38
+ "cors": "^2.8.5",
39
+ "express": "^5.1.0",
40
+ "http-proxy-middleware": "^3.0.3",
41
+ "morgan": "^1.10.0",
42
+ "yaml": "^2.8.1"
43
+ },
44
+ "devDependencies": {
45
+ "@types/cors": "^2.8.17",
46
+ "@types/express": "^5.0.5",
47
+ "@types/morgan": "^1.9.9",
48
+ "@types/node": "^24.10.1",
49
+ "tsx": "^4.20.6",
50
+ "typescript": "^5.9.3"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "scripts": {
56
+ "dev": "tsx src/index.ts",
57
+ "build": "tsc",
58
+ "start": "node dist/index.js"
59
+ }
60
+ }
@@ -0,0 +1,15 @@
1
+ # revx.simple.yaml - Simple Reverse Proxy Configuration
2
+
3
+ server:
4
+ port: 3000
5
+
6
+ routes:
7
+ - path: "/api/*"
8
+ target: "http://localhost:4000"
9
+ pathRewrite:
10
+ "^/api": ""
11
+
12
+ - path: "/auth/*"
13
+ target: "http://localhost:5000"
14
+ pathRewrite:
15
+ "^/auth": ""
@@ -0,0 +1,95 @@
1
+ # revx.yaml - Reverse Proxy Configuration Example
2
+
3
+ # Server settings
4
+ server:
5
+ port: 3000
6
+ host: 0.0.0.0
7
+ name: "My Reverse Proxy"
8
+
9
+ # Global configuration
10
+ global:
11
+ # Request timeout in milliseconds
12
+ timeout: 30000
13
+
14
+ # CORS settings
15
+ cors:
16
+ enabled: true
17
+ origin: "*"
18
+ methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
19
+ credentials: true
20
+
21
+ # Logging settings
22
+ logging:
23
+ enabled: true
24
+ format: "combined" # combined | dev | common | short | tiny
25
+ level: "info" # error | warn | info | debug
26
+
27
+ # Route definitions
28
+ routes:
29
+ # Example 1: Simple proxy to API server
30
+ - path: "/api/*"
31
+ target: "http://localhost:4000"
32
+ changeOrigin: true
33
+ pathRewrite:
34
+ "^/api": ""
35
+ options:
36
+ timeout: 5000
37
+ headers:
38
+ X-Forwarded-Host: "${HOST}"
39
+
40
+ # Example 2: WebSocket proxy
41
+ - path: "/ws"
42
+ target: "ws://localhost:5000"
43
+ ws: true
44
+ changeOrigin: true
45
+
46
+ # Example 3: Load balancing with multiple targets
47
+ - path: "/balanced/*"
48
+ targets:
49
+ - "http://localhost:8001"
50
+ - "http://localhost:8002"
51
+ - "http://localhost:8003"
52
+ strategy: "round-robin" # round-robin | random | ip-hash
53
+ pathRewrite:
54
+ "^/balanced": ""
55
+ healthCheck:
56
+ enabled: true
57
+ interval: 30000
58
+ path: "/health"
59
+ timeout: 3000
60
+
61
+ # Example 4: Request/Response transformation
62
+ - path: "/transform/*"
63
+ target: "http://localhost:6000"
64
+ changeOrigin: true
65
+ transform:
66
+ request:
67
+ headers:
68
+ add:
69
+ X-API-Version: "v2"
70
+ remove:
71
+ - "Cookie"
72
+ response:
73
+ headers:
74
+ add:
75
+ X-Proxy-Server: "revx"
76
+ remove:
77
+ - "Server"
78
+
79
+ # Middleware configuration
80
+ middleware:
81
+ # Request ID middleware
82
+ - type: "requestId"
83
+ enabled: true
84
+ headerName: "X-Request-ID"
85
+
86
+ # Compression middleware
87
+ - type: "compression"
88
+ enabled: true
89
+ threshold: 1024
90
+
91
+ # SSL/TLS settings (optional)
92
+ # ssl:
93
+ # enabled: false
94
+ # key: "/path/to/private.key"
95
+ # cert: "/path/to/certificate.crt"