https://github.com/zuruoke/mongodb-typesafe-aggregation
https://github.com/zuruoke/mongodb-typesafe-aggregation
Last synced: 10 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/zuruoke/mongodb-typesafe-aggregation
- Owner: zuruoke
- Created: 2025-06-17T17:10:58.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-06-25T23:09:28.000Z (about 1 year ago)
- Last Synced: 2025-06-26T00:21:14.659Z (about 1 year ago)
- Language: TypeScript
- Size: 24.4 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ๐ Aggregation Module Documentation
## Table of Contents
1. [Introduction](#introduction)
2. [The Problem: Why Change Was Needed](#problem)
3. [The Solution: Fluent, Type-Safe Aggregations](#solution)
4. [Migration Guide: Step-by-Step](#migration)
5. [Mapping Old to New Patterns](#mapping)
6. [Before vs After: Real Examples](#examples)
7. [Best Practices](#best-practices)
8. [Directory Structure Overview](#structure)
9. [Further Reading](#further-reading)
This module transforms how we build MongoDB aggregation pipelines. It provides a **type-safe**, **fluent**, and **maintainable** way to structure complex queries without worrying about fragile JSON blobs.
## โ The Problem: Why Change Was Needed
Old aggregation pipelines looked like this:
```ts
[
{ $match: { officeId: query.officeId } },
{
$addFields: {
hasAccess: {
$cond: { if: { $eq: ['$role', 'admin'] }, then: true, else: false },
},
},
},
];
```
### Problems:
- Hard to read ๐ต
- No type hints ๐งฉ
- Runtime-only errors ๐จ
- Difficult to test ๐งช
## โ
The Solution: Fluent, Type-Safe Aggregations
Using `PipelineBuilder` and `filter()` operators:
```ts
new PipelineBuilder()
.match({ officeId: query.officeId })
.addFields({
isAdmin: filter()
.cond(filter().fieldEq('$role', 'admin').build(), true, false)
.build(),
})
.build();
```
### ๐ฅ Advantages
- **Type-safe** pipelines at compile time.
- **Autocomplete** for fields and operators.
- **Readable and maintainable** structure.
- **Unit-testable** simple arrays.
## ๐ Migration Guide: Step-by-Step
### ๐น Step 1: Find Legacy Pipelines
Search for usage of `aggregate([...])` or raw `PipelineStage[]` arrays.
### ๐น Step 2: Initialize a PipelineBuilder
```ts
const pipeline = new PipelineBuilder();
```
### ๐น Step 3: Convert Each Stage Carefully
| MongoDB Stage | New Fluent Style |
| :------------ | :---------------------------------- |
| `$match` | `.match({...})` |
| `$addFields` | `.addFields({...})` |
| `$project` | `.project({...})` |
| `$lookup` | `.lookup({...})` |
| `$unwind` | `.unwind('field') or unwind({...})` |
### ๐น Step 4: Handle Conditional Logic
**Old Way:**
```ts
$cond: { if: { $eq: ['$role', 'admin'] }, then: true, else: false }
```
**New Way:**
```ts
filter()
.cond(filter().fieldEq('$role', 'admin').build(), true, false)
.build();
```
### ๐น Step 5: Validate Output
- Snapshot the pipeline.
- Compare outputs with old version.
- Use unit tests.
## ๐ฅ Mapping Old to New Patterns
| ๐๏ธ Old Pattern | ๐ New Fluent Pattern |
| :------------------------------------------- | :------------------------------------------------ |
| `$match: { status: 'active' }` | `.match({ status: 'active' })` |
| `$addFields: { total: { $sum: '$amount' } }` | `.addFields({ total: sum('$amount') })` |
| `$lookup` | `.lookup({ from, localField, foreignField, as })` |
| `$unwind: 'items'` | `.unwind('items')` |
| `$cond` expressions | `.addFields({ field: filter().cond(...) })` |
## ๐ฅ Before vs After: Real Examples
### ๐ Before (Old Style)
```ts
[
{ $match: { status: 'PENDING' } },
{
$addFields: {
isLate: {
$cond: {
if: { $gt: ['$dueDate', new Date()] },
then: true,
else: false,
},
},
},
},
];
```
### ๐ After (New Style)
```ts
new PipelineBuilder()
.match({ status: 'PENDING' })
.addFields({
isLate: filter()
.cond(filter().fieldGt('$dueDate', new Date()).build(), true, false)
.build(),
})
.build();
```
### ๐ฉโ๐ป Old Way:
```ts
[
{
$lookup: {
from: COLLECTION_NAMES.TRANSLATIONS,
localField: '_id',
foreignField: 'messageId',
as: 'translation',
},
},
];
```
### ๐ New Fluent Style:
```ts
new PipelineBuilder()
.lookup({
from: COLLECTION_NAMES.TRANSLATIONS,
localField: '_id',
foreignField: 'messageId',
as: 'translation',
})
.build();
```
**Tip:** You can also include `pipeline` inside `lookup` if you need deeper filtering!
- **Prefer short, chainable stages.**
- **Use `filter()` instead of raw operators.**
- **Write helper functions** for repeated conditions.
- **Snapshot complex pipelines** during migration.
- **Comment non-trivial logic.**
## ๐ Directory Structure Overview
```bash
packages/src/aggregation/
โโโ builder.ts
โโโ constants.ts
โโโ stages/
โโโ types/
โโโ index.ts
```
- [MongoDB Aggregation Framework โ Official Docs](https://www.mongodb.com/docs/manual/aggregation/)
- [Aggregation Optimization in MongoDB (Case Study)](https://medium.com/mongodb/aggregation-optimization-in-mongodb-a-case-study-from-the-field-part-1-15aec13fe1bc)
- [Fluent Aggregation Builder (npm)](https://www.npmjs.com/package/mongodb-aggregation-builder)