UNPKG

@autobe/agent

Version:

AI backend server code generator

339 lines (311 loc) 11.3 kB
import { AutoBeOpenApi, AutoBeRealizeAuthorization, AutoBeRealizeCollectorFunction, AutoBeRealizeTransformerFunction, } from "@autobe/interface"; import { AutoBeOpenApiTypeChecker, StringUtil } from "@autobe/utils"; import { IAutoBeRealizeScenarioResult } from "../../structures/IAutoBeRealizeScenarioResult"; import { AutoBeRealizeCollectorProgrammer } from "../AutoBeRealizeCollectorProgrammer"; import { AutoBeRealizeTransformerProgrammer } from "../AutoBeRealizeTransformerProgrammer"; import { IResolvedTransformer, resolvePropertyTransformer, } from "./resolvePropertyTransformer"; export function writeRealizeOperationTemplate(props: { scenario: IAutoBeRealizeScenarioResult; operation: AutoBeOpenApi.IOperation; imports: string[]; authorization: AutoBeRealizeAuthorization | null; schemas: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>; collectors: AutoBeRealizeCollectorFunction[]; transformers: AutoBeRealizeTransformerFunction[]; }): string { const functionParameters: string[] = []; if (props.authorization && props.authorization.actor.name) { functionParameters.push( `${props.authorization.actor.name}: ${props.authorization.payload.name}`, ); } functionParameters.push( ...props.operation.parameters.map( (param: AutoBeOpenApi.IParameter): string => `${param.name}: ${writeParameterType(param.schema)}`, ), ); if ( props.operation.requestBody?.typeName.endsWith(".ILogin") || props.operation.requestBody?.typeName.endsWith(".IJoin") ) functionParameters.push("ip: string"); if (props.operation.requestBody?.typeName) { functionParameters.push(`body: ${props.operation.requestBody.typeName}`); } const hasParameters: boolean = functionParameters.length > 0; const formattedSignature: string = hasParameters ? `props: {\n${functionParameters.map((p: string): string => ` ${p}`).join(";\n")};\n}` : ""; const returnType: string = props.operation.responseBody?.typeName ?? "void"; const body: string = writeBody({ method: props.operation.method, operation: props.operation, schemas: props.schemas, collectors: props.collectors, transformers: props.transformers, }); const indentedBody: string = body .split("\n") .map((line: string): string => (line.length > 0 ? ` ${line}` : line)) .join("\n"); return StringUtil.trim` Complete the code below, disregard the import part and return only the function part. \`\`\`typescript ${props.imports.join("\n")} // DON'T CHANGE FUNCTION NAME AND PARAMETERS, // ONLY YOU HAVE TO WRITE THIS FUNCTION BODY, AND USE IMPORTED. export async function ${props.scenario.functionName}(${formattedSignature}): Promise<${returnType}> { ${indentedBody} } \`\`\` `; } function writeBody(props: { method: string; operation: AutoBeOpenApi.IOperation; schemas: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>; collectors: AutoBeRealizeCollectorFunction[]; transformers: AutoBeRealizeTransformerFunction[]; }): string { const collector: AutoBeRealizeCollectorFunction | undefined = props.operation .requestBody?.typeName ? props.collectors.find( (c: AutoBeRealizeCollectorFunction): boolean => c.plan.dtoTypeName === props.operation.requestBody!.typeName, ) : undefined; const responseTypeName: string | undefined = props.operation.responseBody?.typeName; const isPageType: boolean = !!responseTypeName?.startsWith("IPage"); const innerTypeName: string | undefined = isPageType ? responseTypeName!.replace(/^IPage/, "") : responseTypeName; const transformer: AutoBeRealizeTransformerFunction | undefined = innerTypeName ? props.transformers.find( (t: AutoBeRealizeTransformerFunction): boolean => t.plan.dtoTypeName === innerTypeName, ) : undefined; // pagination (collector 와 동시에 올 수 없음) if (isPageType && transformer) { const tName: string = AutoBeRealizeTransformerProgrammer.getName( transformer.plan.dtoTypeName, ); const table: string = transformer.plan.databaseSchemaName; const isRecursive: boolean = AutoBeRealizeTransformerProgrammer.getRecursiveProperty({ schemas: props.schemas as Record<string, AutoBeOpenApi.IJsonSchema>, typeName: transformer.plan.dtoTypeName, }) !== null; const dataLine: string = isRecursive ? `data: await ${tName}.transformAll(records),` : `data: await ArrayUtil.asyncMap(records, ${tName}.transform),`; return StringUtil.trim` const records = await MyGlobal.prisma.${table}.findMany({ ...${tName}.select(), ..., }); return { pagination: { current: ..., limit: ..., records: ..., pages: ..., }, ${dataLine} }; `; } // collector + transformer (create and return) if (collector && transformer) { const cName: string = AutoBeRealizeCollectorProgrammer.getName( collector.plan.dtoTypeName, ); const tName: string = AutoBeRealizeTransformerProgrammer.getName( transformer.plan.dtoTypeName, ); const table: string = collector.plan.databaseSchemaName; return StringUtil.trim` const record = await MyGlobal.prisma.${table}.create({ data: await ${cName}.collect({ body: props.body, ... }), ...${tName}.select(), }); return await ${tName}.transform(record); `; } // collector only (create, void return) if (collector) { const cName: string = AutoBeRealizeCollectorProgrammer.getName( collector.plan.dtoTypeName, ); const table: string = collector.plan.databaseSchemaName; return StringUtil.trim` await MyGlobal.prisma.${table}.create({ data: await ${cName}.collect({ body: props.body, ... }), }); `; } // update + transformer (PUT: manual update, re-fetch, transform) if (props.method === "put" && transformer) { const tName: string = AutoBeRealizeTransformerProgrammer.getName( transformer.plan.dtoTypeName, ); const table: string = transformer.plan.databaseSchemaName; return StringUtil.trim` await MyGlobal.prisma.${table}.update({ where: { ... }, data: { ... }, }); const updated = await MyGlobal.prisma.${table}.findUniqueOrThrow({ where: { ... }, ...${tName}.select(), }); return await ${tName}.transform(updated); `; } // update void (PUT: manual update, void return) if (props.method === "put") { const table: string = transformer?.plan.databaseSchemaName ?? "..."; return StringUtil.trim` await MyGlobal.prisma.${table}.update({ where: { ... }, data: { ... }, }); `; } // delete (DELETE: simple delete, void return) if (props.method === "delete") { const table: string = transformer?.plan.databaseSchemaName ?? "..."; return StringUtil.trim` await MyGlobal.prisma.${table}.delete({ where: { ... }, }); `; } // transformer only (read single or other non-PUT/DELETE) if (transformer) { const tName: string = AutoBeRealizeTransformerProgrammer.getName( transformer.plan.dtoTypeName, ); const table: string = transformer.plan.databaseSchemaName; return StringUtil.trim` const record = await MyGlobal.prisma.${table}.findFirstOrThrow({ ...${tName}.select(), where: { ... }, }); return await ${tName}.transform(record); `; } // object response with transformer-backed properties if (responseTypeName) { const objectBody: string | null = writeObjectBody({ responseTypeName, schemas: props.schemas, transformers: props.transformers, }); if (objectBody !== null) return objectBody; } return [ "// No matching Collector/Transformer found for this operation.", "// You MUST call getDatabaseSchemas first to get exact relation property names.", "// NEVER guess relation names from table names — always verify against the schema.", "...", ].join("\n "); } function writeObjectBody(props: { responseTypeName: string; schemas: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>; transformers: AutoBeRealizeTransformerFunction[]; }): string | null { const schema: AutoBeOpenApi.IJsonSchemaDescriptive | undefined = props.schemas[props.responseTypeName]; if (!schema || !AutoBeOpenApiTypeChecker.isObject(schema)) return null; const lines: string[] = []; let hasMatch: boolean = false; for (const [key, prop] of Object.entries(schema.properties) as Array< [string, AutoBeOpenApi.IJsonSchemaProperty] >) { const match: IResolvedTransformer | null = resolvePropertyTransformer({ schema: prop, transformers: props.transformers, }); if (match) { hasMatch = true; const tName: string = AutoBeRealizeTransformerProgrammer.getName( match.transformer.plan.dtoTypeName, ); if (match.isArray) { const isRecursive: boolean = AutoBeRealizeTransformerProgrammer.getRecursiveProperty({ schemas: props.schemas as Record<string, AutoBeOpenApi.IJsonSchema>, typeName: match.transformer.plan.dtoTypeName, }) !== null; lines.push( isRecursive ? ` ${key}: await ${tName}.transformAll(...),` : ` ${key}: await ArrayUtil.asyncMap(..., (r) => ${tName}.transform(r)),`, ); } else { lines.push(` ${key}: await ${tName}.transform(...),`); } } else { lines.push(` ${key}: ...,`); } } if (!hasMatch) return null; return StringUtil.trim` return { ${lines.join("\n")} }; `; } function writeParameterType( schema: AutoBeOpenApi.IParameter["schema"], ): string { const elements: string[] = schema.type === "integer" ? ["number", `tags.Type<"int32">`] : [schema.type]; if (schema.type === "number") { if (schema.minimum !== undefined) elements.push(`tags.Minimum<${schema.minimum}>`); if (schema.maximum !== undefined) elements.push(`tags.Maximum<${schema.maximum}>`); if (schema.exclusiveMinimum !== undefined) elements.push(`tags.ExclusiveMinimum<${schema.exclusiveMinimum}>`); if (schema.exclusiveMaximum !== undefined) elements.push(`tags.ExclusiveMaximum<${schema.exclusiveMaximum}>`); if (schema.multipleOf !== undefined) elements.push(`tags.MultipleOf<${schema.multipleOf}>`); } else if (schema.type === "string") { if (schema.format !== undefined) elements.push(`tags.Format<${JSON.stringify(schema.format)}>`); if (schema.contentMediaType !== undefined) elements.push( `tags.ContentMediaType<${JSON.stringify(schema.contentMediaType)}>`, ); if (schema.pattern !== undefined) elements.push(`tags.Pattern<${JSON.stringify(schema.pattern)}>`); if (schema.minLength !== undefined) elements.push(`tags.MinLength<${schema.minLength}>`); if (schema.maxLength !== undefined) elements.push(`tags.MaxLength<${schema.maxLength}>`); } return elements.join(" & "); }