UNPKG

fake-api-middleware

Version:

Express/Connect middleware for dummy responses

213 lines (206 loc) 5.93 kB
// src/middleware.ts import { ServerResponse } from "http"; import * as querystring from "querystring"; import bodyParse from "co-body"; // src/responsesLoader.ts import { EventEmitter } from "events"; import * as chokidar from "chokidar"; // src/helpers.ts var delay = (time) => { return new Promise((r) => { setTimeout(r, time); }); }; var isString = (value) => { return typeof value === "string" || value instanceof String; }; var debounce = (func, wait, immediate = false) => { let timeout = null; return (...args) => { const later = () => { timeout = null; if (!immediate) { func.apply(void 0, args); } }; const callNow = immediate && !timeout; if (timeout) { clearTimeout(timeout); } timeout = setTimeout(later, wait); if (callNow) { func.apply(void 0, args); } }; }; // src/module.ts import * as path from "path"; import * as fs from "fs"; import * as module from "module"; import { build as esbuildBuild } from "esbuild"; var _require = module.createRequire(process.cwd()); var executeModule = (filePath, bundledCode) => { filePath = path.resolve(process.cwd(), filePath); const extension = path.extname(filePath); const extensions = module.Module._extensions; const defaultLoader = extensions[extension]; extensions[extension] = (module2, filename) => { if (filename === filePath) { module2._compile(bundledCode, filename); } else { defaultLoader(module2, filename); } }; if (_require && _require.cache) { delete _require.cache[filePath]; } const raw = _require(filePath); const config = raw.__esModule ? raw.default : raw; if (defaultLoader) { extensions[extension] = defaultLoader; } return config; }; var extendedRequire = async (filePath) => { const pkg = JSON.parse( fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8") ); const srcCode = await (async () => { const result = await esbuildBuild({ entryPoints: [filePath], outfile: "out.js", write: false, platform: "node", bundle: true, format: "cjs", metafile: true, target: "es2015", external: [ "esbuild", ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {}), ...Object.keys(pkg.peerDependencies || {}) ], logLevel: "silent" }); const { text } = result.outputFiles[0]; return text; })(); return await executeModule(filePath, srcCode); }; // src/responsesLoader.ts var ResponsesLoader = class extends EventEmitter { constructor({ responsesFile, watchFiles }) { super(); this.responsesFile = responsesFile; if (isString(watchFiles)) { watchFiles = [watchFiles]; } this.watchFiles = watchFiles || []; chokidar.watch([this.responsesFile, ...this.watchFiles]).on( "all", debounce(async () => { try { const responsesConfig = await extendedRequire(this.responsesFile); this.emit("update", responsesConfig); } catch (e) { this.emit("error", e); } }, 100) ); } }; // src/middleware.ts import { match } from "path-to-regexp"; var middleware = (middlewareOptions) => { const prepareResponses = (responses) => { const result = []; for (const [key, response] of Object.entries(responses)) { const [method, apiPath] = key.split(" "); result.push({ method, apiPath, matchUrlFn: match(apiPath, { decode: decodeURIComponent }), response }); } return result; }; let preparedResponses = prepareResponses(middlewareOptions.responses || {}); if (middlewareOptions.responsesFile) { const responsesConfigLoader = new ResponsesLoader({ responsesFile: middlewareOptions.responsesFile, watchFiles: middlewareOptions.watchFiles }); let wasErrorLastTime = false; responsesConfigLoader.on("update", (newResponses) => { preparedResponses = prepareResponses(newResponses); if (wasErrorLastTime) { console.info("[FakeResponses]", "Responses successfully loaded"); } }); responsesConfigLoader.on("error", (err) => { console.error("[FakeResponses]", err); wasErrorLastTime = true; }); } return async (req, res, next) => { if (middlewareOptions.enable !== void 0 && !middlewareOptions.enable) { return next(); } for (const { method, matchUrlFn, response } of preparedResponses) { const [url, queryStr] = req.url.split("?"); const matchResult = matchUrlFn(url); if (matchResult && req.method === method) { if (middlewareOptions.responseDelay) { await delay(middlewareOptions.responseDelay); } let body = {}; try { body = await bodyParse(req); } catch { } if (typeof response === "function") { const responseResult = await response({ body, query: querystring.parse(queryStr), headers: req.headers, params: matchResult.params, req, res }); if (responseResult === void 0) { return; } if (responseResult instanceof ServerResponse) { return responseResult; } res.setHeader("Content-Type", "application/json"); return res.end(JSON.stringify(responseResult)); } res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(response)); } } return next(); }; }; // src/vitePlugin.ts var vitePlugin = (middlewareOptions) => { return { name: "vite-plugin-fake-response", configureServer(server) { server.middlewares.use(middleware(middlewareOptions)); } }; }; export { debounce, delay, isString, middleware, vitePlugin };