1+ import { PipelineStage } from "mongoose" ;
2+
3+ type FilterFieldsStage = {
4+ [ k : string ] : {
5+ $filter : {
6+ input : `$${string } `;
7+ as : string ;
8+ cond : { $ne : [ string , boolean ] }
9+ }
10+ } ;
11+ } ;
12+
13+ /**
14+ * Checks if a lookup stage is valid for soft delete filtering
15+ */
16+ const isValidLookupStage = ( lookupStage : PipelineStage . Lookup [ '$lookup' ] ) : boolean => {
17+ return ! ! (
18+ lookupStage . from &&
19+ lookupStage . localField &&
20+ lookupStage . foreignField &&
21+ lookupStage . as &&
22+ ! lookupStage . localField . includes ( '.' ) // exclude nested lookups
23+ ) ;
24+ } ;
25+
26+ /**
27+ * Creates an $addFields stage to filter out soft deleted documents from lookup results
28+ */
29+ const createSoftDeleteFilterStage = ( fieldName : string ) : { $addFields : FilterFieldsStage } => {
30+ return {
31+ $addFields : {
32+ [ fieldName ] : {
33+ $filter : {
34+ input : `$${ fieldName } ` ,
35+ as : 'temp' ,
36+ cond : { $ne : [ '$$temp.isDeleted' , true ] } ,
37+ } ,
38+ } ,
39+ } ,
40+ } ;
41+ } ;
42+
43+ /**
44+ * Processes a $lookup stage and adds soft delete filtering if applicable
45+ */
46+ const processLookupStage = (
47+ stage : PipelineStage ,
48+ pipeline : PipelineStage [ ] ,
49+ index : number
50+ ) : void => {
51+ const lookupStage = stage [ '$lookup' as keyof typeof stage ] as PipelineStage . Lookup [ '$lookup' ] ;
52+
53+ if ( ! lookupStage || ! isValidLookupStage ( lookupStage ) ) {
54+ return ;
55+ }
56+
57+ const { as } = lookupStage ;
58+ const filterStage = createSoftDeleteFilterStage ( as ) ;
59+ pipeline . splice ( index + 1 , 0 , filterStage ) ;
60+ } ;
61+
62+ /**
63+ * Processes a $match stage and adds soft delete filtering if needed
64+ */
65+ const processMatchStage = ( stage : PipelineStage , pipeline : PipelineStage [ ] , index : number ) : void => {
66+ const matchStage = stage [ '$match' as keyof typeof stage ] as PipelineStage . Match [ '$match' ] ;
67+
68+ if ( ! matchStage ) {
69+ return ;
70+ }
71+
72+ // Skip if already filtering for deleted documents
73+ if ( matchStage . isDeleted === true ) {
74+ return ;
75+ }
76+
77+ // Add soft delete filter to existing match conditions
78+ ( pipeline [ index ] as { '$match' : PipelineStage . Match [ '$match' ] } ) [ '$match' ] = {
79+ ...matchStage ,
80+ isDeleted : false
81+ } ;
82+ } ;
83+
84+ /**
85+ * Overwrites aggregation pipeline to handle soft deleted documents
86+ * - Adds filtering for soft deleted documents in $lookup results
87+ * - Ensures $match stages exclude soft deleted documents
88+ */
89+ export const overwriteAggregatePipeline = ( pipeline : PipelineStage [ ] ) : PipelineStage [ ] => {
90+ pipeline . forEach ( ( stage , index ) => {
91+ processLookupStage ( stage , pipeline , index ) ;
92+ processMatchStage ( stage , pipeline , index ) ;
93+ } ) ;
94+
95+ return pipeline ;
96+ } ;
0 commit comments