UNPKG

iagate-querykit

Version:

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

258 lines (257 loc) 7.75 kB
import { QueryBuilder } from './query-builder'; import { runSeed } from './seed'; /** * Classe base para modelos de dados no QueryKit. * Fornece funcionalidades de ORM básicas como queries, relacionamentos e seeds. * Suporta múltiplos bancos de dados e controle de atributos fillable/guarded. * * @example * ```typescript * // Dados iniciais * class User extends Model { * protected static tableName = 'users'; * protected fillable = ['name', 'email', 'age']; * protected guarded = ['id', 'created_at', 'updated_at', 'password']; * } * * // Como usar * const user = new User(); * user.fill({ name: 'John', email: 'john@example.com', age: 30 }); * await user.save(); * * // Output: Usuário salvo no banco de dados * ``` */ export class Model { /** Nome da tabela para o modelo */ static tableName; /** Bancos de dados estáticos para o modelo */ static banks; /** Bancos de dados de instância para o modelo */ banks; /** Atributos que podem ser preenchidos */ fillable = []; /** Atributos que não podem ser preenchidos */ guarded = ['id', 'created_at', 'updated_at']; /** * Cria um QueryBuilder estático para o modelo. * Configura automaticamente o nome da tabela e bancos. * * @returns QueryBuilder configurado para o modelo * * @example * ```typescript * // Dados iniciais * class User extends Model { * protected static tableName = 'users'; * protected static banks = ['main_db', 'replica_db']; * } * * // Como usar * const query = User.query().where('active', true); * const activeUsers = await query.all(); * * // Output: QueryBuilder configurado para tabela 'users' com bancos configurados * ``` */ static query() { const qb = new QueryBuilder(this.tableName); const banks = this.banks; if (banks && banks.length) qb.bank(banks); return qb; } /** * Cria um QueryBuilder com relacionamentos pré-carregados. * * @param selector - Seletor opcional para definir quais relacionamentos carregar * @returns QueryBuilder configurado com relacionamentos * * @example * ```typescript * // Dados iniciais * class User extends Model { * protected static tableName = 'users'; * } * * // Como usar * const query = User.withRelations((rel) => { * rel('posts', ['id', 'title']); * rel('profile'); * }); * const usersWithRelations = await query.all(); * * // Output: QueryBuilder com relacionamentos configurados * ``` */ static withRelations(selector) { const qb = this.query().relationship(selector); return qb; } /** * Executa seed para o modelo. * * @param dataOrSeed - Dados para inserir ou classe seed executável * @param opts - Opções incluindo truncate * @returns Promise que resolve com número de linhas inseridas * * @example * ```typescript * // Dados iniciais * class User extends Model { * protected static tableName = 'users'; * } * * const userData = [ * { name: 'John', email: 'john@example.com' }, * { name: 'Jane', email: 'jane@example.com' } * ]; * * // Como usar * const insertedRows = await User.seed(userData, { truncate: true }); * * // Output: 2 (usuários inseridos na tabela) * ``` */ static async seed(dataOrSeed, opts = {}) { return runSeed(this.tableName, dataOrSeed, opts); } /** * Define bancos de dados para a instância do modelo. * * @param bankOrBanks - Nome do banco ou array de nomes * @returns Instância do modelo para method chaining * * @example * ```typescript * // Dados iniciais * const user = new User(); * * // Como usar * user.bank('analytics_db'); * // ou * user.bank(['main_db', 'backup_db']); * * // Output: Modelo configurado para usar bancos específicos * ``` */ bank(bankOrBanks) { this.banks = Array.isArray(bankOrBanks) ? bankOrBanks : [bankOrBanks]; return this; } /** * Aplica configuração de bancos a um QueryBuilder. * * @param qb - QueryBuilder para aplicar bancos * @returns QueryBuilder com bancos configurados */ applyBanks(qb) { if (this.banks && this.banks.length) return qb.bank(this.banks); return qb; } /** * Preenche atributos do modelo baseado em fillable/guarded. * Respeita as regras de segurança definidas no modelo. * * @param attributes - Atributos para preencher * * @example * ```typescript * // Dados iniciais * class User extends Model { * protected fillable = ['name', 'email']; * protected guarded = ['id', 'password']; * } * * const user = new User(); * * // Como usar * user.fill({ name: 'John', email: 'john@example.com', id: 999, password: 'secret' }); * * // Output: Apenas name e email são preenchidos, id e password são ignorados * ``` */ fill(attributes) { const fillableAttributes = this.getFillableAttributes(attributes); Object.assign(this, fillableAttributes); } /** * Filtra atributos baseado em fillable/guarded. * * @param attributes - Atributos para filtrar * @returns Atributos filtrados e seguros */ getFillableAttributes(attributes) { if (this.fillable.length > 0) { const result = {}; this.fillable.forEach(key => { if (attributes[key] !== undefined) result[key] = attributes[key]; }); return result; } if (this.guarded.length > 0) { const result = { ...attributes }; this.guarded.forEach(key => { delete result[key]; }); return result; } return attributes; } /** * Salva o modelo no banco de dados. * Cria novo registro se não tiver ID, atualiza se tiver. * * @returns Promise que resolve com resultado da operação * * @example * ```typescript * // Dados iniciais * const user = new User(); * user.fill({ name: 'John', email: 'john@example.com' }); * * // Como usar * const result = await user.save(); * * // Output: Usuário inserido no banco com ID gerado * * // Para atualizar * user.id = 1; * user.name = 'John Doe'; * await user.save(); * * // Output: Usuário atualizado no banco * ``` */ save() { const queryBase = this.constructor.query(); const query = this.applyBanks(queryBase); const attributes = this.getFillableAttributes(this); if (this.id) { return query.where('id', '=', this.id).update(attributes).make(); } return query.insert(attributes).make(); } /** * Remove o modelo do banco de dados. * Requer que o modelo tenha ID definido. * * @returns Promise que resolve com resultado da operação * @throws Error se não tiver ID definido * * @example * ```typescript * // Dados iniciais * const user = new User(); * user.id = 1; * * // Como usar * const result = await user.delete(); * * // Output: Usuário removido do banco de dados * ``` */ delete() { const queryBase = this.constructor.query(); const query = this.applyBanks(queryBase); return query.where('id', '=', this.id).delete().make(); } }