alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
182 lines (181 loc) • 5.77 kB
JavaScript
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