UNPKG

adonis-odm

Version:

A comprehensive MongoDB ODM for AdonisJS with Lucid-style API, type-safe relationships, embedded documents, and transaction support

209 lines (208 loc) 7.58 kB
import { ModelNotFoundException, DatabaseOperationException } from '../exceptions/index.js'; /** * StaticQueryMethods - Handles all static query methods for BaseModel * * This class encapsulates all the static methods for querying and creating * model instances like find, create, all, etc. */ export class StaticQueryMethods { /** * Create a new query builder instance with type-safe relationship loading */ static query(_modelClass, _options) { // This would be injected by the service provider // For now, we'll throw an error to indicate it needs to be set up throw new Error('Database connection not configured. Please register the MongoDB ODM provider.'); } /** * Find a document by its ID */ static async find(modelClass, id, options) { try { // Use the query builder which already has hooks integrated return await modelClass.query(options).where('_id', id).first(); } catch (error) { throw new DatabaseOperationException(`find by ID "${id}" on ${modelClass.name}`, error); } } /** * Find a document by its ID or throw an exception */ static async findOrFail(modelClass, id, options) { try { const result = await this.find(modelClass, id, options); if (!result) { throw new ModelNotFoundException(modelClass.name, id); } return result; } catch (error) { // Re-throw ModelNotFoundException as-is, wrap others if (error instanceof ModelNotFoundException) { throw error; } throw new DatabaseOperationException(`findOrFail by ID "${id}" on ${modelClass.name}`, error); } } /** * Find a document by a specific field */ static async findBy(modelClass, field, value) { try { // Use the query builder which already has hooks integrated return await modelClass.query().where(field, value).first(); } catch (error) { throw new DatabaseOperationException(`findBy ${field}="${value}" on ${modelClass.name}`, error); } } /** * Find a document by a specific field or throw an exception */ static async findByOrFail(modelClass, field, value) { try { const result = await this.findBy(modelClass, field, value); if (!result) { throw new ModelNotFoundException(modelClass.name, `${field}=${value}`); } return result; } catch (error) { // Re-throw ModelNotFoundException as-is, wrap others if (error instanceof ModelNotFoundException) { throw error; } throw new DatabaseOperationException(`findByOrFail ${field}="${value}" on ${modelClass.name}`, error); } } /** * Get the first document */ static async first(modelClass) { try { // Use the query builder which already has hooks integrated return await modelClass.query().first(); } catch (error) { throw new DatabaseOperationException(`first on ${modelClass.name}`, error); } } /** * Get the first document or throw an exception */ static async firstOrFail(modelClass) { try { const result = await this.first(modelClass); if (!result) { throw new ModelNotFoundException(modelClass.name); } return result; } catch (error) { // Re-throw ModelNotFoundException as-is, wrap others if (error instanceof ModelNotFoundException) { throw error; } throw new DatabaseOperationException(`firstOrFail on ${modelClass.name}`, error); } } /** * Get all documents */ static async all(modelClass) { try { // Use the query builder which already has hooks integrated return await modelClass.query().all(); } catch (error) { throw new DatabaseOperationException(`all on ${modelClass.name}`, error); } } /** * Create a new document */ static async create(modelClass, attributes, options) { try { const instance = new modelClass(attributes); if (options?.client) { instance.useTransaction(options.client); } await instance.save(); return instance; } catch (error) { throw new DatabaseOperationException(`create on ${modelClass.name}`, error); } } /** * Create multiple documents */ static async createMany(modelClass, attributesArray) { try { const instances = attributesArray.map((attributes) => new modelClass(attributes)); const savedInstances = []; // For now, save them one by one. In a real implementation, we'd use insertMany for (const [index, instance] of instances.entries()) { try { await instance.save(); savedInstances.push(instance); } catch (error) { // If one fails, we should provide context about which one failed throw new DatabaseOperationException(`createMany on ${modelClass.name} (failed at index ${index})`, error); } } return savedInstances; } catch (error) { // Re-throw DatabaseOperationException as-is, wrap others if (error instanceof DatabaseOperationException) { throw error; } throw new DatabaseOperationException(`createMany on ${modelClass.name}`, error); } } /** * Update or create a document */ static async updateOrCreate(modelClass, searchPayload, persistencePayload) { try { let query = modelClass.query(); // Add search conditions for (const [field, value] of Object.entries(searchPayload)) { query = query.where(field, value); } const result = await query.first(); if (result) { // Update existing try { const instance = new modelClass(); instance.hydrateFromDocument(result); instance.merge(persistencePayload); await instance.save(); return instance; } catch (error) { throw new DatabaseOperationException(`updateOrCreate (update) on ${modelClass.name}`, error); } } else { // Create new try { return this.create(modelClass, { ...searchPayload, ...persistencePayload }); } catch (error) { throw new DatabaseOperationException(`updateOrCreate (create) on ${modelClass.name}`, error); } } } catch (error) { // Re-throw DatabaseOperationException as-is, wrap others if (error instanceof DatabaseOperationException) { throw error; } throw new DatabaseOperationException(`updateOrCreate on ${modelClass.name}`, error); } } }