UNPKG

@autobe/agent

Version:

AI backend server code generator

327 lines (325 loc) 15.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AutoBeRealizeOperationProgrammer = void 0; const utils_1 = require("@autobe/utils"); const utils_2 = require("@typia/utils"); const AutoBeRealizeCollectorProgrammer_1 = require("./AutoBeRealizeCollectorProgrammer"); const AutoBeRealizeTransformerProgrammer_1 = require("./AutoBeRealizeTransformerProgrammer"); const resolvePropertyTransformer_1 = require("./internal/resolvePropertyTransformer"); const writeRealizeOperationTemplate_1 = require("./internal/writeRealizeOperationTemplate"); var AutoBeRealizeOperationProgrammer; (function (AutoBeRealizeOperationProgrammer) { /** * Check if the operation is a public auth operation (login, join, refresh). * These operations must be publicly accessible and should not have auth * decorators. */ function isPublicAuthOperation(operation) { return (operation.authorizationType === "login" || operation.authorizationType === "join" || operation.authorizationType === "refresh"); } AutoBeRealizeOperationProgrammer.isPublicAuthOperation = isPublicAuthOperation; function getName(operation) { return getFunctionName(operation); } AutoBeRealizeOperationProgrammer.getName = getName; function getScenario(props) { // Skip authorization for public auth operations (login, join, refresh) const authorization = isPublicAuthOperation(props.operation) ? undefined : props.authorizations.find((el) => el.actor.name === props.operation.authorizationActor); const functionName = getFunctionName(props.operation); return { operation: props.operation, functionName: functionName, location: `src/providers/${functionName}.ts`, decoratorEvent: authorization, }; } AutoBeRealizeOperationProgrammer.getScenario = getScenario; function getAdditional(props) { return Object.assign(Object.assign(Object.assign({}, Object.fromEntries(props.authorizations.map((a) => [ a.payload.location, a.payload.content, ]))), Object.fromEntries(props.collectors.map((c) => [c.location, c.content]))), Object.fromEntries(props.transformers.map((t) => [t.location, t.content]))); } AutoBeRealizeOperationProgrammer.getAdditional = getAdditional; function replaceImportStatements(ctx, props) { return __awaiter(this, void 0, void 0, function* () { let { code, payload } = props; // Beautify code first for consistent formatting const compiler = yield ctx.compiler(); code = yield compiler.typescript.removeImportStatements(code); // Build the standard imports const imports = writeImportStatements(props); // Only add decoratorType import if it exists if (payload) { imports.push(`import { ${payload} } from "../decorators/payload/${payload}"`); } imports.push(...AutoBeRealizeCollectorProgrammer_1.AutoBeRealizeCollectorProgrammer.getNeighbors(code).map((c) => `import { ${c} } from "../collectors/${c}"`), ...AutoBeRealizeTransformerProgrammer_1.AutoBeRealizeTransformerProgrammer.getNeighbors(code).map((c) => `import { ${c} } from "../transformers/${c}"`)); code = [...imports, "", code].join("\n"); // Clean up formatting issues code = code // Remove lines with only whitespace .replace(/^\s+$/gm, "") // Replace 3+ consecutive newlines with exactly 2 newlines .replace(/\n{3,}/g, "\n\n") // Ensure proper spacing after import section .replace(/(import.*?;)(\s*)(\n(?!import|\s*$))/g, "$1\n\n$3") // Trim and ensure single trailing newline .trim() + "\n"; // fix escaped codes code = code.replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\'/g, "'"); // Apply final beautification code = yield compiler.typescript.beautify(code); code = code.replaceAll("typia.tags.assert", "typia.assert"); return code; }); } AutoBeRealizeOperationProgrammer.replaceImportStatements = replaceImportStatements; function writeTemplate(props) { var _a; const scenario = getScenario({ authorizations: props.authorizations, operation: props.operation, }); // Skip authorization for public auth operations (login, join, refresh) const authorization = isPublicAuthOperation(props.operation) ? null : ((_a = props.authorizations.find((a) => a.actor.name === props.operation.authorizationActor)) !== null && _a !== void 0 ? _a : null); return (0, writeRealizeOperationTemplate_1.writeRealizeOperationTemplate)({ scenario, operation: props.operation, imports: writeImportStatements(props), authorization, schemas: props.schemas, collectors: props.collectors, transformers: props.transformers, }); } AutoBeRealizeOperationProgrammer.writeTemplate = writeTemplate; function writeStructures(ctx, operation) { return __awaiter(this, void 0, void 0, function* () { const document = filterDocument(operation, ctx.state().interface.document); const compiler = yield ctx.compiler(); const entries = Object.entries(yield compiler.interface.write(document, [])); return Object.fromEntries(entries.filter(([key]) => key.startsWith("src/api/structures"))); }); } AutoBeRealizeOperationProgrammer.writeStructures = writeStructures; /** * Resolves transformers relevant to an operation, including neighbor * transformers for composite response types (e.g., dashboard endpoints). * Falls back to direct top-level match for simple response types. */ function getLocalTransformers(props) { var _a, _b; const responseTypeName = (_a = props.operation.responseBody) === null || _a === void 0 ? void 0 : _a.typeName; if (!responseTypeName) return []; const innerTypeName = responseTypeName.replace(/^IPage/, ""); // Direct match (covers simple and paginated types) const direct = props.transformers.filter((t) => t.plan.dtoTypeName === innerTypeName); if (direct.length > 0) return direct; // Composite: resolve each property's transformer const schema = props.schemas[innerTypeName]; if (!schema || !utils_1.AutoBeOpenApiTypeChecker.isObject(schema)) return []; const results = []; for (const value of Object.values((_b = schema.properties) !== null && _b !== void 0 ? _b : {})) { const resolved = (0, resolvePropertyTransformer_1.resolvePropertyTransformer)({ schema: value, transformers: props.transformers, }); if (resolved && !results.includes(resolved.transformer)) results.push(resolved.transformer); } return results; } AutoBeRealizeOperationProgrammer.getLocalTransformers = getLocalTransformers; function validateEmptyCode(props) { const errors = []; if (props.draft.includes(props.functionName) === false) errors.push({ path: "$input.request.draft", expected: `string (including function named '${props.functionName}')`, value: props.draft, description: description(props.functionName), }); if (props.revise.final !== null && props.revise.final.includes(props.functionName) === false) errors.push({ path: "$input.request.revise.final", expected: `string (including function named '${props.functionName}')`, value: props.revise.final, description: description(props.functionName), }); return errors; } AutoBeRealizeOperationProgrammer.validateEmptyCode = validateEmptyCode; /** * Validates that Transformer.select() and Transformer.transform() are always * used as a pair in operation code. Using one without the other causes type * mismatches: select() shapes the Prisma payload for transform(), so they * must appear together. */ function validateSelectTransformContract(props) { const errors = []; validateSelectTransformContractForCode({ content: props.draft, path: "$input.request.draft", errors, }); if (props.revise.final !== null) { validateSelectTransformContractForCode({ content: props.revise.final, path: "$input.request.revise.final", errors, }); } return errors; } AutoBeRealizeOperationProgrammer.validateSelectTransformContract = validateSelectTransformContract; })(AutoBeRealizeOperationProgrammer || (exports.AutoBeRealizeOperationProgrammer = AutoBeRealizeOperationProgrammer = {})); function validateSelectTransformContractForCode(props) { const selectUsers = new Set(); const transformUsers = new Set(); const selectRegex = /(\w+Transformer)\.select/g; const transformRegex = /(\w+Transformer)\.transform/g; let match; while ((match = selectRegex.exec(props.content)) !== null) selectUsers.add(match[1]); while ((match = transformRegex.exec(props.content)) !== null) transformUsers.add(match[1]); for (const name of transformUsers) { if (selectUsers.has(name) === false) props.errors.push({ path: props.path, expected: `${name}.select() must appear in the query when ${name}.transform() is used.`, value: props.content, description: utils_1.StringUtil.trim ` You call ${name}.transform() but never include ${name}.select() in your Prisma query. The Payload type of ${name}.transform() is derived from ${name}.select() — without it, the data shape will not match and you will get type mismatch compile errors. Add \`...${name}.select()\` to your Prisma query's select/spread. `, }); } for (const name of selectUsers) { if (transformUsers.has(name) === false) props.errors.push({ path: props.path, expected: `${name}.transform() must be called when ${name}.select() is used.`, value: props.content, description: utils_1.StringUtil.trim ` You include ${name}.select() in your query but never call ${name}.transform() to convert the result. The data fetched via ${name}.select() is a raw Prisma payload shaped for ${name}.transform() — assigning it directly to a DTO field or transforming it inline will cause type mismatches. Call ${name}.transform() on the fetched data. `, }); } } function writeImportStatements(props) { const typeReferences = new Set(); const visit = (key) => utils_2.OpenApiTypeChecker.visit({ schema: { $ref: `#/components/schemas/${key}` }, components: { schemas: props.schemas }, closure: (next) => { if (utils_2.OpenApiTypeChecker.isReference(next)) typeReferences.add(next.$ref.split("/").pop().split(".")[0]); }, }); if (props.operation.requestBody) visit(props.operation.requestBody.typeName); if (props.operation.responseBody) visit(props.operation.responseBody.typeName); return [ `import { ArrayUtil } from "@nestia/e2e";`, 'import { HttpException } from "@nestjs/common";', 'import { Prisma } from "@prisma/sdk";', 'import jwt from "jsonwebtoken";', 'import typia, { tags } from "typia";', 'import { v4 } from "uuid";', 'import { MyGlobal } from "../MyGlobal";', 'import { PasswordUtil } from "../utils/PasswordUtil";', 'import { toISOStringSafe } from "../utils/toISOStringSafe"', "", `import { IEntity } from "@ORGANIZATION/PROJECT-api/lib/structures/IEntity";`, ...Array.from(typeReferences).map((ref) => `import { ${ref} } from "@ORGANIZATION/PROJECT-api/lib/structures/${ref}";`), ]; } function getFunctionName(operation) { const functionName = `${operation.method}${operation.path .split("/") .filter(Boolean) .map((segment) => { if (segment.startsWith("{") && segment.endsWith("}")) { // {userId} → UserId const paramName = segment.slice(1, -1); return paramName .split("-") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(""); } // api → Api, v1 → V1 const words = segment.split("-"); return words .map((word) => { return word.charAt(0).toUpperCase() + word.slice(1); }) .join(""); }) .join("")}`; return functionName; } const description = (func) => utils_1.StringUtil.trim ` The function ${func} does not exist in the provided code snippet. The first reason of the non-existence is that the code snippet is empty, and the second reason is that AI has written different function name by mistake. Please make sure that the code snippet includes the function ${func}. Note that, you never have to write empty code or different function name. `; function filterDocument(operation, document) { const components = { authorizations: document.components.authorizations, schemas: {}, }; const visit = (typeName) => { utils_2.OpenApiTypeChecker.visit({ components: document.components, schema: { $ref: `#/components/schemas/${typeName}` }, closure: (s) => { if (utils_2.OpenApiTypeChecker.isReference(s)) { const key = s.$ref.split("/").pop(); components.schemas[key] = document.components.schemas[key]; } }, }); }; if (operation.requestBody) visit(operation.requestBody.typeName); if (operation.responseBody) visit(operation.responseBody.typeName); return { operations: [operation], components, }; } //# sourceMappingURL=AutoBeRealizeOperationProgrammer.js.map