UNPKG

counterfact

Version:

Generate a TypeScript-based mock server from an OpenAPI spec in seconds — with stateful routes, hot reload, and REPL support.

72 lines (71 loc) 2.83 kB
import { Readable } from "node:stream"; import createDebug from "debug"; import Koa from "koa"; import bodyParser from "koa-bodyparser"; import { koaSwagger } from "koa2-swagger-ui"; import { adminApiMiddleware } from "./admin-api-middleware.js"; import { routesMiddleware } from "./routes-middleware.js"; import { openapiMiddleware } from "./openapi-middleware.js"; const debug = createDebug("counterfact:server:create-koa-app"); /** * Builds and configures the Koa application with all built-in middleware. * * The middleware stack (in order) is: * 1. Per runner: OpenAPI document serving at `/counterfact/openapi${runner.subdirectory}` * 2. Per runner: Swagger UI at `/counterfact/swagger${runner.subdirectory}` * 3. Per runner: Admin API (when `config.startAdminApi` is `true`) at `/_counterfact/api${runner.subdirectory}` * 4. Redirect `/counterfact` → `/counterfact/swagger` * 5. Body parser * 6. JSON serialisation of object bodies * 7. Per runner: Route-dispatching middleware at `runner.prefix` * * @param runners - The ApiRunner instances, one per API spec. * @param config - Server configuration. * @returns A configured Koa application (not yet listening). */ export function createKoaApp({ runners, config, }) { const app = new Koa(); for (const runner of runners) { app.use(openapiMiddleware(`/counterfact/openapi${runner.subdirectory}`, { path: runner.openApiPath, baseUrl: `//localhost:${config.port}${runner.prefix}`, overlays: runner.overlays, })); app.use(koaSwagger({ routePrefix: `/counterfact/swagger${runner.subdirectory}`, swaggerOptions: { url: `/counterfact/openapi${runner.subdirectory}`, }, })); if (config.startAdminApi) { app.use(adminApiMiddleware(`/_counterfact/api${runner.subdirectory}`, runner.registry, runner.contextRegistry, config)); } } debug("basePath: %s", config.basePath); app.use(async (ctx, next) => { if (ctx.URL.pathname === "/counterfact") { ctx.redirect("/counterfact/swagger"); return; } await next(); }); app.use(bodyParser()); app.use(async (ctx, next) => { await next(); if (ctx.body !== null && ctx.body !== undefined && typeof ctx.body === "object" && !Buffer.isBuffer(ctx.body) && !(ctx.body instanceof Readable)) { ctx.body = JSON.stringify(ctx.body, null, 2); ctx.type = "application/json"; } }); for (const runner of runners) { app.use(routesMiddleware(runner.prefix, runner.dispatcher, { proxyPaths: config.proxyPaths, proxyUrl: config.proxyUrl, })); } return app; }