@the-teacher/the-router
Version:
Simple router for Express.js, making routes and actions easy to manage.
369 lines (360 loc) • 11 kB
JavaScript
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