BREAKING CHANGE(documentation): Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0
This commit is contained in:
		| @@ -1,5 +1,14 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-05-10 - 15.0.0 - BREAKING CHANGE(documentation) | ||||
| Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0 | ||||
|  | ||||
| - Added detailed description of IRouteConfig, IRouteMatch, and IRouteAction interfaces | ||||
| - Improved explanation of port, domain, path, client IP, and TLS version matching features | ||||
| - Included examples of helper functions (createHttpRoute, createHttpsRoute, etc.) with usage of template variables | ||||
| - Enhanced migration guide from legacy configurations to the new match/action pattern | ||||
| - Updated examples and tests to reflect the new documentation structure | ||||
|  | ||||
| ## 2025-05-09 - 13.1.3 - fix(documentation) | ||||
| Update readme.md to provide a unified and comprehensive overview of SmartProxy, with reorganized sections, enhanced diagrams, and detailed usage examples for various proxy scenarios. | ||||
|  | ||||
|   | ||||
							
								
								
									
										254
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										254
									
								
								readme.md
									
									
									
									
									
								
							| @@ -202,64 +202,234 @@ await proxy.stop(); | ||||
|  | ||||
| ## Route-Based Configuration System | ||||
|  | ||||
| The new route-based configuration in v14.0.0 follows a match/action pattern, making it more powerful and flexible: | ||||
| SmartProxy v14.0.0 introduces a new unified route configuration system based on the `IRouteConfig` interface. This system follows a match/action pattern that makes routing more powerful, flexible, and declarative. | ||||
|  | ||||
| ### IRouteConfig Interface | ||||
|  | ||||
| The `IRouteConfig` interface is the core building block of SmartProxy's configuration system. Each route definition consists of match criteria and an action to perform on matched traffic: | ||||
|  | ||||
| ```typescript | ||||
| interface IRouteConfig { | ||||
|   // What traffic to match (required) | ||||
|   match: IRouteMatch; | ||||
|  | ||||
|   // What to do with matched traffic (required) | ||||
|   action: IRouteAction; | ||||
|  | ||||
|   // Metadata (all optional) | ||||
|   name?: string;             // Human-readable name for this route | ||||
|   description?: string;      // Description of the route's purpose | ||||
|   priority?: number;         // Controls matching order (higher = matched first) | ||||
|   tags?: string[];           // Arbitrary tags for categorization | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Match Criteria (IRouteMatch) | ||||
|  | ||||
| The `match` property defines criteria for identifying which incoming traffic should be handled by this route: | ||||
|  | ||||
| ```typescript | ||||
| interface IRouteMatch { | ||||
|   // Listen on these ports (required) | ||||
|   ports: TPortRange;  // number | number[] | Array<{ from: number; to: number }> | ||||
|  | ||||
|   // Optional domain patterns to match (default: all domains) | ||||
|   domains?: string | string[];  // Supports wildcards like '*.example.com' | ||||
|  | ||||
|   // Advanced matching criteria (all optional) | ||||
|   path?: string;           // Match specific URL paths, supports glob patterns | ||||
|   clientIp?: string[];     // Match specific client IPs, supports glob patterns | ||||
|   tlsVersion?: string[];   // Match specific TLS versions e.g. ['TLSv1.2', 'TLSv1.3'] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **Port Specification:** | ||||
| - **Single port:** `ports: 80` | ||||
| - **Multiple ports:** `ports: [80, 443]` | ||||
| - **Port ranges:** `ports: [{ from: 8000, to: 8100 }]` | ||||
| - **Mixed format:** `ports: [80, 443, { from: 8000, to: 8100 }]` | ||||
|  | ||||
| **Domain Matching:** | ||||
| - **Single domain:** `domains: 'example.com'` | ||||
| - **Multiple domains:** `domains: ['example.com', 'api.example.com']` | ||||
| - **Wildcard domains:** `domains: '*.example.com'` (matches any subdomain) | ||||
| - **Root domain + subdomains:** `domains: ['example.com', '*.example.com']` | ||||
|  | ||||
| **Path Matching:** | ||||
| - **Exact path:** `path: '/api'` (matches only /api exactly) | ||||
| - **Prefix match:** `path: '/api/*'` (matches /api and any paths under it) | ||||
| - **Multiple patterns:** Use multiple routes with different priorities | ||||
|  | ||||
| **Client IP Matching:** | ||||
| - **Exact IP:** `clientIp: ['192.168.1.1']` | ||||
| - **Subnet wildcards:** `clientIp: ['10.0.0.*', '192.168.1.*']` | ||||
| - **CIDR notation:** `clientIp: ['10.0.0.0/24']` | ||||
|  | ||||
| **TLS Version Matching:** | ||||
| - `tlsVersion: ['TLSv1.2', 'TLSv1.3']` (only match these TLS versions) | ||||
|  | ||||
| #### Action Configuration (IRouteAction) | ||||
|  | ||||
| The `action` property defines what to do with traffic that matches the criteria: | ||||
|  | ||||
| ```typescript | ||||
| interface IRouteAction { | ||||
|   // Action type (required) | ||||
|   type: 'forward' | 'redirect' | 'block'; | ||||
|  | ||||
|   // For 'forward' actions | ||||
|   target?: IRouteTarget; | ||||
|  | ||||
|   // TLS handling for 'forward' actions | ||||
|   tls?: IRouteTls; | ||||
|  | ||||
|   // For 'redirect' actions | ||||
|   redirect?: IRouteRedirect; | ||||
|  | ||||
|   // Security options for any action | ||||
|   security?: IRouteSecurity; | ||||
|  | ||||
|   // Advanced options | ||||
|   advanced?: IRouteAdvanced; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **Forward Action:** | ||||
| When `type: 'forward'`, the traffic is forwarded to the specified target: | ||||
| ```typescript | ||||
| interface IRouteTarget { | ||||
|   host: string | string[];  // Target host(s) - string array enables round-robin | ||||
|   port: number;             // Target port | ||||
|   preservePort?: boolean;   // Use incoming port as target port (default: false) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **TLS Configuration:** | ||||
| When forwarding with TLS, you can configure how TLS is handled: | ||||
| ```typescript | ||||
| interface IRouteTls { | ||||
|   mode: 'passthrough' | 'terminate' | 'terminate-and-reencrypt'; | ||||
|   certificate?: 'auto' | {   // 'auto' = use ACME (Let's Encrypt) | ||||
|     key: string;            // TLS private key content | ||||
|     cert: string;           // TLS certificate content | ||||
|   }; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **TLS Modes:** | ||||
| - **passthrough:** Forward raw encrypted TLS traffic without decryption | ||||
| - **terminate:** Terminate TLS and forward as HTTP | ||||
| - **terminate-and-reencrypt:** Terminate TLS and create a new TLS connection to the backend | ||||
|  | ||||
| **Redirect Action:** | ||||
| When `type: 'redirect'`, the client is redirected: | ||||
| ```typescript | ||||
| interface IRouteRedirect { | ||||
|   to: string;                   // URL or template with variables | ||||
|   status: 301 | 302 | 307 | 308;  // HTTP status code | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **Block Action:** | ||||
| When `type: 'block'`, the connection is immediately closed. | ||||
|  | ||||
| **Security Options:** | ||||
| For any action type, you can add security controls: | ||||
| ```typescript | ||||
| interface IRouteSecurity { | ||||
|   allowedIps?: string[];     // IP allowlist with glob pattern support | ||||
|   blockedIps?: string[];     // IP blocklist with glob pattern support | ||||
|   maxConnections?: number;   // Maximum concurrent connections | ||||
|   authentication?: {         // Optional authentication (future support) | ||||
|     type: 'basic' | 'digest' | 'oauth'; | ||||
|     // Auth-specific options | ||||
|   }; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **Advanced Options:** | ||||
| Additional advanced configurations: | ||||
| ```typescript | ||||
| interface IRouteAdvanced { | ||||
|   timeout?: number;                 // Connection timeout in milliseconds | ||||
|   headers?: Record<string, string>; // Custom HTTP headers | ||||
|   keepAlive?: boolean;              // Enable connection pooling | ||||
|   // Additional advanced options | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Template Variables | ||||
|  | ||||
| String values in redirect URLs and headers can include variables: | ||||
|  | ||||
| - `{domain}`: The requested domain name | ||||
| - `{port}`: The incoming port number | ||||
| - `{path}`: The requested URL path | ||||
| - `{query}`: The query string | ||||
| - `{clientIp}`: The client's IP address | ||||
| - `{sni}`: The SNI hostname | ||||
|  | ||||
| Example with template variables: | ||||
| ```typescript | ||||
| redirect: { | ||||
|   to: 'https://{domain}{path}?source=redirect', | ||||
|   status: 301 | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Route Metadata and Prioritization | ||||
|  | ||||
| You can add metadata to routes to help with organization and control matching priority: | ||||
|  | ||||
| ```typescript | ||||
| // Basic structure of a route configuration | ||||
| { | ||||
|   // What traffic to match | ||||
|   name: 'API Server',                 // Human-readable name | ||||
|   description: 'Main API endpoints',  // Description | ||||
|   priority: 100,                      // Matching priority (higher = matched first) | ||||
|   tags: ['api', 'internal']           // Arbitrary tags | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Routes with higher priority values are matched first, allowing you to create specialized routes that take precedence over more general ones. | ||||
|  | ||||
| #### Complete Route Configuration Example | ||||
|  | ||||
| ```typescript | ||||
| // Example of a complete route configuration | ||||
| { | ||||
|   match: { | ||||
|     ports: 443,                          // Required: port(s) to listen on | ||||
|     domains: 'example.com',              // Optional: domain(s) to match | ||||
|     path: '/api',                        // Optional: URL path pattern | ||||
|     clientIp: ['10.0.0.*'],              // Optional: client IP patterns to match | ||||
|     tlsVersion: ['TLSv1.2', 'TLSv1.3']   // Optional: TLS versions to match | ||||
|     ports: 443, | ||||
|     domains: ['api.example.com', 'api-v2.example.com'], | ||||
|     path: '/secure/*', | ||||
|     clientIp: ['10.0.0.*', '192.168.1.*'] | ||||
|   }, | ||||
|    | ||||
|   // What to do with matched traffic | ||||
|   action: { | ||||
|     type: 'forward',                     // 'forward', 'redirect', or 'block' | ||||
|      | ||||
|     // For 'forward' actions | ||||
|     type: 'forward', | ||||
|     target: { | ||||
|       host: 'localhost',                 // Target host(s) for forwarding | ||||
|       port: 8080                         // Target port | ||||
|       host: ['10.0.0.1', '10.0.0.2'],  // Round-robin between these hosts | ||||
|       port: 8080 | ||||
|     }, | ||||
|      | ||||
|     // TLS handling (for 'forward' actions) | ||||
|     tls: { | ||||
|       mode: 'terminate',                 // 'passthrough', 'terminate', 'terminate-and-reencrypt' | ||||
|       certificate: 'auto'                // 'auto' for Let's Encrypt or {key, cert} | ||||
|       mode: 'terminate', | ||||
|       certificate: 'auto'  // Use Let's Encrypt | ||||
|     }, | ||||
|      | ||||
|     // For 'redirect' actions | ||||
|     redirect: { | ||||
|       to: 'https://{domain}{path}',      // URL pattern with variables | ||||
|       status: 301                        // HTTP status code | ||||
|     }, | ||||
|      | ||||
|     // Security controls for any action | ||||
|     security: { | ||||
|       allowedIps: ['10.0.0.*'],          // IP allowlist | ||||
|       blockedIps: ['1.2.3.4'],           // IP blocklist | ||||
|       maxConnections: 100                // Connection limits | ||||
|       allowedIps: ['10.0.0.*'], | ||||
|       maxConnections: 100 | ||||
|     }, | ||||
|      | ||||
|     // Advanced options | ||||
|     advanced: { | ||||
|       timeout: 30000,                    // Connection timeout | ||||
|       headers: {                         // Custom headers | ||||
|         'X-Forwarded-For': '{clientIp}' | ||||
|       timeout: 30000, | ||||
|       headers: { | ||||
|         'X-Original-Host': '{domain}', | ||||
|         'X-Client-IP': '{clientIp}' | ||||
|       }, | ||||
|       keepAlive: true                    // Connection pooling | ||||
|       keepAlive: true | ||||
|     } | ||||
|   }, | ||||
|    | ||||
|   // Metadata (optional) | ||||
|   name: 'API Server',                    // Human-readable name | ||||
|   description: 'Main API endpoints',     // Description | ||||
|   priority: 100,                         // Matching priority (higher = matched first) | ||||
|   tags: ['api', 'internal']              // Arbitrary tags | ||||
|   name: 'Secure API Route', | ||||
|   description: 'Route for secure API endpoints with authentication', | ||||
|   priority: 100, | ||||
|   tags: ['api', 'secure', 'internal'] | ||||
| } | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -16,7 +16,7 @@ import { | ||||
| } from '../ts/proxies/smart-proxy/index.js'; | ||||
|  | ||||
| // Import test helpers | ||||
| import { getCertificate } from './helpers/certificates.js'; | ||||
| import { loadTestCertificates } from './helpers/certificates.js'; | ||||
|  | ||||
| tap.test('Routes: Should create basic HTTP route', async () => { | ||||
|   // Create a simple HTTP route | ||||
| @@ -31,12 +31,12 @@ tap.test('Routes: Should create basic HTTP route', async () => { | ||||
|   }); | ||||
|  | ||||
|   // Validate the route configuration | ||||
|   expect(httpRoute.match.ports).to.equal(8080); | ||||
|   expect(httpRoute.match.domains).to.equal('example.com'); | ||||
|   expect(httpRoute.action.type).to.equal('forward'); | ||||
|   expect(httpRoute.action.target?.host).to.equal('localhost'); | ||||
|   expect(httpRoute.action.target?.port).to.equal(3000); | ||||
|   expect(httpRoute.name).to.equal('Basic HTTP Route'); | ||||
|   expect(httpRoute.match.ports).toEqual(8080); | ||||
|   expect(httpRoute.match.domains).toEqual('example.com'); | ||||
|   expect(httpRoute.action.type).toEqual('forward'); | ||||
|   expect(httpRoute.action.target?.host).toEqual('localhost'); | ||||
|   expect(httpRoute.action.target?.port).toEqual(3000); | ||||
|   expect(httpRoute.name).toEqual('Basic HTTP Route'); | ||||
| }); | ||||
|  | ||||
| tap.test('Routes: Should create HTTPS route with TLS termination', async () => { | ||||
| @@ -52,14 +52,14 @@ tap.test('Routes: Should create HTTPS route with TLS termination', async () => { | ||||
|   }); | ||||
|  | ||||
|   // Validate the route configuration | ||||
|   expect(httpsRoute.match.ports).to.equal(443); // Default HTTPS port | ||||
|   expect(httpsRoute.match.domains).to.equal('secure.example.com'); | ||||
|   expect(httpsRoute.action.type).to.equal('forward'); | ||||
|   expect(httpsRoute.action.tls?.mode).to.equal('terminate'); | ||||
|   expect(httpsRoute.action.tls?.certificate).to.equal('auto'); | ||||
|   expect(httpsRoute.action.target?.host).to.equal('localhost'); | ||||
|   expect(httpsRoute.action.target?.port).to.equal(8080); | ||||
|   expect(httpsRoute.name).to.equal('HTTPS Route'); | ||||
|   expect(httpsRoute.match.ports).toEqual(443); // Default HTTPS port | ||||
|   expect(httpsRoute.match.domains).toEqual('secure.example.com'); | ||||
|   expect(httpsRoute.action.type).toEqual('forward'); | ||||
|   expect(httpsRoute.action.tls?.mode).toEqual('terminate'); | ||||
|   expect(httpsRoute.action.tls?.certificate).toEqual('auto'); | ||||
|   expect(httpsRoute.action.target?.host).toEqual('localhost'); | ||||
|   expect(httpsRoute.action.target?.port).toEqual(8080); | ||||
|   expect(httpsRoute.name).toEqual('HTTPS Route'); | ||||
| }); | ||||
|  | ||||
| tap.test('Routes: Should create HTTP to HTTPS redirect', async () => { | ||||
| @@ -70,11 +70,11 @@ tap.test('Routes: Should create HTTP to HTTPS redirect', async () => { | ||||
|   }); | ||||
|  | ||||
|   // Validate the route configuration | ||||
|   expect(redirectRoute.match.ports).to.equal(80); | ||||
|   expect(redirectRoute.match.domains).to.equal('example.com'); | ||||
|   expect(redirectRoute.action.type).to.equal('redirect'); | ||||
|   expect(redirectRoute.action.redirect?.to).to.equal('https://{domain}{path}'); | ||||
|   expect(redirectRoute.action.redirect?.status).to.equal(301); | ||||
|   expect(redirectRoute.match.ports).toEqual(80); | ||||
|   expect(redirectRoute.match.domains).toEqual('example.com'); | ||||
|   expect(redirectRoute.action.type).toEqual('redirect'); | ||||
|   expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}{path}'); | ||||
|   expect(redirectRoute.action.redirect?.status).toEqual(301); | ||||
| }); | ||||
|  | ||||
| tap.test('Routes: Should create complete HTTPS server with redirects', async () => { | ||||
| @@ -90,20 +90,20 @@ tap.test('Routes: Should create complete HTTPS server with redirects', async () | ||||
|   }); | ||||
|  | ||||
|   // Validate that we got two routes (HTTPS route and HTTP redirect) | ||||
|   expect(routes.length).to.equal(2); | ||||
|   expect(routes.length).toEqual(2); | ||||
|  | ||||
|   // Validate HTTPS route | ||||
|   const httpsRoute = routes[0]; | ||||
|   expect(httpsRoute.match.ports).to.equal(443); | ||||
|   expect(httpsRoute.match.domains).to.equal('example.com'); | ||||
|   expect(httpsRoute.action.type).to.equal('forward'); | ||||
|   expect(httpsRoute.action.tls?.mode).to.equal('terminate'); | ||||
|   expect(httpsRoute.match.ports).toEqual(443); | ||||
|   expect(httpsRoute.match.domains).toEqual('example.com'); | ||||
|   expect(httpsRoute.action.type).toEqual('forward'); | ||||
|   expect(httpsRoute.action.tls?.mode).toEqual('terminate'); | ||||
|  | ||||
|   // Validate HTTP redirect route | ||||
|   const redirectRoute = routes[1]; | ||||
|   expect(redirectRoute.match.ports).to.equal(80); | ||||
|   expect(redirectRoute.action.type).to.equal('redirect'); | ||||
|   expect(redirectRoute.action.redirect?.to).to.equal('https://{domain}{path}'); | ||||
|   expect(redirectRoute.match.ports).toEqual(80); | ||||
|   expect(redirectRoute.action.type).toEqual('redirect'); | ||||
|   expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}{path}'); | ||||
| }); | ||||
|  | ||||
| tap.test('Routes: Should create load balancer route', async () => { | ||||
| @@ -118,18 +118,18 @@ tap.test('Routes: Should create load balancer route', async () => { | ||||
|   }); | ||||
|  | ||||
|   // Validate the route configuration | ||||
|   expect(lbRoute.match.domains).to.equal('app.example.com'); | ||||
|   expect(lbRoute.action.type).to.equal('forward'); | ||||
|   expect(Array.isArray(lbRoute.action.target?.host)).to.equal(true); | ||||
|   expect((lbRoute.action.target?.host as string[]).length).to.equal(3); | ||||
|   expect((lbRoute.action.target?.host as string[])[0]).to.equal('10.0.0.1'); | ||||
|   expect(lbRoute.action.target?.port).to.equal(8080); | ||||
|   expect(lbRoute.action.tls?.mode).to.equal('terminate'); | ||||
|   expect(lbRoute.match.domains).toEqual('app.example.com'); | ||||
|   expect(lbRoute.action.type).toEqual('forward'); | ||||
|   expect(Array.isArray(lbRoute.action.target?.host)).toBeTrue(); | ||||
|   expect((lbRoute.action.target?.host as string[]).length).toEqual(3); | ||||
|   expect((lbRoute.action.target?.host as string[])[0]).toEqual('10.0.0.1'); | ||||
|   expect(lbRoute.action.target?.port).toEqual(8080); | ||||
|   expect(lbRoute.action.tls?.mode).toEqual('terminate'); | ||||
| }); | ||||
|  | ||||
| tap.test('SmartProxy: Should create instance with route-based config', async () => { | ||||
|   // Create TLS certificates for testing | ||||
|   const cert = await getCertificate(); | ||||
|   const certs = loadTestCertificates(); | ||||
|  | ||||
|   // Create a SmartProxy instance with route-based configuration | ||||
|   const proxy = new SmartProxy({ | ||||
| @@ -150,8 +150,8 @@ tap.test('SmartProxy: Should create instance with route-based config', async () | ||||
|           port: 8443 | ||||
|         }, | ||||
|         certificate: { | ||||
|           key: cert.key, | ||||
|           cert: cert.cert | ||||
|           key: certs.privateKey, | ||||
|           cert: certs.publicKey | ||||
|         }, | ||||
|         name: 'HTTPS Route' | ||||
|       }) | ||||
| @@ -173,9 +173,9 @@ tap.test('SmartProxy: Should create instance with route-based config', async () | ||||
|   }); | ||||
|  | ||||
|   // Simply verify the instance was created successfully | ||||
|   expect(proxy).to.be.an('object'); | ||||
|   expect(proxy.start).to.be.a('function'); | ||||
|   expect(proxy.stop).to.be.a('function'); | ||||
|   expect(typeof proxy).toEqual('object'); | ||||
|   expect(typeof proxy.start).toEqual('function'); | ||||
|   expect(typeof proxy.stop).toEqual('function'); | ||||
| }); | ||||
|  | ||||
| export default tap.start(); | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@push.rocks/smartproxy', | ||||
|   version: '13.1.3', | ||||
|   description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.' | ||||
|   version: '15.0.0', | ||||
|   description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user