UNPKG

@the-teacher/the-router

Version:

Simple router for Express.js, making routes and actions easy to manage.

369 lines (360 loc) 11 kB
import { createRequire } from 'module'; const require = createRequire(import.meta.url); // src.ts/base.ts import { Router } from "express"; // src.ts/router-state.ts var DEFAULT_ACTIONS_PATH = "src/actions"; var createRouterState = () => { return { router: null, currentScope: null, scopeMiddlewares: [], actionsPath: DEFAULT_ACTIONS_PATH, isCustomPath: false, routerOptions: {}, routesMap: /* @__PURE__ */ new Map() }; }; var resetRouterState = () => { return createRouterState(); }; // src.ts/base.ts var state = createRouterState(); var setRouterOptions = (options2) => { state.routerOptions = options2; }; var getRouter = () => { if (!state.router) { state.router = Router(state.routerOptions); } return state.router; }; var resetRouter = () => { state = resetRouterState(); }; var setActionsPath = (path3) => { state.isCustomPath = true; state.actionsPath = path3; return path3; }; var isCustomActionsPath = () => state.isCustomPath; var getActionsPath = () => state.actionsPath; var setRouterScope = (scope2) => { state.currentScope = scope2; }; var getScopeMiddlewares = () => state.scopeMiddlewares; var setScopeMiddlewares = (middlewares) => { state.scopeMiddlewares = middlewares; }; var routeScope = (scope2, middlewaresOrCallback, routesDefinitionCallback) => { const scopedRouter = Router(state.routerOptions); const originalRouter = state.router; const originalScope = state.currentScope; const originalScopeMiddlewares = state.scopeMiddlewares; state.router = scopedRouter; const newScope = originalScope ? `${originalScope}/${scope2}` : scope2; setRouterScope(newScope); if (Array.isArray(middlewaresOrCallback)) { setScopeMiddlewares([ ...originalScopeMiddlewares, ...middlewaresOrCallback ]); if (routesDefinitionCallback) { routesDefinitionCallback(); } } else { setScopeMiddlewares([...originalScopeMiddlewares]); middlewaresOrCallback(); } state.router = originalRouter; setRouterScope(originalScope); setScopeMiddlewares(originalScopeMiddlewares); getRouter().use(`/${scope2}`, scopedRouter); }; var addRouteToMap = (method, path3, action, middlewares = []) => { let pathString = path3 instanceof RegExp ? path3.toString() : path3; if (state.currentScope && path3 instanceof RegExp) { const regexStr = path3.toString().replace(/^\/|\/$/g, ""); pathString = `/${state.currentScope}/${regexStr}/`; } else if (typeof path3 === "string") { const normalizedPath = path3.startsWith("/") ? path3 : `/${path3}`; pathString = state.currentScope ? `/${state.currentScope}${normalizedPath}` : normalizedPath; } const routeKey = `${method.toUpperCase()}:${pathString}`; state.routesMap.set(routeKey, { method: method.toUpperCase(), path: pathString, action, middlewares }); }; var getRoutesMap = () => state.routesMap; // src.ts/utils.ts import fs from "fs"; import path from "path"; var VALID_EXTENSIONS = [".js", ".ts"]; var getProjectRoot = () => process.cwd(); var validateActionsPath = (actionsPath) => { if (!actionsPath) { throw new Error("Actions path is not set"); } if (!fs.existsSync(actionsPath) || !fs.lstatSync(actionsPath).isDirectory()) { throw new Error(`Actions path ${actionsPath} is not a directory`); } }; var resolveFullActionPath = (actionsPath, actionPath) => { if (!actionPath) { throw new Error("Action path cannot be empty"); } const actionFile = `${actionPath}Action`; return !isCustomActionsPath() ? path.join(getProjectRoot(), actionsPath, actionFile) : path.join(actionsPath, actionFile); }; var validateActionFile = (fullActionPath, validExtensions) => { for (const ext of validExtensions) { const candidatePath = `${fullActionPath}${ext}`; if (fs.existsSync(candidatePath) && fs.lstatSync(candidatePath).isFile()) { return candidatePath; } } throw new Error(`Action file ${fullActionPath} does not exist`); }; var validateActionModule = (actionModule, fullActionPath) => { if (typeof actionModule.perform !== "function") { throw new Error( `Action module at ${fullActionPath} must export a 'perform' function` ); } }; var loadActionImplementation = async (actionPath) => { const actionsPath = getActionsPath(); validateActionsPath(actionsPath); const fullActionPath = resolveFullActionPath(actionsPath, actionPath); const validActionPath = validateActionFile(fullActionPath, VALID_EXTENSIONS); const actionModule = await import(validActionPath); validateActionModule(actionModule, validActionPath); return actionModule.perform; }; var loadAction = (actionPath) => { return async (req, res) => { try { const handler = await loadActionImplementation(actionPath); return handler(req, res); } catch (error) { res.status(501).json({ error: "Action loading failed", message: "Failed to load the specified action", details: error instanceof Error ? error.message : "Unknown error" }); } }; }; // src.ts/helpers/buildRoutesSchema.ts import fs2 from "fs"; import path2 from "path"; var buildRoutesSchema = async () => { const routesMap = getRoutesMap(); const projectRoot = getProjectRoot(); const schemaDir = path2.join(projectRoot, "routes"); const schemaPath = path2.join(schemaDir, "routesSchema.md"); if (!fs2.existsSync(schemaDir)) { fs2.mkdirSync(schemaDir, { recursive: true }); } const content = generateMarkdownSchema(routesMap); fs2.writeFileSync(schemaPath, content, "utf8"); }; var generateMarkdownSchema = (routesMap) => { const lines = [ "# API Routes Schema", "", "| Method | Path | Action | Middlewares |", "|--------|------|--------|------------|" ]; const sortedRoutes = Array.from(routesMap.values()).sort((a, b) => { return a.path.localeCompare(b.path); }); for (const route of sortedRoutes) { const middlewaresCount = route.middlewares.length - 1; const middlewaresInfo = middlewaresCount > 0 ? `${middlewaresCount} middleware(s)` : "none"; lines.push( `| ${route.method} | ${route.path} | ${route.action} | ${middlewaresInfo} |` ); } return lines.join("\n"); }; // src.ts/index.ts var root = (middlewares, actionPath) => { let handlers = [...getScopeMiddlewares()]; let finalActionPath; if (Array.isArray(middlewares)) { if (!actionPath) { throw new Error("Action path is required when middlewares are provided"); } handlers = [...handlers, ...middlewares]; finalActionPath = actionPath; } else { finalActionPath = middlewares; } handlers.push(loadAction(finalActionPath)); addRouteToMap("GET", "/", finalActionPath, handlers); getRouter().get("/", ...handlers); }; var createRouteHandler = (method) => { return (urlPath, middlewares, actionPath) => { let handlers = [...getScopeMiddlewares()]; let finalActionPath; if (Array.isArray(middlewares)) { if (!actionPath) { throw new Error( "Action path is required when middlewares are provided" ); } handlers = [...handlers, ...middlewares]; finalActionPath = actionPath; } else { finalActionPath = middlewares; } handlers.push(loadAction(finalActionPath)); const router = getRouter(); const path3 = urlPath instanceof RegExp ? urlPath : urlPath.startsWith("/") ? urlPath : `/${urlPath}`; addRouteToMap(method, urlPath, finalActionPath, handlers); switch (method) { case "get": router.get(path3, ...handlers); break; case "post": router.post(path3, ...handlers); break; case "put": router.put(path3, ...handlers); break; case "patch": router.patch(path3, ...handlers); break; case "delete": router.delete(path3, ...handlers); break; case "options": router.options(path3, ...handlers); break; case "head": router.head(path3, ...handlers); break; case "all": router.all(path3, ...handlers); break; default: throw new Error(`Unsupported HTTP method: ${method}`); } }; }; var get = createRouteHandler("get"); var post = createRouteHandler("post"); var put = createRouteHandler("put"); var patch = createRouteHandler("patch"); var destroy = createRouteHandler("delete"); var options = createRouteHandler("options"); var head = createRouteHandler("head"); var all = createRouteHandler("all"); var resources = (resourceName, middlewaresOrOptions, options2) => { let middlewares = []; let resourceOptions = {}; if (Array.isArray(middlewaresOrOptions)) { middlewares = middlewaresOrOptions; resourceOptions = options2 || {}; } else { resourceOptions = middlewaresOrOptions || {}; } const { only, except } = resourceOptions; const allActions = [ "index", "new", "create", "show", "edit", "update", "destroy" ]; let actions = allActions; if (only) { actions = only; } else if (except) { actions = allActions.filter((action) => !except.includes(action)); } const normalizedName = resourceName.toLowerCase(); const basePath = `/${normalizedName}`; const router = getRouter(); if (actions.includes("new")) { router.get( basePath + "/new", ...createHandlers(middlewares, normalizedName, "new") ); } if (actions.includes("edit")) { router.get( basePath + "/:id/edit", ...createHandlers(middlewares, normalizedName, "edit") ); } if (actions.includes("index")) { router.get( basePath, ...createHandlers(middlewares, normalizedName, "index") ); } if (actions.includes("create")) { router.post( basePath, ...createHandlers(middlewares, normalizedName, "create") ); } if (actions.includes("show")) { router.get( basePath + "/:id", ...createHandlers(middlewares, normalizedName, "show") ); } if (actions.includes("update")) { router.put( basePath + "/:id", ...createHandlers(middlewares, normalizedName, "update") ); router.patch( basePath + "/:id", ...createHandlers(middlewares, normalizedName, "update") ); } if (actions.includes("destroy")) { router.delete( basePath + "/:id", ...createHandlers(middlewares, normalizedName, "destroy") ); } }; var createHandlers = (middlewares, resourcePath, action) => { const handlers = [...getScopeMiddlewares(), ...middlewares]; const fullActionPath = `${resourcePath}/${action}`; handlers.push(loadAction(fullActionPath)); return handlers; }; var scope = routeScope; export { all, buildRoutesSchema, destroy, get, getActionsPath, getRouter, head, options, patch, post, put, resetRouter, resources, root, routeScope, scope, setActionsPath, setRouterOptions }; //# sourceMappingURL=index.js.map