An open API service indexing awesome lists of open source software.

https://github.com/zuruoke/mongodb-typesafe-aggregation


https://github.com/zuruoke/mongodb-typesafe-aggregation

Last synced: 10 months ago
JSON representation

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)

## ๐Ÿ“– Introduction

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();
```

## ๐Ÿ“ฆ Lookup Example

### ๐Ÿ‘ฉโ€๐Ÿ’ป 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!

## โœ๏ธ Best Practices

- **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
```

## ๐Ÿ“š Further Reading

- [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)