UNPKG

@autobe/agent

Version:

AI backend server code generator

584 lines (574 loc) 26.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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AutoBeRealizeTransformerProgrammer = void 0; const utils_1 = require("@autobe/utils"); const utils_2 = require("@typia/utils"); const typia_1 = __importDefault(require("typia")); const AutoBeRealizeCollectorProgrammer_1 = require("./AutoBeRealizeCollectorProgrammer"); const writeRealizeTransformerTemplate_1 = require("./internal/writeRealizeTransformerTemplate"); var AutoBeRealizeTransformerProgrammer; (function (AutoBeRealizeTransformerProgrammer) { function filter(props) { var _a; const schema = props.schemas[props.key]; if (schema === undefined) return false; return (utils_1.AutoBeOpenApiTypeChecker.isObject(schema) && Object.keys(schema.properties).length !== 0 && ((_a = schema.additionalProperties) !== null && _a !== void 0 ? _a : false) === false && props.key !== "IAuthorizationToken" && props.key !== "IEntity" && props.key.startsWith("IPage") === false && props.key.endsWith(".IRequest") === false && props.key.endsWith(".ICreate") === false && props.key.endsWith(".IUpdate") === false && props.key.endsWith(".IAuthorized") === false && props.key.endsWith(".IJoin") === false && props.key.endsWith(".ILogin") === false && props.key.endsWith(".IRefresh") === false); } AutoBeRealizeTransformerProgrammer.filter = filter; function getName(dtoTypeName) { return (dtoTypeName .split(".") .map((s) => (s.startsWith("I") ? s.substring(1) : s)) .join("At") + "Transformer"); } AutoBeRealizeTransformerProgrammer.getName = getName; function getNeighbors(code) { const unique = new Set(); const regex = /(\w+Transformer)\.(select|transform)/g; while (true) { const match = regex.exec(code); if (match === null) break; unique.add(match[1]); } return Array.from(unique); } AutoBeRealizeTransformerProgrammer.getNeighbors = getNeighbors; function getRelationMappingTable(props) { const result = []; // belongsTo relations (forward FK on this model) for (const f of props.model.foreignFields) { result.push({ propertyKey: f.relation.name, targetModel: f.relation.targetModel, relationType: "belongsTo", fkColumns: f.name, }); } // hasMany/hasOne relations (FK on the other model pointing to this model) for (const file of props.application.files) { for (const om of file.models) { for (const fk of om.foreignFields) { if (fk.relation.targetModel === props.model.name) { result.push({ propertyKey: fk.relation.oppositeName, targetModel: om.name, relationType: fk.unique ? "hasOne" : "hasMany", fkColumns: fk.name, }); } } } } return result; } AutoBeRealizeTransformerProgrammer.getRelationMappingTable = getRelationMappingTable; function formatRelationMappingTable(props) { const relations = getRelationMappingTable(props); if (relations.length === 0) return "(no relations)"; return [ "| propertyKey | Target Model | Relation Type | FK Column(s) |", "|---|---|---|---|", ...relations.map((r) => `| ${r.propertyKey} | ${r.targetModel} | ${r.relationType} | ${r.fkColumns} |`), ].join("\n"); } AutoBeRealizeTransformerProgrammer.formatRelationMappingTable = formatRelationMappingTable; function getSelectMappingMetadata(props) { return AutoBeRealizeCollectorProgrammer_1.AutoBeRealizeCollectorProgrammer.getMappingMetadata(props); } AutoBeRealizeTransformerProgrammer.getSelectMappingMetadata = getSelectMappingMetadata; function getTransformMappingMetadata(props) { const schema = props.document .components.schemas[props.plan.dtoTypeName]; return Object.keys(schema.properties).map((key) => ({ property: key, })); } AutoBeRealizeTransformerProgrammer.getTransformMappingMetadata = getTransformMappingMetadata; function computeNeighborRelations(props) { var _a, _b, _c, _d; const result = []; // Count how many DTO properties reference each neighbor type and // how many relations point to each target model. // When either is ambiguous (>1), skip — wrong mapping is worse than none. const dtoRefCount = new Map(); const relationCount = new Map(); const neighborSchemaCount = new Map(); for (const neighbor of props.neighbors) { const targetRef = `#/components/schemas/${neighbor.dtoTypeName}`; let count = 0; for (const [, prop] of Object.entries(props.schema.properties)) { if (!prop) continue; if (findNeighborRef({ schema: prop, targetRef })) count++; } dtoRefCount.set(neighbor.dtoTypeName, count); relationCount.set(neighbor.databaseSchemaName, props.relations.filter((r) => r.targetModel === neighbor.databaseSchemaName).length); neighborSchemaCount.set(neighbor.databaseSchemaName, ((_a = neighborSchemaCount.get(neighbor.databaseSchemaName)) !== null && _a !== void 0 ? _a : 0) + 1); } for (const neighbor of props.neighbors) { // Skip ambiguous cases: multiple DTO properties, relations, or // neighbors sharing the same database schema (would produce // duplicate select keys → silent JS overwrite) if (((_b = dtoRefCount.get(neighbor.dtoTypeName)) !== null && _b !== void 0 ? _b : 0) !== 1) continue; if (((_c = relationCount.get(neighbor.databaseSchemaName)) !== null && _c !== void 0 ? _c : 0) !== 1) continue; if (((_d = neighborSchemaCount.get(neighbor.databaseSchemaName)) !== null && _d !== void 0 ? _d : 0) !== 1) continue; const targetRef = `#/components/schemas/${neighbor.dtoTypeName}`; let dtoMatch = null; for (const [key, prop] of Object.entries(props.schema.properties)) { if (!prop) continue; const ref = findNeighborRef({ schema: prop, targetRef }); if (ref) { dtoMatch = Object.assign({ property: key }, ref); break; } } const relation = props.relations.find((r) => r.targetModel === neighbor.databaseSchemaName); if (dtoMatch && relation) { result.push({ dtoProperty: dtoMatch.property, relationKey: relation.propertyKey, transformerName: getName(neighbor.dtoTypeName), isArray: dtoMatch.isArray, isNullable: dtoMatch.isNullable, }); } } return result; } AutoBeRealizeTransformerProgrammer.computeNeighborRelations = computeNeighborRelations; function findNeighborRef(props) { const { schema, targetRef } = props; if (utils_1.AutoBeOpenApiTypeChecker.isReference(schema) && schema.$ref === targetRef) return { isArray: false, isNullable: false }; if (utils_1.AutoBeOpenApiTypeChecker.isArray(schema) && utils_1.AutoBeOpenApiTypeChecker.isReference(schema.items) && schema.items.$ref === targetRef) return { isArray: true, isNullable: false }; if (utils_1.AutoBeOpenApiTypeChecker.isOneOf(schema)) { const hasNull = schema.oneOf.some((s) => utils_1.AutoBeOpenApiTypeChecker.isNull(s)); for (const sub of schema.oneOf) { if (utils_1.AutoBeOpenApiTypeChecker.isNull(sub)) continue; const inner = findNeighborRef({ schema: sub, targetRef }); if (inner) return Object.assign(Object.assign({}, inner), { isNullable: hasNull }); } } return null; } AutoBeRealizeTransformerProgrammer.writeTemplate = writeRealizeTransformerTemplate_1.writeRealizeTransformerTemplate; function writeStructures(ctx, dtoTypeName) { return AutoBeRealizeCollectorProgrammer_1.AutoBeRealizeCollectorProgrammer.writeStructures(ctx, dtoTypeName); } AutoBeRealizeTransformerProgrammer.writeStructures = writeStructures; function replaceImportStatements(ctx, props) { return __awaiter(this, void 0, void 0, function* () { const compiler = yield ctx.compiler(); let code = yield compiler.typescript.removeImportStatements(props.code); const imports = writeImportStatements(props); const selfName = getName(props.dtoTypeName); code = [ ...imports, "", ...getNeighbors(code) .filter((trs) => trs !== selfName) .map((trs) => `import { ${trs} } from "./${trs}";`), "", code, ].join("\n"); return yield compiler.typescript.beautify(code); }); } AutoBeRealizeTransformerProgrammer.replaceImportStatements = replaceImportStatements; function validate(props) { const errors = []; // mapping plans validateTransformMappings({ document: props.document, errors, plan: props.plan, transformMappings: props.transformMappings, }); validateSelectMappings({ application: props.application, errors, plan: props.plan, selectMappings: props.selectMappings, }); // validate draft validateEmptyCode({ plan: props.plan, content: props.draft, path: "$input.request.draft", errors, }); validateNeighbors({ plan: props.plan, neighbors: props.neighbors, content: props.draft, path: "$input.request.draft", errors, }); validateSelectReturnType({ content: props.draft, path: "$input.request.draft", errors, }); validateSelectTransformContract({ plan: props.plan, content: props.draft, path: "$input.request.draft", errors, }); // validate final if (props.revise.final !== null) { validateEmptyCode({ plan: props.plan, content: props.revise.final, path: "$input.request.revise.final", errors, }); validateNeighbors({ plan: props.plan, neighbors: props.neighbors, content: props.revise.final, path: "$input.request.revise.final", errors, }); validateSelectReturnType({ content: props.revise.final, path: "$input.request.revise.final", errors, }); validateSelectTransformContract({ plan: props.plan, content: props.revise.final, path: "$input.request.revise.final", errors, }); } return errors; } AutoBeRealizeTransformerProgrammer.validate = validate; function validateSelectMappings(props) { const model = props.application.files .map((f) => f.models) .flat() .find((m) => m.name === props.plan.databaseSchemaName); const required = getSelectMappingMetadata({ application: props.application, model, }); props.selectMappings.forEach((m, i) => { const metadata = required.find((r) => r.member === m.member); if (metadata === undefined) props.errors.push({ path: `$input.request.selectMappings[${i}].member`, value: m.member, expected: required.map((s) => JSON.stringify(s)).join(" | "), description: utils_1.StringUtil.trim ` '${m.member}' is not a valid Prisma member. Please provide mapping only for existing Prisma members: ${required.map((r) => `- ${r.member}`).join("\n")} `, }); else { if (metadata.kind !== m.kind) props.errors.push({ path: `$input.request.selectMappings[${i}].kind`, value: m.kind, expected: `"${metadata.kind}"`, description: utils_1.StringUtil.trim ` The mapping kind for Prisma member '${m.member}' is invalid. Expected kind is '${metadata.kind}', but received kind is '${m.kind}'. `, }); if (metadata.nullable !== m.nullable) props.errors.push({ path: `$input.request.selectMappings[${i}].nullable`, value: m.nullable, expected: `${metadata.nullable}`, description: utils_1.StringUtil.trim ` The mapping nullable for Prisma member '${m.member}' is invalid. Expected nullable is '${metadata.nullable}', but received nullable is '${m.nullable}'. `, }); } }); for (const r of required) { if (props.selectMappings.some((m) => m.member === r.member)) continue; props.errors.push({ path: "$input.request.selectMappings[]", value: undefined, expected: utils_1.StringUtil.trim `{ member: "${r.member}"; kind: "${r.kind}"; how: string; }`, description: utils_1.StringUtil.trim ` You missed mapping for required Prisma member '${r.member}'. Make sure to provide mapping for all required members. `, }); } } function validateTransformMappings(props) { const schema = props.document .components.schemas[props.plan.dtoTypeName]; props.transformMappings.forEach((m, i) => { if (schema.properties[m.property] !== undefined) return; props.errors.push({ path: `$input.request.transformMappings[${i}].property`, value: m.property, expected: Object.keys(schema.properties) .map((key) => JSON.stringify(key)) .join(" | "), description: utils_1.StringUtil.trim ` The mapping for the property '${m.property}' does not exist in DTO '${props.plan.dtoTypeName}'. Please provide mapping only for existing properties: ${Object.keys(schema.properties) .map((key) => `- ${key}`) .join("\n")} `, }); }); for (const key of Object.keys(schema.properties)) { if (props.transformMappings.some((m) => m.property === key)) continue; props.errors.push({ path: `$input.request.transformMappings[]`, value: undefined, expected: utils_1.StringUtil.trim `{ property: "${key}"; how: string; }`, description: utils_1.StringUtil.trim ` You missed the mapping for the property '${key}' of DTO '${props.plan.dtoTypeName}'. Make sure to provide mapping for all properties. `, }); } } 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]); }, }); visit(props.dtoTypeName); const imports = [ `import { ArrayUtil } from "@nestia/e2e";`, `import { HttpException } from "@nestjs/common";`, `import { Prisma } from "@prisma/sdk";`, `import { VariadicSingleton } from "tstl";`, `import typia, { tags } from "typia";`, "", `import { IEntity } from "@ORGANIZATION/PROJECT-api/lib/structures/IEntity";`, ...Array.from(typeReferences).map((ref) => `import { ${ref} } from "@ORGANIZATION/PROJECT-api/lib/structures/${ref}";`), "", 'import { MyGlobal } from "../MyGlobal";', `import { toISOStringSafe } from "../utils/toISOStringSafe";`, ]; return imports; } function validateEmptyCode(props) { const name = getName(props.plan.dtoTypeName); if (props.content.includes(`export namespace ${name}`) === false) props.errors.push({ path: props.path, expected: `Namespace '${name}' to be present in the code.`, value: props.content, description: `The generated code does not contain the expected namespace '${name}'.`, }); } function validateSelectReturnType(props) { if (/function\s+select\s*\(\s*\)\s*:/.test(props.content)) props.errors.push({ path: props.path, expected: "select() must use inferred return type (no explicit annotation).", value: props.content, description: utils_1.StringUtil.trim ` select() has an explicit return type annotation. This widens the literal type and destroys Prisma GetPayload inference, causing cascading type errors. Remove the return type — use satisfies on the return value instead. `, }); } function validateNeighbors(props) { const selfName = getName(props.plan.dtoTypeName); const neighborNames = getNeighbors(props.content); for (const x of neighborNames) if (x !== selfName && props.neighbors.some((y) => getName(y.dtoTypeName) === x) === false) props.errors.push({ path: props.path, expected: `Use existing transformer.`, value: props.content, description: utils_1.StringUtil.trim ` You've imported and utilized ${x}, but it does not exist. Use one of them below, or change to another code: ${props.neighbors .map((y) => `- ${getName(y.dtoTypeName)}`) .join("\n")} `, }); } function validateSelectTransformContract(props) { const selfName = getName(props.plan.dtoTypeName); 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 (name === selfName) continue; if (selectUsers.has(name) === false) props.errors.push({ path: props.path, expected: `${name}.select() must appear in select() when ${name}.transform() is used.`, value: props.content, description: utils_1.StringUtil.trim ` You call ${name}.transform() but never include ${name}.select() in your select query. The Payload type of ${name}.transform() is derived from ${name}.select() — without it, the data shape will not match and you will get "missing properties" compile errors. Reuse ${name}.select() in your select instead of writing an inline select. `, }); } for (const name of selectUsers) { if (name === selfName) continue; 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 getRecursiveRelations(props) { const schema = props.schemas[props.typeName]; if (schema === undefined || !utils_1.AutoBeOpenApiTypeChecker.isObject(schema)) return { parent: null, children: null }; const selfRef = `#/components/schemas/${props.typeName}`; const hasSelfRef = (s) => { if (utils_1.AutoBeOpenApiTypeChecker.isReference(s)) return s.$ref === selfRef; if (utils_1.AutoBeOpenApiTypeChecker.isOneOf(s)) return s.oneOf.some((sub) => hasSelfRef(sub)); return false; }; const hasSelfRefArray = (s) => { if (utils_1.AutoBeOpenApiTypeChecker.isArray(s)) return hasSelfRef(s.items); else if (utils_1.AutoBeOpenApiTypeChecker.isOneOf(s)) return s.oneOf.some((sub) => hasSelfRefArray(sub)); return false; }; let parent = null; let children = null; for (const [key, value] of Object.entries(schema.properties)) { if (!value) continue; if (hasSelfRef(value)) parent = key; else if (hasSelfRefArray(value)) children = key; } return { parent, children }; } AutoBeRealizeTransformerProgrammer.getRecursiveRelations = getRecursiveRelations; function getRecursiveProperty(props) { const { parent, children } = getRecursiveRelations(props); return parent !== null && parent !== void 0 ? parent : children; } AutoBeRealizeTransformerProgrammer.getRecursiveProperty = getRecursiveProperty; AutoBeRealizeTransformerProgrammer.fixApplication = (props) => { const $defs = props.definition.functions[0].parameters.$defs; // transform (() => { const transform = $defs["AutoBeRealizeTransformerTransformMapping"]; if (transform === undefined || utils_2.LlmTypeChecker.isObject(transform) === false) throw new Error(`AutoBeRealizeTransformerTransformMapping type is not defined in the function calling schema.`); const property = transform.properties.property; if (property === undefined || utils_2.LlmTypeChecker.isString(property) === false) throw new Error(`AutoBeRealizeTransformerTransformMapping.property is not defined as string type.`); property.enum = getTransformMappingMetadata(props).map((m) => m.property); })(); // select (() => { const select = $defs["AutoBeRealizeTransformerSelectMapping"]; if (select === undefined || utils_2.LlmTypeChecker.isObject(select) === false) throw new Error(`AutoBeRealizeTransformerSelectMapping type is not defined in the function calling schema.`); const member = select.properties.member; if (member === undefined || utils_2.LlmTypeChecker.isString(member) === false) throw new Error(`AutoBeRealizeTransformerSelectMapping.member is not defined as string type.`); member.enum = getSelectMappingMetadata({ application: props.application, model: props.application.files .map((f) => f.models) .flat() .find((m) => m.name === props.plan.databaseSchemaName), }).map((m) => m.member); })(); }; })(AutoBeRealizeTransformerProgrammer || (exports.AutoBeRealizeTransformerProgrammer = AutoBeRealizeTransformerProgrammer = {})); //# sourceMappingURL=AutoBeRealizeTransformerProgrammer.js.map