@autobe/agent
Version:
AI backend server code generator
200 lines (183 loc) • 6.21 kB
text/typescript
import { AutoBeOpenApi, AutoBeProgressEventBase } from "@autobe/interface";
import { missedOpenApiSchemas } from "@autobe/utils";
import { AutoBeContext } from "../../context/AutoBeContext";
import { orchestrateInterfaceSchemaCasting } from "./orchestrateInterfaceSchemaCasting";
import { orchestrateInterfaceSchemaComplement } from "./orchestrateInterfaceSchemaComplement";
import { orchestrateInterfaceSchemaDecouple } from "./orchestrateInterfaceSchemaDecouple";
import { orchestrateInterfaceSchemaRefine } from "./orchestrateInterfaceSchemaRefine";
import { orchestrateInterfaceSchemaRename } from "./orchestrateInterfaceSchemaRename";
import { orchestrateInterfaceSchemaReview } from "./orchestrateInterfaceSchemaReview";
import { orchestrateInterfaceSchemaWrite } from "./orchestrateInterfaceSchemaWrite";
import { AutoBeInterfaceSchemaReviewProgrammer } from "./programmers/AutoBeInterfaceSchemaReviewProgrammer";
import { AutoBeJsonSchemaCollection } from "./utils/AutoBeJsonSchemaCollection";
import { AutoBeJsonSchemaFactory } from "./utils/AutoBeJsonSchemaFactory";
import { AutoBeJsonSchemaNamingConvention } from "./utils/AutoBeJsonSchemaNamingConvention";
export const orchestrateInterfaceSchema = async (
ctx: AutoBeContext,
props: {
instruction: string;
operations: AutoBeOpenApi.IOperation[];
},
): Promise<Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>> => {
//----
// PREPARATIONS
//----
// MOCK DOCUMENT
const document: AutoBeOpenApi.IDocument = {
operations: props.operations,
components: {
authorizations: ctx.state().analyze?.actors ?? [],
schemas: {},
},
};
// RENAME REQUEST/RESPONSE BODY TYPE NAMES
const renameProgress: AutoBeProgressEventBase = {
completed: 0,
total: 0,
};
AutoBeJsonSchemaNamingConvention.normalize({
operations: document.operations,
collection: new AutoBeJsonSchemaCollection({}, {}),
});
await orchestrateInterfaceSchemaRename(ctx, {
operations: document.operations,
progress: renameProgress,
collection: new AutoBeJsonSchemaCollection({}, {}),
});
// PREPARE ITERATOR
const castingProgress: AutoBeProgressEventBase = {
completed: 0,
total: 0,
};
const refineProgress: AutoBeProgressEventBase = {
completed: 0,
total: 0,
};
const reviewProgress: AutoBeProgressEventBase = {
completed: 0,
total: 0,
};
//----
// LOGIC FUNCTION
//----
const iterate = async (
initialize: () => Promise<Record<string, AutoBeOpenApi.IJsonSchema>>,
) => {
const schemas: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive> = {};
const overwrite = async (
next: Record<
string,
AutoBeOpenApi.IJsonSchema | AutoBeOpenApi.IJsonSchemaDescriptive
>,
) => {
for (const [k, v] of Object.entries(next))
if (v === undefined) delete next[k];
if (Object.keys(next).length === 0) return;
// assign schemas
const collection: AutoBeJsonSchemaCollection =
new AutoBeJsonSchemaCollection(document.components.schemas, schemas);
collection.assign(
next as Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>,
);
collection.assign(
AutoBeJsonSchemaFactory.presets(new Set(Object.keys(schemas))),
);
// naming convention
AutoBeJsonSchemaNamingConvention.normalize({
operations: document.operations,
collection,
});
await orchestrateInterfaceSchemaRename(ctx, {
operations: document.operations,
progress: renameProgress,
collection,
});
// special logics
AutoBeJsonSchemaFactory.fixPaginationSchemas(document.components.schemas);
AutoBeJsonSchemaFactory.fixAuthorizationSchemas(
document.components.schemas,
);
AutoBeJsonSchemaFactory.finalize({
application: ctx.state().database!.result.data,
operations: document.operations,
collection,
});
};
// initialize schemas
await overwrite(await initialize());
// type casting
await overwrite(
await orchestrateInterfaceSchemaCasting(ctx, {
instruction: props.instruction,
document: {
operations: document.operations,
components: {
authorizations: ctx.state().analyze?.actors ?? [],
schemas: document.components.schemas,
},
},
schemas,
progress: castingProgress,
}),
);
// refine schemas
await overwrite(
await orchestrateInterfaceSchemaRefine(ctx, {
instruction: props.instruction,
document,
schemas,
progress: refineProgress,
}),
);
// review schemas
const REVIEW_ITERATIONS = 1;
reviewProgress.total +=
Object.entries(schemas).filter(([k, v]) =>
AutoBeInterfaceSchemaReviewProgrammer.filter(k, v),
).length * REVIEW_ITERATIONS;
for (let i: number = 0; i < REVIEW_ITERATIONS; i++)
await overwrite(
await orchestrateInterfaceSchemaReview(ctx, {
instruction: props.instruction,
document,
schemas,
progress: reviewProgress,
}),
);
};
//----
// SCHEMA GENERATION LOOP
//----
// INITIAL SCHEMAS
await iterate(() =>
orchestrateInterfaceSchemaWrite(ctx, {
instruction: props.instruction,
operations: document.operations,
}),
);
// COMPLEMENTATION
const complementProgress: AutoBeProgressEventBase = {
completed: 0,
total: 0,
};
const failures: Map<string, number> = new Map();
while (missedOpenApiSchemas(document).length !== 0)
await iterate(() =>
orchestrateInterfaceSchemaComplement(ctx, {
instruction: props.instruction,
progress: complementProgress,
failures,
document,
}),
);
// DECOUPLE - break cross-type circular references (A->B->C->A)
await orchestrateInterfaceSchemaDecouple(ctx, {
schemas: document.components.schemas,
operations: document.operations,
});
AutoBeJsonSchemaFactory.removeUnused({
operations: document.operations,
schemas: document.components.schemas,
});
return document.components.schemas;
};