import { isObject } from "bknd/utils"; import type { KyselyJsonFrom } from "data/relations/EntityRelation"; import type { RepoQuery } from "data/server/query"; import { InvalidSearchParamsException } from "data/errors"; import type { Entity, EntityManager, RepositoryQB } from "data/entities"; import { $console } from "bknd/utils"; export class WithBuilder { static addClause( em: EntityManager, qb: RepositoryQB, entity: Entity, withs: RepoQuery["with"], ) { if (!withs || !isObject(withs)) { $console.warn(`'withs' undefined or invalid, given: ${JSON.stringify(withs)}`); return qb; } const fns = em.connection.fn; let newQb = qb; for (const [ref, query] of Object.entries(withs)) { const relation = em.relationOf(entity.name, ref); if (!relation) { throw new Error(`Relation "${entity.name}<>${ref}" not found`); } const cardinality = relation.ref(ref).cardinality; const jsonFrom: KyselyJsonFrom = cardinality === 1 ? fns.jsonObjectFrom : fns.jsonArrayFrom; if (!jsonFrom) { throw new Error("Connection does not support jsonObjectFrom/jsonArrayFrom"); } const other = relation.other(entity); newQb = newQb.select((eb) => { let subQuery = relation.buildWith(entity, ref)(eb); if (query) { subQuery = em.repo(other.entity).addOptionsToQueryBuilder(subQuery, query as any, { ignore: ["with", cardinality === 1 ? "limit" : undefined].filter(Boolean) as any, alias: other.reference, }); } if (query.with) { subQuery = WithBuilder.addClause(em, subQuery, other.entity, query.with as any); } return jsonFrom(subQuery).as(other.reference); }); } return newQb; } static validateWiths(em: EntityManager, entity: string, withs: RepoQuery["with"]) { let depth = 0; if (!withs || !isObject(withs)) { withs && $console.warn(`'withs' invalid, given: ${JSON.stringify(withs)}`); return depth; } const child_depths: number[] = []; for (const [ref, query] of Object.entries(withs)) { const related = em.relationOf(entity, ref); if (!related) { throw new InvalidSearchParamsException( `WITH: "${ref}" is not a relation of "${entity}"`, ); } depth++; if ("with" in query) { child_depths.push(WithBuilder.validateWiths(em, ref, query.with as any)); } } if (child_depths.length > 0) { depth += Math.max(...child_depths); } return depth; } }