mongo-bolt
Version:
A lightweight, beginner-friendly MongoDB wrapper with inbuilt caching and simplified joins/indexing.
240 lines (239 loc) • 6.7 kB
JavaScript
import { operatorMap } from "./operatorMap.js";
class AggregationBuilder {
constructor() {
this.pipeline = [];
// Keep track of the last $group stage's object for chaining accumulator methods.
this.lastGroupStage = null;
}
ensureGroupStage() {
if (!this.lastGroupStage) {
throw new Error("Must call group() before using group operators.");
}
}
match(field, condition) {
const mongoCondition = {};
for (const [key, value] of Object.entries(condition)) {
const mongoOperator = operatorMap[key];
if (!mongoOperator) {
throw new Error(`Unsupported operator: ${key}`);
}
mongoCondition[mongoOperator] = value;
}
this.pipeline.push({
$match: {
[field]: mongoCondition,
},
});
return this;
}
group(groupBy) {
let groupStage = { $group: {} };
if (typeof groupBy === "string") {
groupStage.$group._id = `$${groupBy}`;
}
else if (typeof groupBy === "object") {
// Use the provided object as the composite key.
groupStage.$group._id = groupBy;
}
else {
throw new Error("group() requires a string or an object");
}
this.pipeline.push(groupStage);
// Save a reference to the internal group object for accumulator methods.
this.lastGroupStage = groupStage.$group;
return this;
}
sum(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $sum: `$${value}` };
return this;
}
avg(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $avg: `$${value}` };
return this;
}
min(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $min: `$${value}` };
return this;
}
max(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $max: `$${value}` };
return this;
}
first(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $first: `$${value}` };
return this;
}
last(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $last: `$${value}` };
return this;
}
push(field, key = field) {
this.ensureGroupStage();
this.lastGroupStage[key] = {
$push: field === "ENTIRE" ? "$$ROOT" : `$${field}`,
};
return this;
}
addToSet(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $addToSet: `$${value}` };
return this;
}
stdDevPop(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $stdDevPop: `$${value}` };
return this;
}
stdDevSamp(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $stdDevSamp: `$${value}` };
return this;
}
mergeObjects(value, key) {
this.ensureGroupStage();
this.lastGroupStage[key] = { $mergeObjects: `$${value}` };
return this;
}
count(key = "count") {
this.ensureGroupStage();
this.lastGroupStage[key] = { $sum: 1 };
return this;
}
project(projection) {
this.pipeline.push({
$project: projection,
});
return this;
}
sort(sortFields) {
const sortStage = {};
for (const [field, direction] of Object.entries(sortFields)) {
if (direction === "asc") {
sortStage[field] = 1;
}
else if (direction === "desc") {
sortStage[field] = -1;
}
else {
throw new Error(`Invalid sort direction for "${field}": use "asc" or "desc"`);
}
}
this.pipeline.push({ $sort: sortStage });
return this; // for chaining
}
limit(n) {
this.pipeline.push({
$limit: n,
});
return this;
}
skip(n) {
this.pipeline.push({
$skip: n,
});
return this;
}
lookup(from, localField, foreignField, as) {
this.pipeline.push({
$lookup: {
from,
localField: `$${localField}`,
foreignField: `$${foreignField}`,
as,
},
});
return this;
}
unset(...fields) {
this.pipeline.push({ $unset: fields });
return this;
}
addFields(fields) {
this.pipeline.push({ $addFields: fields });
return this;
}
replaceRoot(newRoot) {
this.pipeline.push({ $replaceRoot: { newRoot } });
return this;
}
replaceWith(replacement) {
this.pipeline.push({ $replaceWith: replacement });
return this;
}
graphLookup(options) {
this.pipeline.push({ $graphLookup: options });
return this;
}
unwind(field) {
this.pipeline.push({
$unwind: typeof field === "string" ? `$${field}` : field,
});
return this;
}
bucket(options) {
this.pipeline.push({ $bucket: options });
return this;
}
bucketAuto(options) {
this.pipeline.push({ $bucketAuto: options });
return this;
}
facet(facets) {
this.pipeline.push({ $facet: facets });
return this;
}
merge(options) {
this.pipeline.push({
$merge: typeof options === "string" ? { into: options } : options,
});
return this;
}
out(collectionName) {
this.pipeline.push({
$out: collectionName,
});
return this;
}
search(searchStage) {
this.pipeline.push({ $search: searchStage });
return this;
}
searchMeta(searchStage) {
this.pipeline.push({ $searchMeta: searchStage });
return this;
}
redact(expression) {
this.pipeline.push({ $redact: expression });
return this;
}
indexStats() {
this.pipeline.push({ $indexStats: {} });
return this;
}
currentOp() {
this.pipeline.push({ $currentOp: {} });
return this;
}
collStats() {
this.pipeline.push({ $collStats: {} });
return this;
}
planCacheStats() {
this.pipeline.push({ $planCacheStats: {} });
return this;
}
listSessions() {
this.pipeline.push({ $listSessions: {} });
return this;
}
build() {
return this.pipeline;
}
}
export default AggregationBuilder;