UNPKG

apitally

Version:

Simple API monitoring & analytics for REST APIs built with Express, Fastify, NestJS, AdonisJS, Hono, H3, Elysia, Hapi, and Koa.

1 lines 14 kB
{"version":3,"sources":["../../src/express/utils.js"],"sourcesContent":["// Adapted from https://github.com/AlbertoFdzM/express-list-endpoints/blob/305535d43008b46f34e18b01947762e039af6d2d/src/index.js\n// and also incorporated https://github.com/AlbertoFdzM/express-list-endpoints/pull/96\n// and https://github.com/labithiotis/express-list-routes/blob/5432c83a67c2788c56a1cdfc067ec809961c0c05/index.js\n\n/**\n * @typedef {Object} Route\n * @property {Object} methods\n * @property {string | string[]} path\n * @property {any[]} stack\n *\n * @typedef {Object} Endpoint\n * @property {string} path Path name\n * @property {string[]} methods Methods handled\n * @property {string[]} middlewares Mounted middlewares\n */\n\nconst regExpToParseExpressPathRegExp =\n /^\\/\\^\\\\?\\/?(?:(:?[\\w\\\\.-]*(?:\\\\\\/:?[\\w\\\\.-]*)*)|(\\(\\?:\\\\?\\/?\\([^)]+\\)\\)))\\\\\\/.*/;\nconst regExpToReplaceExpressPathRegExpParams = /\\(\\?:\\\\?\\/?\\([^)]+\\)\\)/;\nconst regexpExpressParamRegexp = /\\(\\?:\\\\?\\\\?\\/?\\([^)]+\\)\\)/g;\nconst regexpExpressPathParamRegexp = /(:[^)]+)\\([^)]+\\)/g;\n\nconst EXPRESS_ROOT_PATH_REGEXP_VALUE = \"/^\\\\/?(?=\\\\/|$)/i\";\nconst STACK_ITEM_VALID_NAMES = [\"router\", \"bound dispatch\", \"mounted_app\"];\n\n/**\n * Detects Express version and returns router information\n * @param {import('express').Express | import('express').Router | any} app\n * @returns {{stack: any[] | null, version: 'v4' | 'v5'}}\n */\nexport const getRouterInfo = function (app) {\n if (app.stack) {\n // Express 4 router\n return { stack: app.stack, version: \"v4\" };\n } else if (app._router?.stack) {\n // Express 4\n return { stack: app._router.stack, version: \"v4\" };\n } else if (app.router?.stack) {\n // Express 5\n return { stack: app.router.stack, version: \"v5\" };\n }\n return { stack: null, version: \"v4\" };\n};\n\n/**\n * Returns all the verbs detected for the passed route\n * @param {Route} route\n */\nconst getRouteMethods = function (route) {\n let methods = Object.keys(route.methods);\n\n methods = methods.filter((method) => method !== \"_all\");\n methods = methods.map((method) => method.toUpperCase());\n\n return methods;\n};\n\n/**\n * Returns the names (or anonymous) of all the middlewares attached to the\n * passed route\n * @param {Route} route\n * @returns {string[]}\n */\nconst getRouteMiddlewares = function (route) {\n return route.stack.map((item) => {\n return item.handle.name || \"anonymous\";\n });\n};\n\n/**\n * Returns true if found regexp related with express params\n * @param {string} expressPathRegExp\n * @returns {boolean}\n */\nconst hasParams = function (expressPathRegExp) {\n return regexpExpressParamRegexp.test(expressPathRegExp);\n};\n\n/**\n * @param {Route} route Express route object to be parsed\n * @param {string} basePath The basePath the route is on\n * @return {Endpoint[]} Endpoints info\n */\nconst parseExpressRoute = function (route, basePath) {\n const paths = [];\n\n if (Array.isArray(route.path)) {\n paths.push(...route.path);\n } else {\n paths.push(route.path);\n }\n\n /** @type {Endpoint[]} */\n const endpoints = paths.map((path) => {\n const completePath =\n basePath && path === \"/\" ? basePath : `${basePath}${path}`;\n\n /** @type {Endpoint} */\n const endpoint = {\n path: completePath.replace(regexpExpressPathParamRegexp, \"$1\"),\n methods: getRouteMethods(route),\n middlewares: getRouteMiddlewares(route),\n };\n\n return endpoint;\n });\n\n return endpoints;\n};\n\n/**\n * @param {RegExp} expressPathRegExp\n * @param {any[]} keys\n * @returns {string}\n */\nexport const parseExpressPathRegExp = function (expressPathRegExp, keys) {\n let parsedRegExp = expressPathRegExp.toString();\n let expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n let paramIndex = 0;\n\n while (hasParams(parsedRegExp)) {\n const paramName = keys[paramIndex].name;\n const paramId = `:${paramName}`;\n\n parsedRegExp = parsedRegExp.replace(\n regExpToReplaceExpressPathRegExpParams,\n (str) => {\n // Express >= 4.20.0 uses a different RegExp for parameters: it\n // captures the slash as part of the parameter. We need to check\n // for this case and add the slash to the value that will replace\n // the parameter in the path.\n if (str.startsWith(\"(?:\\\\/\")) {\n return `\\\\/${paramId}`;\n }\n\n return paramId;\n },\n );\n\n paramIndex++;\n }\n\n if (parsedRegExp !== expressPathRegExp.toString()) {\n expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n }\n\n return expressPathRegExpExec[1].replace(/\\\\\\//g, \"/\");\n};\n\n/**\n * @param {string} expressPath\n * @param {Object.<string, string>} params\n * @returns {string}\n */\nexport const parseExpressPath = function (expressPath, params) {\n let result = expressPath;\n for (const [paramName, paramValue] of Object.entries(params)) {\n result = result.replace(paramValue, `:${paramName}`);\n }\n return result;\n};\n\n/**\n * @param {import('express').Express | import('express').Router | any} app\n * @param {string} [basePath]\n * @param {Endpoint[]} [endpoints]\n * @returns {Endpoint[]}\n */\nconst parseEndpoints = function (app, basePath, endpoints) {\n const routerInfo = getRouterInfo(app);\n const stack = routerInfo.stack;\n const version = routerInfo.version;\n\n endpoints = endpoints || [];\n basePath = basePath || \"\";\n\n if (!stack) {\n if (endpoints.length) {\n endpoints = addEndpoints(endpoints, [\n {\n path: basePath,\n methods: [],\n middlewares: [],\n },\n ]);\n }\n } else {\n endpoints = parseStack(stack, basePath, endpoints, version);\n }\n\n return endpoints;\n};\n\n/**\n * Ensures the path of the new endpoints isn't yet in the array.\n * If the path is already in the array merges the endpoints with the existing\n * one, if not, it adds them to the array.\n *\n * @param {Endpoint[]} currentEndpoints Array of current endpoints\n * @param {Endpoint[]} endpointsToAdd New endpoints to be added to the array\n * @returns {Endpoint[]} Updated endpoints array\n */\nconst addEndpoints = function (currentEndpoints, endpointsToAdd) {\n endpointsToAdd.forEach((newEndpoint) => {\n const existingEndpoint = currentEndpoints.find(\n (endpoint) => endpoint.path === newEndpoint.path,\n );\n\n if (existingEndpoint !== undefined) {\n const newMethods = newEndpoint.methods.filter(\n (method) => !existingEndpoint.methods.includes(method),\n );\n\n existingEndpoint.methods = existingEndpoint.methods.concat(newMethods);\n } else {\n currentEndpoints.push(newEndpoint);\n }\n });\n\n return currentEndpoints;\n};\n\n/**\n * @param {any[]} stack\n * @param {string} basePath\n * @param {Endpoint[]} endpoints\n * @param {'v4' | 'v5'} [version]\n * @returns {Endpoint[]}\n */\nconst parseStack = function (stack, basePath, endpoints, version) {\n stack.forEach((stackItem) => {\n if (stackItem.route) {\n const newEndpoints = parseExpressRoute(stackItem.route, basePath);\n\n endpoints = addEndpoints(endpoints, newEndpoints);\n } else if (STACK_ITEM_VALID_NAMES.includes(stackItem.name)) {\n let newBasePath = basePath;\n\n if (version === \"v4\") {\n const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(\n stackItem.regexp,\n );\n if (isExpressPathRegExp) {\n const parsedPath = parseExpressPathRegExp(\n stackItem.regexp,\n stackItem.keys,\n );\n newBasePath += `/${parsedPath}`;\n } else if (\n !stackItem.path &&\n stackItem.regexp &&\n stackItem.regexp.toString() !== EXPRESS_ROOT_PATH_REGEXP_VALUE\n ) {\n const regExpPath = `RegExp(${stackItem.regexp})`;\n newBasePath += `/${regExpPath}`;\n }\n } else if (version === \"v5\") {\n if (!stackItem.path) {\n return;\n } else if (stackItem.path !== \"/\") {\n newBasePath += stackItem.path.startsWith(\"/\")\n ? stackItem.path\n : `/${stackItem.path}`;\n }\n }\n\n endpoints = parseEndpoints(stackItem.handle, newBasePath, endpoints);\n }\n });\n\n return endpoints;\n};\n\nexport const getEndpoints = function (app, basePath) {\n const endpoints = parseEndpoints(app);\n const standardHttpMethods = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"];\n return endpoints.flatMap((route) =>\n route.methods\n .filter((method) => standardHttpMethods.includes(method.toUpperCase()))\n .map((method) => ({\n method,\n path: (basePath + route.path).replace(/\\/\\//g, \"/\"),\n })),\n );\n};\n"],"mappings":"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","names":["regExpToParseExpressPathRegExp","regExpToReplaceExpressPathRegExpParams","regexpExpressParamRegexp","regexpExpressPathParamRegexp","EXPRESS_ROOT_PATH_REGEXP_VALUE","STACK_ITEM_VALID_NAMES","getRouterInfo","app","stack","version","_router","router","getRouteMethods","route","methods","Object","keys","filter","method","map","toUpperCase","getRouteMiddlewares","item","handle","name","hasParams","expressPathRegExp","test","parseExpressRoute","basePath","paths","Array","isArray","path","push","endpoints","completePath","endpoint","replace","middlewares","parseExpressPathRegExp","parsedRegExp","toString","expressPathRegExpExec","exec","paramIndex","paramName","paramId","str","startsWith","parseExpressPath","expressPath","params","result","paramValue","entries","parseEndpoints","routerInfo","length","addEndpoints","parseStack","currentEndpoints","endpointsToAdd","forEach","newEndpoint","existingEndpoint","find","undefined","newMethods","includes","concat","stackItem","newEndpoints","newBasePath","isExpressPathRegExp","regexp","parsedPath","regExpPath","getEndpoints","standardHttpMethods","flatMap"]}