UNPKG

@equinor/fusion-framework-vite-plugin-api-service

Version:

Vite plugin for proxying service discovery and mocking

99 lines 4.43 kB
import { DEFAULT_VALUES } from './constants.js'; import { createResponseInterceptor } from './create-response-interceptor.js'; /** * Creates an {@link ApiProxyHandler} that proxies requests to a service * discovery endpoint, transforms the response using `generateRoutes`, and * dynamically registers the resulting API routes for subsequent proxying. * * The returned handler is intended to be passed as the `proxyHandler` argument * to the {@link plugin} factory. On each service discovery response the * handler re-generates its route table, so route changes are picked up * automatically. * * @template TResponse - Shape of the raw JSON response from the discovery endpoint. * @template TResult - Shape of the transformed JSON sent back to the client. * * @param target - The URL of the upstream service discovery endpoint. * @param generateRoutes - Callback that processes the discovery response and * returns the client payload and the API routes to register. * @param args - Optional configuration. * @param args.route - Route path where the discovery proxy is mounted. * Defaults to {@link DEFAULT_VALUES.SERVICES_PATH}. * @param args.apiRoute - Base path prepended to generated API routes. * Defaults to {@link DEFAULT_VALUES.API_PATH}. * @param args.proxyOptions - Extra `http-proxy` options merged into the * generated proxy configuration. * @param args.logger - Logger for proxy diagnostics. * @returns An {@link ApiProxyHandler} with `route`, `routes`, and * `createProxyOptions` members. * * @example * ```ts * import { createProxyHandler } from '@equinor/fusion-framework-vite-plugin-api-service'; * * const handler = createProxyHandler( * 'https://discovery.example.com/services', * (services, { route }) => { * const routes = services.map((svc) => ({ * match: `${route}/${svc.name}/:path*`, * proxy: { target: svc.url, rewrite: (p) => p.replace(`${route}/${svc.name}`, '') }, * })); * return { data: services, routes }; * }, * ); * ``` */ export function createProxyHandler(target, generateRoutes, args) { const { route = DEFAULT_VALUES.SERVICES_PATH, apiRoute = DEFAULT_VALUES.API_PATH, proxyOptions = {}, logger, } = args ?? {}; // Initialize routes - this will be updated on each response let apiRoutes; return { get route() { return route; }, get routes() { return apiRoutes || []; }, createProxyOptions: (...args) => { // Resolve the provided options const providedOptions = typeof proxyOptions === 'function' ? proxyOptions(...args) : proxyOptions; // Configure the proxy server const configure = (proxyServer) => { proxyServer.on('proxyRes', (proxyRes, req, res) => { // initialize the interceptor since the `generateRoutes` requires the request const interceptor = createResponseInterceptor((data) => { const { data: transformedData, routes } = generateRoutes(data, { route: apiRoute, request: req, }); apiRoutes = routes; return transformedData; }, { logger, }); // execute the interceptor interceptor(proxyRes, req, res); }); proxyServer.on('proxyReq', (proxyReq, req) => { logger?.info(`Proxying ${req.originalUrl} -> ${proxyReq.protocol}//${proxyReq.host}${proxyReq.path}`); }); proxyServer.on('error', (err) => { logger?.error(`proxy for ${apiRoute} to ${target} failed: ${err.message}`); }); }; return { target, // default options changeOrigin: true, secure: process.env.NODE_ENV === 'production', rewrite: (path) => path.replace(new RegExp(`^${route}`), ''), // provided options ...providedOptions, // custom options - required for response transformation selfHandleResponse: true, configure, }; }, }; } //# sourceMappingURL=create-proxy-handler.js.map