UNPKG

@autobe/agent

Version:

AI backend server code generator

465 lines (450 loc) 17.9 kB
"use strict"; 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