@autobe/agent
Version:
AI backend server code generator
165 lines (152 loc) • 5.38 kB
text/typescript
import {
AutoBeDatabase,
AutoBeInterfaceSchemaDesign,
AutoBeOpenApi,
} from "@autobe/interface";
import { StringUtil } from "@autobe/utils";
import { LlmTypeChecker, NamingConvention } from "@typia/utils";
import { plural } from "pluralize";
import { ILlmApplication, ILlmFunction, ILlmSchema, IValidation } from "typia";
import { AutoBeDatabaseModelProgrammer } from "../../database/programmers/AutoBeDatabaseModelProgrammer";
import { AutoBeJsonSchemaValidator } from "../utils/AutoBeJsonSchemaValidator";
export namespace AutoBeInterfaceSchemaProgrammer {
export interface IDatabaseSchemaMember {
key: string;
nullable: boolean;
/**
* Database column type for type compatibility validation.
*
* - `null` means this member is a relation (object/array), not a column, so
* primitive type checking should be skipped.
*/
type: AutoBeDatabase.IPlainField["type"] | null;
}
export const getDatabaseSchemaName = (typeName: string): string =>
plural(NamingConvention.snake(typeName.split(".")[0]!.substring(1)));
export const getNeighborDatabaseSchemas = (props: {
typeName: string;
application: AutoBeDatabase.IApplication;
}): AutoBeDatabase.IModel[] | undefined => {
const expected: string = getDatabaseSchemaName(props.typeName);
const found: AutoBeDatabase.IModel | undefined = props.application.files
.flatMap((f) => f.models)
.find((m) => m.name === expected);
if (found === undefined) return;
return AutoBeDatabaseModelProgrammer.getNeighbors({
application: props.application,
model: found,
});
};
export const getDatabaseSchemaProperties = (props: {
everyModels: AutoBeDatabase.IModel[];
model: AutoBeDatabase.IModel;
}): IDatabaseSchemaMember[] => [
// Primary key
{
key: props.model.primaryField.name,
nullable: false,
type: "uuid",
},
// Plain fields (columns like title, description, created_at, etc.)
...props.model.plainFields.map((f) => ({
key: f.name,
nullable: f.nullable,
type: f.type,
})),
// Foreign key columns (e.g., todo_app_user_id)
...props.model.foreignFields.map((f) => ({
key: f.name,
nullable: f.nullable,
type: "uuid" as const,
})),
// Foreign key relation names (e.g., user) — relations, not columns
...props.model.foreignFields.map((f) => ({
key: f.relation.name,
nullable: f.nullable,
type: null,
})),
// Opposite relations from other models — relations, not columns
...props.everyModels
.map((m) =>
m.foreignFields
.filter((f) => f.relation.targetModel === props.model.name)
.map((f) => ({
key: f.relation.oppositeName,
nullable: f.unique,
type: null,
})),
)
.flat(),
];
export const validate = (props: {
// common
errors: IValidation.IError[];
path: string;
everyModels: AutoBeDatabase.IModel[];
operations: AutoBeOpenApi.IOperation[];
// specific
typeName: string;
design: AutoBeInterfaceSchemaDesign;
}): void => {
AutoBeJsonSchemaValidator.validateSchema({
errors: props.errors,
operations: props.operations,
typeName: props.typeName,
schema: props.design.schema,
path: `${props.path}.schema`,
});
if (
props.design.databaseSchema !== null &&
props.everyModels.some((m) => m.name === props.design.databaseSchema) ===
false
)
props.errors.push({
path: `${props.path}.databaseSchema`,
expected: props.everyModels
.map((m) => JSON.stringify(m.name))
.join(" | "),
value: props.design.databaseSchema,
description: StringUtil.trim`
You set "databaseSchema" to ${JSON.stringify(props.design.databaseSchema)},
but no database model with that name exists.
Available database models are:
${props.everyModels.map((m) => `- ${m.name}`).join("\n")}
Fix: Use one of the available model names above, or set to null
if this type does not map to a single database table (e.g.,
computed/aggregated types, types composed purely by business logic,
or embedded JSON structures).
`,
});
};
export const fixApplication = (props: {
application: ILlmApplication;
everyModels: AutoBeDatabase.IModel[];
}): void => {
const func: ILlmFunction = props.application.functions[0];
fixDatabaseSchema({
$defs: func.parameters.$defs,
parameters: func.parameters,
everyModels: props.everyModels,
});
};
const fixDatabaseSchema = (props: {
$defs: Record<string, ILlmSchema>;
parameters: ILlmSchema.IParameters;
everyModels: AutoBeDatabase.IModel[];
}): void => {
LlmTypeChecker.visit({
$defs: props.$defs,
schema: props.parameters,
closure: (next) => {
if (LlmTypeChecker.isObject(next) === false) return;
const member: ILlmSchema | undefined = next.properties.databaseSchema;
if (member === undefined || LlmTypeChecker.isAnyOf(member) === false)
return;
const value: ILlmSchema | undefined = member.anyOf.find((x) =>
LlmTypeChecker.isString(x),
);
if (value === undefined) return;
},
});
};
}