Aggregation
Use aggregation when you need to penetrate more than one step in the depth of relationships โ going from father to grandson, or vice versa. Don't worry: every $lookup, $unwind, and $project you need is generated for you. You only write the filtering logic.
The neat trick: Lesan's aggregation is always one step behind the client request. Because relations are embedded, deep paths that can't be read from a snapshot get $lookuped on demand. For the conceptual background, see Why NoSQL? โ Lesan pipeline example.
Get a list of documents with aggregationโ
const getCitiesAggregationValidator = () => {
return object({
set: object({
page: number(),
take: number(),
countryId: optional(objectIdValidation),
}),
get: coreApp.schemas.selectStruct("city", 3),
});
};
const getCitiesAggregation: ActFn = async (body) => {
const {
set: { page, take, countryId },
get,
} = body.details;
const pipeline = [];
pipeline.push({ $skip: (page - 1) * take });
pipeline.push({ $limit: take });
countryId &&
pipeline.push({ $match: { "country._id": new ObjectId(countryId) } });
return await cities
.aggregation({
pipeline,
projection: get,
})
.toArray();
};
coreApp.acts.setAct({
schema: "city",
actName: "getCitiesAggregation",
validator: getCitiesAggregationValidator(),
fn: getCitiesAggregation,
});
We add two stages for pagination ($skip, $limit) and one optional $match on the embedded country._id. But those aren't all that reaches the database โ Lesan appends the $lookup, $unwind, and $project stages it needs to satisfy the get shape.
For this request:
{
"body": {
"service": "main",
"model": "city",
"act": "getCitiesAggregation",
"details": {
"get": {
"_id": 1,
"name": 1,
"country": { "_id": 1, "name": 1 },
"users": { "_id": 1, "name": 1 }
},
"set": { "page": 1, "take": 10 }
}
}
}
Only a single $project stage is appended (both country and users are already embedded one level deep):
[
{
"$project": {
"_id": 1,
"name": 1,
"country": { "_id": 1, "name": 1 },
"users": { "_id": 1, "name": 1 }
}
}
]
But for a deeper request โ country.cities, country.citiesByPopulation, country.capital, users.livedCities, users.country โ Lesan appends $lookup/$unwind for each one-step relation, then a $project:
[
{ "$lookup": { "from": "country", "localField": "country._id", "foreignField": "_id", "as": "country" } },
{ "$unwind": { "path": "$country", "preserveNullAndEmptyArrays": true } },
{ "$lookup": { "from": "user", "localField": "users._id", "foreignField": "_id", "as": "users" } },
{ "$project": { "_id": 1, "name": 1, "country": { "_id": 1, "name": 1, "cities": { "_id": 1, "name": 1 }, "citiesByPopulation": { "name": 1, "_id": 1 }, "capital": { "_id": 1, "name": 1 } }, "users": { "_id": 1, "name": 1, "livedCities": { "name": 1, "_id": 1 }, "country": { "_id": 1, "name": 1 } }, "lovedByUser": { "_id": 1, "name": 1 } } }
]
Because we passed 3 to selectStruct("city", 3), the pipeline allows penetrating two extra steps; try deeper get shapes in the playground to see the $lookups grow. The full example is examples/document/07-1-aggregation.ts.
Executing main โ city โ getCitiesAggregation:
Get a single document with aggregationโ
If the client needs a single document's relations more than one step deep, aggregation is required even for one document:
const getUserAggregationValidator = () => {
return object({
set: object({
userId: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("user", 2),
});
};
const getUserAggregation: ActFn = async (body) => {
const {
set: { userId },
get,
} = body.details;
const pipeline = [];
pipeline.push({ $match: { _id: new ObjectId(userId) } });
return await users
.aggregation({
pipeline,
projection: get,
})
.toArray();
};
coreApp.acts.setAct({
schema: "user",
actName: "getUserAggregation",
validator: getUserAggregationValidator(),
fn: getUserAggregation,
});
Note the result is still an array โ but with a single member. The full example is examples/document/07-2-aggregation.ts.
Executing main โ user โ getUserAggregation:
Next stepsโ
- findOneAndUpdate โ updating documents, and why that's the hardest problem in Lesan
- Aggregation pipelines โ grouping, counting, and more advanced pipeline patterns