UNPKG

iagate-querykit

Version:

QueryKit: lightweight TypeScript query toolkit with models, views, triggers, events, scheduler and adapters (better-sqlite3).

99 lines (98 loc) 3.81 kB
import { QueryBuilder } from './query-builder'; /** * Anexa relações a registros de uma tabela baseado em definições de relacionamento. * Suporta hasMany, belongsTo e manyToMany com carregamento lazy. * * @param table - Nome da tabela principal * @param rows - Array de registros para anexar relações * @param selector - Função opcional para selecionar relações específicas * @returns Promise que resolve com registros com relações anexadas * * @example * ```typescript * // Dados iniciais * const users = [ * { id: 1, name: 'John' }, * { id: 2, name: 'Jane' } * ]; * * // Como usar * const usersWithRelations = await attachRelations('users', users, (rel) => { * rel('posts', ['id', 'title']); * rel('profile'); * }); * * // Output: Usuários com posts e profile carregados * // usersWithRelations[0].posts = [{ id: 1, title: 'Post 1' }] * // usersWithRelations[0].profile = { bio: 'Developer' } * ``` */ export async function attachRelations(table, rows, selector) { if (!rows || rows.length === 0) return rows; let registry = {}; try { registry = (await import(process.env.QK_RELATIONS_PATH || '')).RELATIONS || {}; } catch { } const defs = registry[table] || []; if (defs.length === 0) return rows; let wanted = 'ALL'; if (selector) { const tmp = {}; selector((name, select) => { tmp[name] = select; }); wanted = tmp; } const byId = new Map(); for (const r of rows) byId.set(r.id, r); for (const def of defs) { if (wanted !== 'ALL' && !(def.name in wanted)) continue; // Relação hasMany: um para muitos if (def.kind === 'hasMany') { const ids = rows.map(r => r[def.localKey || 'id']); const children = await new QueryBuilder(def.table).whereIn(def.foreignKey, ids).all(); for (const c of children) { const parent = byId.get(c[def.foreignKey]); if (!parent) continue; parent[def.name] = parent[def.name] || []; parent[def.name].push(c); } } // Relação belongsTo: muitos para um if (def.kind === 'belongsTo') { const fks = rows.map(r => r[def.foreignKey]).filter((v) => v !== undefined && v !== null); if (fks.length === 0) continue; const parents = await new QueryBuilder(def.table).whereIn(def.ownerKey || 'id', fks).all(); const parentByKey = new Map(parents.map((p) => [p[def.ownerKey || 'id'], p])); for (const r of rows) r[def.name] = parentByKey.get(r[def.foreignKey]) || null; } // Relação manyToMany: muitos para muitos através de tabela pivot if (def.kind === 'manyToMany') { const ids = rows.map(r => r.id); const join = def.through.table; const leftKey = def.through.leftKey; const rightKey = def.through.rightKey; const links = await new QueryBuilder(join).whereIn(leftKey, ids).all(); const rightIds = links.map((l) => l[rightKey]); const rights = rightIds.length ? await new QueryBuilder(def.table).whereIn('id', rightIds).all() : []; const rightById = new Map(rights.map((x) => [x.id, x])); const bucket = new Map(); for (const l of links) { const arr = bucket.get(l[leftKey]) || []; const item = rightById.get(l[rightKey]); if (item) arr.push(item); bucket.set(l[leftKey], arr); } for (const r of rows) r[def.name] = bucket.get(r.id) || []; } } return rows; }