@equinor/fusion-framework-vite-plugin-api-service
Version:
Vite plugin for proxying service discovery and mocking
141 lines (132 loc) • 4.67 kB
text/typescript
import type ProxyServer from 'http-proxy';
import type {
ApiRoute,
JsonData,
ApiProxyHandler,
PluginLogger,
IncomingRequest,
} from './types.js';
import { DEFAULT_VALUES } from './constants.js';
import { createResponseInterceptor } from './create-response-interceptor.js';
import type { Connect } from 'vite';
/**
* A type representing a function that processes API response data and transforms it
* into a new format along with associated API routes.
*
* @template TResponse - The type of the input JSON data received from the API.
* @template TResult - The type of the transformed JSON data to be returned.
*
* @param data - The input JSON data of type `TResponse` to be processed.
* @returns An object containing:
* - `data`: The transformed JSON data of type `TResult`.
* - `routes`: An array of `ApiRoute` objects associated with the processed data.
*/
export type ApiDataProcessor<
TResponse extends JsonData = JsonData,
TResult extends JsonData = TResponse,
> = (
data: TResponse,
args: {
route: string;
request: Connect.IncomingMessage;
},
) => {
data: TResult;
routes?: ApiRoute[];
};
/**
* Creates a proxy handler for API services, allowing for route resolution and proxy configuration.
*
* @template TResponse - The type of the response data from the API.
* @template TResult - The type of the processed result data. Defaults to `TResponse`.
*
* @param handle - A function that processes the API response and returns the processed data
* along with updated API routes.
* @param options - Optional configuration for the proxy handler.
* @param options.proxy - Additional proxy options to customize the behavior of the proxy.
* @param options.route - The base route for the proxy. Defaults to `'/@services'`.
*
* @returns An object implementing the `ApiProxyHandler` interface, which includes:
* - `route`: The base route for the proxy.
* - `createProxyOptions`: A function to generate proxy options, including default and custom configurations.
* - `resolveRoute`: A function to resolve a route based on the current API routes.
*/
export function createProxyHandler<
TResponse extends JsonData,
TResult extends JsonData = TResponse,
>(
target: string,
generateRoutes: ApiDataProcessor<TResponse, TResult>,
args?: {
route?: string;
apiRoute?: string;
proxyOptions?: Omit<
ApiProxyHandler['createProxyOptions'] | ReturnType<ApiProxyHandler['createProxyOptions']>,
'target' | 'transformResponse' | 'selfHandleResponse'
>;
logger?: PluginLogger;
},
): ApiProxyHandler {
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: ApiRoute[] | undefined;
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) => {
proxyServer.on('proxyRes', (proxyRes, req, res) => {
// initialize the interceptor since the `generateRoutes` requires the request
const interceptor = createResponseInterceptor(
(data: TResponse) => {
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: IncomingRequest) => {
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,
};
},
};
}