@compas/code-gen
Version:
Generate various boring parts of your server
171 lines (147 loc) • 4.5 kB
JavaScript
// @ts-nocheck
import { dirnameForModule, isNil, pathJoin } from "@compas/stdlib";
import { AnyType, ObjectType, TypeCreator } from "../../builders/index.js";
import { structureIteratorNamedTypes } from "../../structure/structureIterators.js";
import { compileTemplateDirectory, executeTemplate } from "../../template.js";
import { getTypeNameForType } from "../types.js";
import { buildTrie } from "./trie.js";
/**
* @param {import("../../generated/common/types").CodeGenContext} context
*/
export function generateRouterFiles(context) {
// TODO: Handle validators
const routeTrie = buildTrie(context.structure);
const routeTags = buildRouteTags(context.structure);
compileTemplateDirectory(
pathJoin(dirnameForModule(import.meta), "templates"),
".tmpl",
);
const groupMiddleware = new ObjectType("group", "middleware")
.keys({})
.build();
groupMiddleware.uniqueName = "GroupMiddleware";
for (const group of Object.keys(context.structure)) {
const groupStructure = context.structure[group];
const hasRouteType = Object.values(groupStructure).find(
(it) => it.type === "route",
);
if (isNil(hasRouteType)) {
continue;
}
const contents = executeTemplate("routerGroupFile", {
groupStructure,
groupName: group,
});
context.outputFiles.push({
contents,
relativePath: `./${group}/controller${context.extension}`,
});
for (const route of Object.values(groupStructure)) {
if (route.type !== "route") {
continue;
}
const fileTypeName = route.files
? getTypeNameForType(context, route.files.reference, "Validated", {
fileTypeIO: "outputRouter",
})
: "";
getTypeNameForType(
context,
{
type: "any",
uniqueName: `${route.uniqueName}Ctx`,
rawValue: `Context<{}, {
event: InsightEvent,
log: Logger,
${
route.query
? `validatedQuery: ${route.query.reference.uniqueName},`
: ""
}
${
route.params
? `validatedParams: ${route.params.reference.uniqueName},`
: ""
}
${
route.body
? `validatedBody: ${route.body.reference.uniqueName},`
: ""
}
${route.files ? `validatedFiles: ${fileTypeName},` : ""}
}, ${
route.response ? route.response.reference.uniqueName : `unknown`
}>`,
rawValueImport: {
typeScript: `import { ParameterizedContext as Context } from "koa";\nimport { InsightEvent, Logger } from "@compas/stdlib";`,
},
},
"",
{},
);
getTypeNameForType(
context,
{
type: "any",
uniqueName: `${route.uniqueName}Fn`,
rawValue: `(ctx: ${route.uniqueName}Ctx, next: Next) => (void|Promise<void>)`,
rawValueImport: {
typeScript: `import { Next } from "@compas/server";`,
},
},
"",
{},
);
}
groupMiddleware.keys[group] = new AnyType()
.raw("Middleware|Middleware[]", {
typeScript: `import { Middleware } from "@compas/server";`,
})
.build();
}
getTypeNameForType(context, groupMiddleware, "", {});
const contents = executeTemplate("routerFile", {
routeTrie,
routeTags,
extension: context.extension,
importExtension: context.extension,
structure: context.structure,
options: context.options,
});
context.outputFiles.push({
contents,
relativePath: `./common/router${context.extension}`,
});
}
/**
* @param {GenerateOpts} options
*/
export function getInternalRoutes(options) {
const T = new TypeCreator("compas");
const G = T.router("_compas/");
const tags = ["_compas"];
const result = [];
if (options.dumpApiStructure) {
result.push(
G.get("structure.json", "structure")
.response(T.any())
.tags(...tags)
.docs("Return the full generated structure as a json object."),
);
}
return result;
}
/**
* @param {import("../../generated/common/types").CodeGenStructure} data
*/
function buildRouteTags(data) {
const set = new Set();
for (const item of structureIteratorNamedTypes(data)) {
if (item.type === "route" && "tags" in item) {
for (const t of item.tags) {
set.add(t);
}
}
}
return [...set];
}