UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

182 lines (181 loc) 5.77 kB
import { $hook, $inject, $module, Alepha, AlephaError, KIND, Primitive, createPrimitive } from "alepha"; import { AlephaServer, ServerRouterProvider, routeMethods } from "alepha/server"; import { ReadableStream } from "node:stream/web"; import { $logger } from "alepha/logger"; //#region ../../src/server/proxy/primitives/$proxy.ts /** * Creates a proxy primitive to forward requests to another server. * * This primitive enables you to create reverse proxy functionality, allowing your Alepha server * to forward requests to other services while maintaining a unified API surface. It's particularly * useful for microservice architectures, API gateways, or when you need to aggregate multiple * services behind a single endpoint. * * **Key Features** * * - **Path-based routing**: Match specific paths or patterns to proxy * - **Dynamic targets**: Support both static and dynamic target resolution * - **Request/Response hooks**: Modify requests before forwarding and responses after receiving * - **URL rewriting**: Transform URLs before forwarding to the target * - **Conditional proxying**: Enable/disable proxies based on environment or conditions * * @example * **Basic proxy setup:** * ```ts * import { $proxy } from "alepha/server/proxy"; * * class ApiGateway { * // Forward all /api/* requests to external service * api = $proxy({ * path: "/api/*", * target: "https://api.example.com" * }); * } * ``` * * @example * **Dynamic target with environment-based routing:** * ```ts * class ApiGateway { * // Route to different environments based on configuration * api = $proxy({ * path: "/api/*", * target: () => process.env.NODE_ENV === "production" * ? "https://api.prod.example.com" * : "https://api.dev.example.com" * }); * } * ``` * * @example * **Advanced proxy with request/response modification:** * ```ts * class SecureProxy { * secure = $proxy({ * path: "/secure/*", * target: "https://secure-api.example.com", * beforeRequest: async (request, proxyRequest) => { * // Add authentication headers * proxyRequest.headers = { * ...proxyRequest.headers, * 'Authorization': `Bearer ${await getServiceToken()}`, * 'X-Forwarded-For': request.headers['x-forwarded-for'] || request.ip * }; * }, * afterResponse: async (request, proxyResponse) => { * // Log response for monitoring * console.log(`Proxied ${request.url} -> ${proxyResponse.status}`); * }, * rewrite: (url) => { * // Remove /secure prefix when forwarding * url.pathname = url.pathname.replace('/secure', ''); * } * }); * } * ``` * * @example * **Conditional proxy based on feature flags:** * ```ts * class FeatureProxy { * newApi = $proxy({ * path: "/v2/*", * target: "https://new-api.example.com", * disabled: !process.env.ENABLE_V2_API // Disable if feature flag is off * }); * } * ``` */ const $proxy = (options) => { return createPrimitive(ProxyPrimitive, options); }; var ProxyPrimitive = class extends Primitive {}; $proxy[KIND] = ProxyPrimitive; //#endregion //#region ../../src/server/proxy/providers/ServerProxyProvider.ts var ServerProxyProvider = class { log = $logger(); routerProvider = $inject(ServerRouterProvider); alepha = $inject(Alepha); configure = $hook({ on: "configure", handler: () => { for (const proxy of this.alepha.primitives($proxy)) this.createProxy(proxy.options); } }); createProxy(options) { if (options.disabled) return; const path = options.path; const target = typeof options.target === "function" ? options.target() : options.target; if (!path.endsWith("/*")) throw new AlephaError("Proxy path should end with '/*'"); const handler = this.createProxyHandler(target, options); for (const method of routeMethods) this.routerProvider.createRoute({ method, path, handler }); this.log.info("Proxying", { path, target }); } createProxyHandler(target, options) { return async (request) => { const url = new URL(target + request.url.pathname); if (request.url.search) url.search = request.url.search; options.rewrite?.(url); const requestInit = { url: url.toString(), method: request.method, headers: { ...request.headers, "accept-encoding": "identity" }, body: this.getRawRequestBody(request) }; if (requestInit.body) requestInit.duplex = "half"; if (options.beforeRequest) await options.beforeRequest(request, requestInit); this.log.debug("Proxying request", { url: url.toString(), method: request.method, headers: request.headers }); const response = await fetch(requestInit.url, requestInit); request.reply.status = response.status; request.reply.headers = Object.fromEntries(response.headers.entries()); request.reply.body = response.body; this.log.debug("Received response", { status: request.reply.status, headers: request.reply.headers }); if (options.afterResponse) await options.afterResponse(request, response); }; } getRawRequestBody(req) { const { method } = req; if (method === "GET" || method === "HEAD" || method === "OPTIONS") return; if (req.raw?.web?.req) return req.raw.web.req.body; if (req.raw?.node?.req) { const nodeReq = req.raw.node.req; return ReadableStream.from(nodeReq); } } }; //#endregion //#region ../../src/server/proxy/index.ts /** * Reverse proxy routing. * * **Features:** * - Proxy configuration and routing * * @module alepha.server.proxy */ const AlephaServerProxy = $module({ name: "alepha.server.proxy", primitives: [$proxy], services: [AlephaServer, ServerProxyProvider] }); //#endregion export { $proxy, AlephaServerProxy, ProxyPrimitive, ServerProxyProvider }; //# sourceMappingURL=index.js.map