@warlock.js/cascade
Version:
ORM for managing databases
670 lines (669 loc) • 18.5 kB
JavaScript
import events from'@mongez/events';import {Random}from'@mongez/reinforcements';import {database}from'../database.js';class Query {
/**
* Connection instance
*/
database = database;
/**
* class event name
*/
eventName = "mongodb.query." + Random.id();
/**
* Set the database instance
*/
setDatabase(database) {
this.database = database;
return this;
}
/**
* Get collection query for the given collection name
*/
query(collection) {
return this.database.collection(collection);
}
/**
* Get current active session from database object
*/
getCurrentSession() {
return this.database.getActiveSession()?.session;
}
/**
* Create a new document in the given collection
*/
async create(collection, data, { session = this.getCurrentSession() } = {}) {
const query = this.query(collection);
await this.trigger("creating saving", {
collection,
data,
query,
isMany: false,
});
const result = await query.insertOne(data, {
session,
});
const document = {
...data,
_id: result.insertedId,
};
await this.trigger("created saved", {
collection,
document,
isMany: false,
});
return document;
}
/**
* Create many documents in the given collection
*/
async createMany(collection, data, { session = this.getCurrentSession() } = {}) {
const query = this.query(collection);
await this.trigger("creating saving", {
collection,
data,
query,
isMany: true,
});
const result = await query.insertMany(data, {
session,
});
const documents = data.map((data, index) => ({
...data,
_id: result.insertedIds[index],
}));
await this.trigger("created saved", {
collection,
documents,
isMany: true,
});
return documents;
}
/**
* Update model by the given id
*/
async update(collection, filter, data, options) {
// get the query of the current collection
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("updating saving", {
collection,
filter,
data,
query,
options,
isMany: false,
});
const result = await query.findOneAndUpdate(filter, {
$set: data,
}, {
returnDocument: "after",
...options,
});
const output = result?.ok ? result.value : null;
await this.trigger("updated saved", {
collection,
filter,
data,
document: output,
isMany: false,
});
return output;
}
/**
* Update a single document in the given collection
*/
async updateOne(collection, filter, update, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("updating saving", {
collection,
filter,
update,
options,
query,
isMany: false,
});
const result = await query.updateOne(filter, update, options);
await this.trigger("updated saved", {
collection,
filter,
update,
options,
result,
isMany: false,
});
return result;
}
/**
* Update many documents
*/
async updateMany(collection, filter, updateOptions, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("updating saving", {
collection,
filter,
updateOptions,
options,
query,
isMany: true,
});
const result = await query.updateMany(filter, updateOptions, options);
await this.trigger("updated saved", {
collection,
filter,
updateOptions,
options,
result: result,
isMany: true,
});
return result;
}
/**
* Increment the value of the given field by the given amount
*/
async increment(collection, filter, field, amount = 1) {
const query = this.query(collection);
const result = await query.findOneAndUpdate(filter, {
$inc: { [field]: amount },
});
return result;
}
/**
* Decrement the value of the given field by the given amount
*/
async decrement(collection, filter, field, amount = 1) {
const query = this.query(collection);
const result = await query.updateOne(filter, {
$inc: { [field]: -amount },
});
return result;
}
/**
* Find and increment the value of the given field by the given amount
*/
async findAndIncrement(collection, filter, field, amount = 1) {
const query = this.query(collection);
const result = await query.findOneAndUpdate(filter, {
$inc: { [field]: amount },
});
return result;
}
/**
* Find and decrement the value of the given field by the given amount
*/
async findAndDecrement(collection, filter, field, amount = 1) {
const query = this.query(collection);
const result = await query.findOneAndUpdate(filter, {
$inc: { [field]: -amount },
});
return result;
}
/**
* Replace the entire document for the given document id with the given new data
*/
async replace(collection, filter, data, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("replacing saving", {
collection,
filter,
data,
query,
});
const result = await query.findOneAndReplace(filter, data, {
returnDocument: "after",
...options,
});
const output = result?.ok ? result.value : null;
await this.trigger("replaced saved", {
collection,
filter,
data,
output,
});
return output;
}
/**
* Find and update the document for the given filter with the given data or create a new document/record
* if filter has no matching
*/
async upsert(collection, filter, data, options) {
// get the query of the current collection
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("upserting saving", {
collection,
filter,
data,
query,
options,
});
// execute the update operation
const result = await query.findOneAndUpdate(filter, {
$set: data,
}, {
returnDocument: "after",
upsert: true,
...options,
});
const output = result?.ok ? result.value : null;
await this.trigger("upserted saved", {
collection,
filter,
data,
output,
});
return output;
}
/**
* Perform a single delete operation for the given collection
*/
async deleteOne(collection, filter, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("deleting", {
collection,
filter,
query,
options,
isMany: false,
});
const result = await query.deleteOne(filter, options);
const isDeleted = result.deletedCount > 0;
await this.trigger("deleted", {
collection,
filter,
isDeleted,
count: result.deletedCount,
result,
isMany: false,
});
return isDeleted;
}
/**
* Delete multiple documents from the given collection
*/
async delete(collection, filter = {}, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("deleting", {
collection,
filter,
query,
isMany: true,
});
const result = await query.deleteMany(filter, options);
const output = result.deletedCount;
await this.trigger("deleted", {
collection,
filter,
output,
result,
count: result.deletedCount,
isMany: true,
});
return output;
}
/**
* Alias to delete
*
* @alias delete
*/
async deleteMany(collection, filter = {}, options) {
return this.delete(collection, filter, options);
}
/**
* Check if document exists for the given collection with the given filter
*/
async exists(collection, filter = {}, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("fetching", {
collection,
filter,
query,
options,
isMany: false,
});
// Use findOne with projection to minimize data transfer
const document = await query.findOne(filter, {
...options,
projection: { _id: 1 }, // Only return the _id field
});
await this.trigger("fetched", {
collection,
filter,
document,
exists: !!document,
});
return !!document;
}
/**
* Find a single document for the given collection with the given filter
*/
async first(collection, filter = {}, findOptions) {
const documents = await this.list(collection, filter, {
limit: 1,
...findOptions,
});
return documents[0];
}
/**
* Find last document for the given collection with the given filter
*/
async last(collection, filter = {}, findOptions) {
const documents = await this.latest(collection, filter, {
limit: 1,
...findOptions,
});
return documents[0];
}
/**
* Find multiple document for the given collection with the given filter
*/
async list(collection, filter = {}, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
if (options?.deselect || options?.select) {
(options?.deselect || []).forEach(field => {
});
(options?.select || []).forEach(field => {
});
delete options.deselect;
delete options.select;
}
await this.trigger("fetching", {
collection,
filter,
query,
options,
isMany: true,
});
const findOperation = query.find(filter, options);
const documents = await findOperation.toArray();
await this.trigger(this.eventName + ".fetched", {
collection,
filter,
documents,
options,
isMany: true,
count: documents.length,
});
return documents;
}
/**
* Find latest documents for the given collection with the given filter
*/
async latest(collection, filter = {}, findOptions) {
return this.list(collection, filter, {
sort: {
id: "desc",
},
...findOptions,
});
}
/**
* Find oldest documents for the given collection with the given filter
*/
async oldest(collection, filter = {}, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("fetching", {
collection,
filter,
query,
options,
isMany: true,
});
const documents = await query
.find(filter, options)
.sort({
id: "asc",
})
.toArray();
await this.trigger("fetched", {
collection,
filter,
documents,
options,
count: documents.length,
isMany: true,
});
return documents;
}
/**
* Get distinct values for the given collection with the given filter
*/
async distinct(collection, field, filter = {}, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("fetching", {
collection,
filter,
query,
isMany: true,
});
const data = await query.distinct(field, filter, options);
await this.trigger("fetched", {
collection,
filter,
data,
count: data.length,
isMany: true,
});
return data;
}
/**
* Count documents for the given collection with the given filter
*/
async count(collection, filter = {}, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("counting", {
collection,
filter,
query,
});
const output = await query.countDocuments(filter, options);
await this.trigger("counted", {
collection,
filter,
output,
});
return output;
}
/**
* Create an explain fetch query
*/
async explain(collection, filter = {}, options, verbosity) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("explaining", {
collection,
filter,
query,
});
const result = await query
.find(filter, {
explain: true,
...options,
})
.explain(verbosity);
await this.trigger("explained", {
collection,
filter,
result,
});
return result;
}
/**
* Create aggregate query
*/
async aggregate(collection, pipeline, options) {
const query = this.query(collection);
options = this.prepareQueryOptions(options);
await this.trigger("aggregating", {
collection,
pipeline,
options,
query,
});
const aggregate = await query.aggregate(pipeline, options);
await this.trigger("aggregated", {
collection,
pipeline,
options,
aggregate,
});
return aggregate;
}
/**
* Trigger event
*/
async trigger(eventName, payload = {}) {
return Promise.all(eventName
.split(" ")
.map(async (eventName) => events.triggerAllAsync(this.eventName + "." + eventName, payload)));
}
/**
* Listen on creating event
*/
onCreating(callback) {
this.on("creating", callback);
return this;
}
/**
* Listen on created event
*/
onCreated(callback) {
this.on("created", callback);
return this;
}
/**
* Listen on updating event
*/
onUpdating(callback) {
this.on("updating", callback);
return this;
}
/**
* Listen on updated event
*/
onUpdated(callback) {
this.on("updated", callback);
return this;
}
/**
* Listen on upserting event
*/
onUpserting(callback) {
this.on("upserting", callback);
return this;
}
/**
* Listen on upserted event
*/
onUpserted(callback) {
this.on("upserted", callback);
return this;
}
/**
* Listen on replacing event
*/
onReplacing(callback) {
this.on("replacing", callback);
return this;
}
/**
* Listen on replaced event
*/
onReplaced(callback) {
this.on("replaced", callback);
return this;
}
/**
* Listen on saving event
*/
onSaving(callback) {
this.on("saving", callback);
return this;
}
/**
* Listen on saved event
*/
onSaved(callback) {
this.on("saved", callback);
return this;
}
/**
* Listen on fetching event
*/
onFetching(callback) {
this.on("fetching", callback);
return this;
}
/**
* Listen on fetched event
*/
onFetched(callback) {
this.on("fetched", callback);
return this;
}
/**
* Listen on counting event
*/
onCounting(callback) {
this.on("counting", callback);
return this;
}
/**
* Listen on counted event
*/
onCounted(callback) {
this.on("counted", callback);
return this;
}
/**
* Listen on explaining event
*/
onExplaining(callback) {
this.on("explaining", callback);
return this;
}
/**
* Listen on explained event
*/
onExplained(callback) {
this.on("explained", callback);
return this;
}
/**
* Listen on deleting event
*/
onDeleting(callback) {
this.on("deleting", callback);
return this;
}
/**
* Listen on deleted event
*/
onDeleted(callback) {
this.on("deleted", callback);
return this;
}
/**
* Listen to the given event
*/
on(event, callback) {
events.on(this.eventName + "." + event, callback);
}
/**
* Prepare query options and add session if not exists
*/
prepareQueryOptions(options = {}) {
if (!options?.session) {
options.session = this.getCurrentSession();
}
return options;
}
}
const query = new Query();export{Query,query};//# sourceMappingURL=query.js.map