{"id":30763617,"url":"https://github.com/zuruoke/mongodb-typesafe-aggregation","last_synced_at":"2025-09-04T16:14:32.612Z","repository":{"id":299671360,"uuid":"1003791331","full_name":"zuruoke/mongodb-typesafe-aggregation","owner":"zuruoke","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-25T23:09:28.000Z","size":25,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-26T00:21:14.659Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zuruoke.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-17T17:10:58.000Z","updated_at":"2025-06-17T17:22:48.000Z","dependencies_parsed_at":"2025-06-17T18:32:35.463Z","dependency_job_id":"54bf80e1-e0db-484e-8842-7c93e734ae53","html_url":"https://github.com/zuruoke/mongodb-typesafe-aggregation","commit_stats":null,"previous_names":["zuruoke/mongodb-typesafe-aggregation"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zuruoke/mongodb-typesafe-aggregation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuruoke%2Fmongodb-typesafe-aggregation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuruoke%2Fmongodb-typesafe-aggregation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuruoke%2Fmongodb-typesafe-aggregation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuruoke%2Fmongodb-typesafe-aggregation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zuruoke","download_url":"https://codeload.github.com/zuruoke/mongodb-typesafe-aggregation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuruoke%2Fmongodb-typesafe-aggregation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273635027,"owners_count":25141095,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-04T16:14:31.423Z","updated_at":"2025-09-04T16:14:32.596Z","avatar_url":"https://github.com/zuruoke.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🛠 Aggregation Module Documentation\n\n## Table of Contents\n\n1. [Introduction](#introduction)\n2. [The Problem: Why Change Was Needed](#problem)\n3. [The Solution: Fluent, Type-Safe Aggregations](#solution)\n4. [Migration Guide: Step-by-Step](#migration)\n5. [Mapping Old to New Patterns](#mapping)\n6. [Before vs After: Real Examples](#examples)\n7. [Best Practices](#best-practices)\n8. [Directory Structure Overview](#structure)\n9. [Further Reading](#further-reading)\n\n## 📖 Introduction \u003ca name=\"introduction\"\u003e\u003c/a\u003e\n\nThis 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.\n\n## ❌ The Problem: Why Change Was Needed \u003ca name=\"problem\"\u003e\u003c/a\u003e\n\nOld aggregation pipelines looked like this:\n\n```ts\n[\n  { $match: { officeId: query.officeId } },\n  {\n    $addFields: {\n      hasAccess: {\n        $cond: { if: { $eq: ['$role', 'admin'] }, then: true, else: false },\n      },\n    },\n  },\n];\n```\n\n### Problems:\n\n- Hard to read 😵\n- No type hints 🧩\n- Runtime-only errors 🚨\n- Difficult to test 🧪\n\n## ✅ The Solution: Fluent, Type-Safe Aggregations \u003ca name=\"solution\"\u003e\u003c/a\u003e\n\nUsing `PipelineBuilder` and `filter()` operators:\n\n```ts\nnew PipelineBuilder()\n  .match({ officeId: query.officeId })\n  .addFields({\n    isAdmin: filter\u003cboolean\u003e()\n      .cond(filter\u003cstring\u003e().fieldEq('$role', 'admin').build(), true, false)\n      .build(),\n  })\n  .build();\n```\n\n### 🔥 Advantages\n\n- **Type-safe** pipelines at compile time.\n- **Autocomplete** for fields and operators.\n- **Readable and maintainable** structure.\n- **Unit-testable** simple arrays.\n\n## 🛠 Migration Guide: Step-by-Step \u003ca name=\"migration\"\u003e\u003c/a\u003e\n\n### 🔹 Step 1: Find Legacy Pipelines\n\nSearch for usage of `aggregate([...])` or raw `PipelineStage[]` arrays.\n\n### 🔹 Step 2: Initialize a PipelineBuilder\n\n```ts\nconst pipeline = new PipelineBuilder();\n```\n\n### 🔹 Step 3: Convert Each Stage Carefully\n\n| MongoDB Stage | New Fluent Style                    |\n| :------------ | :---------------------------------- |\n| `$match`      | `.match({...})`                     |\n| `$addFields`  | `.addFields({...})`                 |\n| `$project`    | `.project({...})`                   |\n| `$lookup`     | `.lookup({...})`                    |\n| `$unwind`     | `.unwind('field') or unwind({...})` |\n\n### 🔹 Step 4: Handle Conditional Logic\n\n**Old Way:**\n\n```ts\n$cond: { if: { $eq: ['$role', 'admin'] }, then: true, else: false }\n```\n\n**New Way:**\n\n```ts\nfilter\u003cboolean\u003e()\n  .cond(filter\u003cstring\u003e().fieldEq('$role', 'admin').build(), true, false)\n  .build();\n```\n\n### 🔹 Step 5: Validate Output\n\n- Snapshot the pipeline.\n- Compare outputs with old version.\n- Use unit tests.\n\n## 🔥 Mapping Old to New Patterns \u003ca name=\"mapping\"\u003e\u003c/a\u003e\n\n| 🏛️ Old Pattern                               | 🚀 New Fluent Pattern                             |\n| :------------------------------------------- | :------------------------------------------------ |\n| `$match: { status: 'active' }`               | `.match({ status: 'active' })`                    |\n| `$addFields: { total: { $sum: '$amount' } }` | `.addFields({ total: sum('$amount') })`           |\n| `$lookup`                                    | `.lookup({ from, localField, foreignField, as })` |\n| `$unwind: 'items'`                           | `.unwind('items')`                                |\n| `$cond` expressions                          | `.addFields({ field: filter().cond(...) })`       |\n\n## 🔥 Before vs After: Real Examples \u003ca name=\"examples\"\u003e\u003c/a\u003e\n\n### 🛑 Before (Old Style)\n\n```ts\n[\n  { $match: { status: 'PENDING' } },\n  {\n    $addFields: {\n      isLate: {\n        $cond: {\n          if: { $gt: ['$dueDate', new Date()] },\n          then: true,\n          else: false,\n        },\n      },\n    },\n  },\n];\n```\n\n### 🚀 After (New Style)\n\n```ts\nnew PipelineBuilder()\n  .match({ status: 'PENDING' })\n  .addFields({\n    isLate: filter\u003cboolean\u003e()\n      .cond(filter\u003cDate\u003e().fieldGt('$dueDate', new Date()).build(), true, false)\n      .build(),\n  })\n  .build();\n```\n\n## 📦 Lookup Example \u003ca name=\"lookup-example\"\u003e\u003c/a\u003e\n\n### 👩‍💻 Old Way:\n\n```ts\n[\n  {\n    $lookup: {\n      from: COLLECTION_NAMES.TRANSLATIONS,\n      localField: '_id',\n      foreignField: 'messageId',\n      as: 'translation',\n    },\n  },\n];\n```\n\n### 🚀 New Fluent Style:\n\n```ts\nnew PipelineBuilder()\n  .lookup({\n    from: COLLECTION_NAMES.TRANSLATIONS,\n    localField: '_id',\n    foreignField: 'messageId',\n    as: 'translation',\n  })\n  .build();\n```\n\n**Tip:** You can also include `pipeline` inside `lookup` if you need deeper filtering!\n\n## ✍️ Best Practices \u003ca name=\"best-practices\"\u003e\u003c/a\u003e\n\n- **Prefer short, chainable stages.**\n- **Use `filter()` instead of raw operators.**\n- **Write helper functions** for repeated conditions.\n- **Snapshot complex pipelines** during migration.\n- **Comment non-trivial logic.**\n\n## 📂 Directory Structure Overview \u003ca name=\"structure\"\u003e\u003c/a\u003e\n\n```bash\npackages/src/aggregation/\n├── builder.ts\n├── constants.ts\n├── stages/\n├── types/\n├── index.ts\n```\n\n## 📚 Further Reading \u003ca name=\"further-reading\"\u003e\u003c/a\u003e\n\n- [MongoDB Aggregation Framework – Official Docs](https://www.mongodb.com/docs/manual/aggregation/)\n- [Aggregation Optimization in MongoDB (Case Study)](https://medium.com/mongodb/aggregation-optimization-in-mongodb-a-case-study-from-the-field-part-1-15aec13fe1bc)\n- [Fluent Aggregation Builder (npm)](https://www.npmjs.com/package/mongodb-aggregation-builder)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzuruoke%2Fmongodb-typesafe-aggregation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzuruoke%2Fmongodb-typesafe-aggregation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzuruoke%2Fmongodb-typesafe-aggregation/lists"}