@autobe/agent
Version:
AI backend server code generator
584 lines (574 loc) • 26.5 kB
JavaScript
;
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