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
JavaScript
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);
}
}
}