@autobe/agent
Version:
AI backend server code generator
465 lines (450 loc) • 17.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeRealizeTransformerTemplate = writeRealizeTransformerTemplate;
const utils_1 = require("@autobe/utils");
const AutoBeRealizeTransformerProgrammer_1 = require("../AutoBeRealizeTransformerProgrammer");
function writeRealizeTransformerTemplate(props) {
const relations = AutoBeRealizeTransformerProgrammer_1.AutoBeRealizeTransformerProgrammer.getRecursiveRelations({
schemas: props.schemas,
typeName: props.plan.dtoTypeName,
});
if (relations.parent !== null || relations.children !== null)
return writeRecursiveTemplate({
plan: props.plan,
schema: props.schema,
parentProperty: relations.parent,
childrenProperty: relations.children,
model: props.model,
});
const neighborRelations = props.neighbors && props.relations
? AutoBeRealizeTransformerProgrammer_1.AutoBeRealizeTransformerProgrammer.computeNeighborRelations({
schema: props.schema,
neighbors: props.neighbors,
relations: props.relations,
})
: [];
return writeNormalTemplate({
plan: props.plan,
schema: props.schema,
neighborRelations,
model: props.model,
});
}
function isScalarProperty(schema) {
if (utils_1.AutoBeOpenApiTypeChecker.isString(schema))
return true;
if (utils_1.AutoBeOpenApiTypeChecker.isNumber(schema))
return true;
if (utils_1.AutoBeOpenApiTypeChecker.isInteger(schema))
return true;
if (utils_1.AutoBeOpenApiTypeChecker.isBoolean(schema))
return true;
if (utils_1.AutoBeOpenApiTypeChecker.isConstant(schema))
return true;
if (utils_1.AutoBeOpenApiTypeChecker.isNull(schema))
return true;
if (utils_1.AutoBeOpenApiTypeChecker.isOneOf(schema))
return schema.oneOf.every((s) => isScalarProperty(s));
return false;
}
function buildSelectEntries(props) {
if (props.model) {
return buildSelectEntriesFromModel({
schema: props.schema,
skipKeys: props.skipKeys,
neighborRelations: props.neighborRelations,
model: props.model,
});
}
return buildSelectEntriesFromDto(props);
}
function buildSelectEntriesFromModel(props) {
const entries = [];
let hasUnresolved = false;
const coveredRelations = new Set(props.neighborRelations.map((n) => n.relationKey));
// Primary key
if (!props.skipKeys.has(props.model.primaryField.name)) {
entries.push(`${props.model.primaryField.name}: true,`);
}
// Plain scalar fields
for (const field of props.model.plainFields) {
if (props.skipKeys.has(field.name))
continue;
entries.push(`${field.name}: true,`);
}
// Foreign keys / belongsTo relations
for (const fk of props.model.foreignFields) {
if (props.skipKeys.has(fk.name) || props.skipKeys.has(fk.relation.name))
continue;
if (coveredRelations.has(fk.relation.name)) {
const nr = props.neighborRelations.find((n) => n.relationKey === fk.relation.name);
entries.push(`${nr.relationKey}: ${nr.transformerName}.select(),`);
}
else {
entries.push(`${fk.name}: true,`);
}
}
// hasMany/hasOne relations covered by neighbors (not already handled via FK)
for (const nr of props.neighborRelations) {
if (props.skipKeys.has(nr.relationKey))
continue;
const isBelongsTo = props.model.foreignFields.some((f) => f.relation.name === nr.relationKey);
if (!isBelongsTo) {
entries.push(`${nr.relationKey}: ${nr.transformerName}.select(),`);
}
}
// Check for unresolved non-scalar DTO properties
for (const k of Object.keys(props.schema.properties)) {
if (props.skipKeys.has(k))
continue;
if (props.neighborRelations.some((n) => n.dtoProperty === k))
continue;
if (!isScalarProperty(props.schema.properties[k])) {
hasUnresolved = true;
break;
}
}
return { entries, hasUnresolved };
}
function buildSelectEntriesFromDto(props) {
const entries = [];
let hasUnresolved = false;
for (const k of Object.keys(props.schema.properties)) {
if (props.skipKeys.has(k))
continue;
const nr = props.neighborRelations.find((n) => n.dtoProperty === k);
if (nr) {
entries.push(`${nr.relationKey}: ${nr.transformerName}.select(),`);
}
else if (isScalarProperty(props.schema.properties[k])) {
entries.push(`${k}: true,`);
}
else {
hasUnresolved = true;
}
}
return { entries, hasUnresolved };
}
function formatSelectBody(entries, hasUnresolved) {
return [...entries, ...(hasUnresolved ? ["..."] : [])].join("\n ");
}
/**
* Find the self-referential FK field for recursive templates. If multiple
* self-referential FKs exist, try to match by DTO property name.
*/
function findRecursiveFk(model, dtoProperty) {
var _a;
if (!model)
return undefined;
const selfFks = model.foreignFields.filter((f) => f.relation.targetModel === model.name);
if (selfFks.length === 1)
return selfFks[0];
return (_a = selfFks.find((f) => f.relation.name === dtoProperty)) !== null && _a !== void 0 ? _a : selfFks[0];
}
function writeNormalTemplate(props) {
const name = AutoBeRealizeTransformerProgrammer_1.AutoBeRealizeTransformerProgrammer.getName(props.plan.dtoTypeName);
const dto = props.plan.dtoTypeName;
const table = props.plan.databaseSchemaName;
const transformBody = Object.keys(props.schema.properties)
.map((k) => {
const nr = props.neighborRelations.find((n) => n.dtoProperty === k);
if (!nr) {
const hint = utils_1.AutoBeOpenApiTypeChecker.getTypeName(props.schema.properties[k]);
return ` ${k}: {${hint}},`;
}
if (nr.isArray) {
const call = `await ArrayUtil.asyncMap(input.${nr.relationKey}, ${nr.transformerName}.transform)`;
if (nr.isNullable)
return ` ${k}: input.${nr.relationKey} ? ${call} : null,`;
return ` ${k}: ${call},`;
}
if (nr.isNullable)
return ` ${k}: input.${nr.relationKey} ? await ${nr.transformerName}.transform(input.${nr.relationKey}) : null,`;
return ` ${k}: await ${nr.transformerName}.transform(input.${nr.relationKey}),`;
})
.join("\n");
const { entries, hasUnresolved } = buildSelectEntries({
schema: props.schema,
skipKeys: new Set(),
neighborRelations: props.neighborRelations,
model: props.model,
});
const selectBody = formatSelectBody(entries, hasUnresolved);
return utils_1.StringUtil.trim `
export namespace ${name} {
export type Payload = Prisma.${table}GetPayload<ReturnType<typeof select>>;
export function select() {
// implicit return type for better type inference
return {
select: {
${selectBody}
},
} satisfies Prisma.${table}FindManyArgs;
}
export async function transform(input: Payload): Promise<${dto}> {
return {
${transformBody}
};
}
}
`;
}
function writeRecursiveTemplate(props) {
const { parentProperty: pp, childrenProperty: cp } = props;
if (pp !== null && cp !== null)
return writeBothRecursiveTemplate(Object.assign(Object.assign({}, props), { parentProperty: pp, childrenProperty: cp }));
if (pp !== null)
return writeParentOnlyRecursiveTemplate(Object.assign(Object.assign({}, props), { parentProperty: pp }));
return writeChildrenOnlyRecursiveTemplate(Object.assign(Object.assign({}, props), { childrenProperty: cp }));
}
function writeParentOnlyRecursiveTemplate(props) {
var _a, _b;
const name = AutoBeRealizeTransformerProgrammer_1.AutoBeRealizeTransformerProgrammer.getName(props.plan.dtoTypeName);
const dto = props.plan.dtoTypeName;
const table = props.plan.databaseSchemaName;
const pp = props.parentProperty;
const selfFk = findRecursiveFk(props.model, pp);
const fk = (_a = selfFk === null || selfFk === void 0 ? void 0 : selfFk.name) !== null && _a !== void 0 ? _a : `${pp}_id`;
const relationName = (_b = selfFk === null || selfFk === void 0 ? void 0 : selfFk.relation.name) !== null && _b !== void 0 ? _b : pp;
const transformBody = Object.keys(props.schema.properties)
.map((k) => k === pp
? ` ${k}: input.${fk} ? await cache.get(input.${fk}) : null,`
: ` ${k}: {${utils_1.AutoBeOpenApiTypeChecker.getTypeName(props.schema.properties[k])}},`)
.join("\n");
const skipKeys = new Set([fk, relationName, pp]);
const { entries, hasUnresolved } = buildSelectEntries({
schema: props.schema,
skipKeys,
neighborRelations: [],
model: props.model,
});
const selectBody = formatSelectBody([
...entries,
`${fk}: true,`,
`${relationName}: undefined, // DO NOT select recursive relation`,
], hasUnresolved);
return utils_1.StringUtil.trim `
export namespace ${name} {
export type Payload = Prisma.${table}GetPayload<ReturnType<typeof select>>;
export function select() {
// implicit return type for better type inference
return {
select: {
${selectBody}
},
} satisfies Prisma.${table}FindManyArgs;
}
export async function transform(
input: Payload,
cache: VariadicSingleton<Promise<${dto}>, [string]> = createParentCache(),
): Promise<${dto}> {
return {
${transformBody}
};
}
export async function transformAll(
inputs: Payload[],
): Promise<${dto}[]> {
const cache = createParentCache();
return await ArrayUtil.asyncMap(inputs, (x) => transform(x, cache));
}
function createParentCache() {
const cache = new VariadicSingleton(
async (id: string): Promise<${dto}> => {
const record =
await MyGlobal.prisma.${table}.findFirstOrThrow({
...select(),
where: { id },
});
return transform(record, cache);
},
);
return cache;
}
}
`;
}
function writeChildrenOnlyRecursiveTemplate(props) {
var _a;
const name = AutoBeRealizeTransformerProgrammer_1.AutoBeRealizeTransformerProgrammer.getName(props.plan.dtoTypeName);
const dto = props.plan.dtoTypeName;
const table = props.plan.databaseSchemaName;
const cp = props.childrenProperty;
const selfFk = findRecursiveFk(props.model, cp);
const fk = (_a = selfFk === null || selfFk === void 0 ? void 0 : selfFk.name) !== null && _a !== void 0 ? _a : "parent_id";
const transformBody = Object.keys(props.schema.properties)
.map((k) => k === cp
? ` ${k}: await cache.get(input.id),`
: ` ${k}: {${utils_1.AutoBeOpenApiTypeChecker.getTypeName(props.schema.properties[k])}},`)
.join("\n");
const { entries, hasUnresolved } = buildSelectEntries({
schema: props.schema,
skipKeys: new Set([cp]),
neighborRelations: [],
model: props.model,
});
const selectBody = formatSelectBody([...entries, `${cp}: undefined, // DO NOT select recursive relation`], hasUnresolved);
return utils_1.StringUtil.trim `
export namespace ${name} {
export type Payload = Prisma.${table}GetPayload<ReturnType<typeof select>>;
export function select() {
// implicit return type for better type inference
return {
select: {
${selectBody}
},
} satisfies Prisma.${table}FindManyArgs;
}
export async function transform(
input: Payload,
cache: VariadicSingleton<Promise<${dto}[]>, [string]> = createChildrenCache(),
): Promise<${dto}> {
return {
${transformBody}
};
}
export async function transformAll(
inputs: Payload[],
): Promise<${dto}[]> {
const cache = createChildrenCache();
return await ArrayUtil.asyncMap(inputs, (x) => transform(x, cache));
}
function createChildrenCache() {
const cache = new VariadicSingleton(
async (parentId: string): Promise<${dto}[]> => {
const records =
await MyGlobal.prisma.${table}.findMany({
...select(),
where: { ${fk}: parentId },
});
return await ArrayUtil.asyncMap(records, (r) => transform(r, cache));
},
);
return cache;
}
}
`;
}
function writeBothRecursiveTemplate(props) {
var _a, _b;
const name = AutoBeRealizeTransformerProgrammer_1.AutoBeRealizeTransformerProgrammer.getName(props.plan.dtoTypeName);
const dto = props.plan.dtoTypeName;
const table = props.plan.databaseSchemaName;
const pp = props.parentProperty;
const cp = props.childrenProperty;
const selfFk = findRecursiveFk(props.model, pp);
const fk = (_a = selfFk === null || selfFk === void 0 ? void 0 : selfFk.name) !== null && _a !== void 0 ? _a : `${pp}_id`;
const relationName = (_b = selfFk === null || selfFk === void 0 ? void 0 : selfFk.relation.name) !== null && _b !== void 0 ? _b : pp;
const transformBody = Object.keys(props.schema.properties)
.map((k) => {
if (k === pp)
return ` ${k}: input.${fk} ? await parentCache.get(input.${fk}) : null,`;
if (k === cp)
return ` ${k}: await childrenCache.get(input.id),`;
return ` ${k}: {${utils_1.AutoBeOpenApiTypeChecker.getTypeName(props.schema.properties[k])}},`;
})
.join("\n");
const skipKeys = new Set([fk, relationName, pp, cp]);
const { entries, hasUnresolved } = buildSelectEntries({
schema: props.schema,
skipKeys,
neighborRelations: [],
model: props.model,
});
const selectBody = formatSelectBody([
...entries,
`${fk}: true,`,
`${relationName}: undefined, // DO NOT select recursive relation`,
`${cp}: undefined, // DO NOT select recursive relation`,
], hasUnresolved);
return utils_1.StringUtil.trim `
export namespace ${name} {
export type Payload = Prisma.${table}GetPayload<ReturnType<typeof select>>;
export function select() {
// implicit return type for better type inference
return {
select: {
${selectBody}
},
} satisfies Prisma.${table}FindManyArgs;
}
export async function transform(
input: Payload,
parentCache: VariadicSingleton<Promise<${dto}>, [string]> = createParentCache(),
childrenCache: VariadicSingleton<Promise<${dto}[]>, [string]> = createChildrenCache(),
): Promise<${dto}> {
return {
${transformBody}
};
}
export async function transformAll(
inputs: Payload[],
): Promise<${dto}[]> {
// Create mutually-referencing caches so the entire tree shares
// one deduplication scope across both parent and children lookups.
// Use definite assignment assertions (!) so TypeScript does not
// flag the cross-references as "used before assigned" — the async
// callbacks only execute after both variables are fully initialized.
let parentCache!: VariadicSingleton<Promise<${dto}>, [string]>;
let childrenCache!: VariadicSingleton<Promise<${dto}[]>, [string]>;
parentCache = new VariadicSingleton(
async (id: string): Promise<${dto}> => {
const record =
await MyGlobal.prisma.${table}.findFirstOrThrow({
...select(),
where: { id },
});
return transform(record, parentCache, childrenCache);
},
);
childrenCache = new VariadicSingleton(
async (parentId: string): Promise<${dto}[]> => {
const records =
await MyGlobal.prisma.${table}.findMany({
...select(),
where: { ${fk}: parentId },
});
return await ArrayUtil.asyncMap(records, (r) =>
transform(r, parentCache, childrenCache),
);
},
);
return await ArrayUtil.asyncMap(inputs, (x) =>
transform(x, parentCache, childrenCache),
);
}
function createParentCache() {
const cache = new VariadicSingleton(
async (id: string): Promise<${dto}> => {
const record =
await MyGlobal.prisma.${table}.findFirstOrThrow({
...select(),
where: { id },
});
return transform(record, cache);
},
);
return cache;
}
function createChildrenCache() {
const cache = new VariadicSingleton(
async (parentId: string): Promise<${dto}[]> => {
const records =
await MyGlobal.prisma.${table}.findMany({
...select(),
where: { ${fk}: parentId },
});
// createParentCache() is called once per batch so all siblings
// in the same children list share one parent-deduplication scope.
const parentCache = createParentCache();
return await ArrayUtil.asyncMap(records, (r) =>
transform(r, parentCache, cache),
);
},
);
return cache;
}
}
`;
}
//# sourceMappingURL=writeRealizeTransformerTemplate.js.map