@autobe/agent
Version:
AI backend server code generator
248 lines (231 loc) • 7.17 kB
text/typescript
import {
AutoBeAnalyzeHistory,
AutoBeAssistantMessageHistory,
AutoBeDatabase,
AutoBeDatabaseCompleteEvent,
AutoBeDatabaseComponent,
AutoBeDatabaseComponentTableDesign,
AutoBeDatabaseGroup,
AutoBeDatabaseHistory,
AutoBeDatabaseSchemaDefinition,
AutoBeDatabaseSchemaEvent,
AutoBeProgressEventBase,
IAutoBeCompiler,
IAutoBeDatabaseValidation,
} from "@autobe/interface";
import { writePrismaApplication } from "@autobe/utils";
import { NamingConvention } from "@typia/utils";
import { v7 } from "uuid";
import { AutoBeContext } from "../../context/AutoBeContext";
import { predicateStateMessage } from "../../utils/predicateStateMessage";
import { IAutoBeFacadeApplicationProps } from "../facade/histories/IAutoBeFacadeApplicationProps";
import { orchestrateDatabaseAuthorization } from "./orchestrateDatabaseAuthorization";
import { orchestrateDatabaseComponent } from "./orchestrateDatabaseComponent";
import { orchestrateDatabaseCorrect } from "./orchestrateDatabaseCorrect";
import { orchestrateDatabaseGroup } from "./orchestrateDatabaseGroup";
import { orchestrateDatabaseSchema } from "./orchestrateDatabaseSchema";
export const orchestrateDatabase = async (
ctx: AutoBeContext,
props: IAutoBeFacadeApplicationProps,
): Promise<AutoBeDatabaseHistory | AutoBeAssistantMessageHistory> => {
// PREDICATION
const start: Date = new Date();
const predicate: string | null = predicateStateMessage(
ctx.state(),
"database",
);
if (predicate !== null)
return ctx.assistantMessage({
type: "assistantMessage",
id: v7(),
created_at: start.toISOString(),
text: predicate,
completed_at: new Date().toISOString(),
});
ctx.dispatch({
type: "databaseStart",
id: v7(),
created_at: start.toISOString(),
reason: props.instruction,
step: ctx.state().analyze?.step ?? 0,
});
// NORMALIZE PREFIX
const analyze: AutoBeAnalyzeHistory | null = ctx.state().analyze;
if (analyze?.prefix) {
analyze.prefix = NamingConvention.snake(analyze.prefix);
}
// GROUPS
const groups: AutoBeDatabaseGroup[] = await orchestrateGroup(ctx, props);
const components: AutoBeDatabaseComponent[] = await orchestrateComponent(
ctx,
{
groups,
instruction: props.instruction,
},
);
const application: AutoBeDatabase.IApplication = await orchestrateSchema(
ctx,
{
instruction: props.instruction,
components,
},
);
// VALIDATE
const validation: IAutoBeDatabaseValidation =
await orchestrateDatabaseCorrect(ctx, application);
const files: Record<string, string> = writePrismaApplication({
dbms: "postgres",
application: validation.data,
});
// PROPAGATE
const compiler: IAutoBeCompiler = await ctx.compiler();
return ctx.dispatch({
type: "databaseComplete",
id: v7(),
result: validation,
schemas: files,
compiled: await compiler.database.compilePrismaSchemas({
files,
}),
aggregates: ctx.getCurrentAggregates("database"),
step: ctx.state().analyze?.step ?? 0,
elapsed: new Date().getTime() - start.getTime(),
created_at: new Date().toISOString(),
} satisfies AutoBeDatabaseCompleteEvent);
};
const orchestrateGroup = async (
ctx: AutoBeContext,
props: IAutoBeFacadeApplicationProps,
): Promise<AutoBeDatabaseGroup[]> => {
return await orchestrateDatabaseGroup(ctx, props.instruction);
};
const orchestrateAuthorization = async (
ctx: AutoBeContext,
props: {
instruction: string;
groups: AutoBeDatabaseGroup[];
},
): Promise<AutoBeDatabaseComponent | null> => {
return await orchestrateDatabaseAuthorization(ctx, {
instruction: props.instruction,
groups: props.groups,
});
};
const orchestrateComponent = async (
ctx: AutoBeContext,
props: {
instruction: string;
groups: AutoBeDatabaseGroup[];
},
): Promise<AutoBeDatabaseComponent[]> => {
const authorization: AutoBeDatabaseComponent | null =
await orchestrateAuthorization(ctx, {
groups: props.groups,
instruction: props.instruction,
});
const components: AutoBeDatabaseComponent[] =
await orchestrateDatabaseComponent(ctx, {
instruction: props.instruction,
groups: props.groups,
});
return [...(authorization ? [authorization] : []), ...components];
};
const orchestrateSchema = async (
ctx: AutoBeContext,
props: {
instruction: string;
components: AutoBeDatabaseComponent[];
},
): Promise<AutoBeDatabase.IApplication> => {
//----
// STATES
//----
// clone groups to keep previous events
const components: AutoBeDatabaseComponent[] = props.components.map((c) => ({
...c,
tables: c.tables.slice(),
}));
// completion set
const written: Set<string> = new Set();
const failed: Map<string, number> = new Map();
const complete = () =>
components
.flatMap((g) => g.tables)
.every((t) => written.has(t.name) === true);
// generated models
interface IModelPair {
namespace: string;
model: AutoBeDatabase.IModel;
}
const pairs: IModelPair[] = [];
//----
// DEFINER
//----
const application = (): AutoBeDatabase.IApplication => ({
files: components.map((comp) => ({
filename: comp.filename,
namespace: comp.namespace,
models: pairs
.filter((p) => p.namespace === comp.namespace)
.map((p) => p.model),
})),
});
const define = (next: {
namespace: string;
definition: AutoBeDatabaseSchemaDefinition;
}): void => {
// find parent component and matched design
const myComponent: AutoBeDatabaseComponent = components.find(
(c) => c.namespace === next.namespace,
)!;
const myTable: AutoBeDatabaseComponentTableDesign = myComponent.tables.find(
(t) => t.name === next.definition.model.name,
)!;
// mark as done
written.add(myTable.name);
const existing: number = pairs.findIndex(
(p) =>
p.namespace === next.namespace &&
p.model.name === next.definition.model.name,
);
if (existing !== -1)
pairs[existing] = {
namespace: next.namespace,
model: next.definition.model,
};
else
pairs.push({ namespace: next.namespace, model: next.definition.model });
// prepare new designs
for (const design of next.definition.newDesigns)
if (
written.has(design.name) === false &&
myComponent.tables.find((t) => t.name === design.name) === undefined &&
components
.flatMap((c) => c.tables)
.find((t) => t.name === design.name) === undefined
)
myComponent.tables.push(design);
};
//----
// THE LOOP
//----
const writeProgress: AutoBeProgressEventBase = { total: 0, completed: 0 };
while (complete() === false) {
const events: AutoBeDatabaseSchemaEvent[] = await orchestrateDatabaseSchema(
ctx,
{
instruction: props.instruction,
components,
written,
failed,
progress: writeProgress,
},
);
for (const e of events)
define({
namespace: e.namespace,
definition: e.definition,
});
}
return application();
};