@@ -11,6 +11,59 @@ import {
1111import { DataSourceDataType } from "./dataSourceConfig" ;
1212import { ActionDataType } from "./queryConfig" ;
1313
14+ function applyFieldFilter (
15+ query : FirebaseFirestore . Query < FirebaseFirestore . DocumentData > ,
16+ fieldPath : string ,
17+ operator : string ,
18+ value : any
19+ ) : FirebaseFirestore . Query < FirebaseFirestore . DocumentData > {
20+ let firestoreOp : FirebaseFirestore . WhereFilterOp ;
21+ switch ( operator ) {
22+ case "EQUAL" : firestoreOp = "==" ; break ;
23+ case "GREATER_THAN" : firestoreOp = ">" ; break ;
24+ case "LESS_THAN" : firestoreOp = "<" ; break ;
25+ case "GREATER_THAN_OR_EQUAL" : firestoreOp = ">=" ; break ;
26+ case "LESS_THAN_OR_EQUAL" : firestoreOp = "<=" ; break ;
27+ case "ARRAY_CONTAINS" : firestoreOp = "array-contains" ; break ;
28+ case "ARRAY_CONTAINS_ANY" : firestoreOp = "array-contains-any" ; break ;
29+ default :
30+ throw badRequest ( `Unsupported operator: ${ operator } ` ) ;
31+ }
32+
33+ const actualValue = value . integerValue ?? value . stringValue ?? value . booleanValue ?? value . doubleValue ;
34+ if ( actualValue === undefined ) {
35+ throw badRequest ( "Unsupported value type in structuredQuery" ) ;
36+ }
37+
38+ return query . where ( fieldPath , firestoreOp , actualValue ) ;
39+ }
40+
41+ // Helper function to apply a unary filter
42+ function applyUnaryFilter (
43+ query : FirebaseFirestore . Query < FirebaseFirestore . DocumentData > ,
44+ fieldPath : string ,
45+ operator : string
46+ ) : FirebaseFirestore . Query < FirebaseFirestore . DocumentData > {
47+ let firestoreOp : FirebaseFirestore . WhereFilterOp ;
48+ switch ( operator ) {
49+ case "IS_NAN" : firestoreOp = "==" ; break ;
50+ case "IS_NULL" : firestoreOp = "==" ; break ;
51+ case "IS_NOT_NAN" : firestoreOp = "!=" ; break ;
52+ case "IS_NOT_NULL" : firestoreOp = "!=" ; break ;
53+ default :
54+ throw badRequest ( `Unsupported unary operator: ${ operator } ` ) ;
55+ }
56+
57+ return query . where ( fieldPath , firestoreOp , null ) ;
58+ }
59+
60+ // Helper function to extract cursor values
61+ function extractCursorValues ( values : any [ ] ) : any [ ] {
62+ return values . map ( ( v : { integerValue ?: any ; stringValue ?: any ; booleanValue ?: any ; doubleValue ?: any ; } ) =>
63+ v . integerValue ?? v . stringValue ?? v . booleanValue ?? v . doubleValue ?? v . booleanValue
64+ ) . filter ( value => value !== undefined ) ;
65+ }
66+
1467export async function runFirebasePlugin (
1568 actionData : ActionDataType ,
1669 dataSourceConfig : DataSourceDataType
@@ -139,67 +192,69 @@ export async function runFirebasePlugin(
139192
140193 let query : FirebaseFirestore . Query < FirebaseFirestore . DocumentData > = ref ;
141194
142- // Apply `where` filters
143- if ( structuredQuery . where && structuredQuery . where . fieldFilter ) {
144- const fieldFilter = structuredQuery . where . fieldFilter ;
145- const fieldPath = fieldFilter . field ?. fieldPath ;
146- const operator = fieldFilter . op ;
147- const value = fieldFilter . value ;
195+ // Apply `select` fields projection if provided
196+ if ( structuredQuery . select && structuredQuery . select . fields ) {
197+ const selectedFields = structuredQuery . select . fields . map ( ( field : { fieldPath : string } ) => field . fieldPath ) ;
198+ query = query . select ( ...selectedFields ) ;
199+ }
148200
149- if ( ! fieldPath || ! operator || value === undefined ) {
150- throw badRequest ( "Invalid fieldFilter in where clause" ) ;
151- }
201+ // Apply `where` filters
202+ if ( structuredQuery . where ) {
203+ if ( structuredQuery . where . compositeFilter ) {
204+ // Composite Filter (AND, OR)
205+ const compositeFilter = structuredQuery . where . compositeFilter ;
206+ const filters = compositeFilter . filters ;
207+ const operator = compositeFilter . op ;
152208
153- let firestoreOp : FirebaseFirestore . WhereFilterOp ;
154- switch ( operator ) {
155- case "EQUAL" :
156- firestoreOp = "==" ;
157- break ;
158- case "GREATER_THAN" :
159- firestoreOp = ">" ;
160- break ;
161- case "LESS_THAN" :
162- firestoreOp = "<" ;
163- break ;
164- case "GREATER_THAN_OR_EQUAL" :
165- firestoreOp = ">=" ;
166- break ;
167- case "LESS_THAN_OR_EQUAL" :
168- firestoreOp = "<=" ;
169- break ;
170- case "ARRAY_CONTAINS" :
171- firestoreOp = "array-contains" ;
172- break ;
173- default :
174- throw badRequest ( `Unsupported operator: ${ operator } ` ) ;
175- }
209+ if ( operator !== "AND" ) {
210+ throw badRequest ( "Only 'AND' composite filters are currently supported." ) ;
211+ }
176212
177- const actualValue = value . integerValue ?? value . stringValue ?? value . booleanValue ?? value . doubleValue ;
178- if ( actualValue === undefined ) {
179- throw badRequest ( "Unsupported value type in structuredQuery" ) ;
213+ filters . forEach ( ( filter : any ) => {
214+ if ( filter . fieldFilter ) {
215+ const fieldFilter = filter . fieldFilter ;
216+ const fieldPath = fieldFilter . field . fieldPath ;
217+ const operator = fieldFilter . op ;
218+ const value = fieldFilter . value ;
219+ query = applyFieldFilter ( query , fieldPath , operator , value ) ;
220+ } else if ( filter . unaryFilter ) {
221+ const unaryFilter = filter . unaryFilter ;
222+ const fieldPath = unaryFilter . field . fieldPath ;
223+ const operator = unaryFilter . op ;
224+ query = applyUnaryFilter ( query , fieldPath , operator ) ;
225+ }
226+ } ) ;
227+ } else if ( structuredQuery . where . fieldFilter ) {
228+ // Single Field Filter
229+ const fieldFilter = structuredQuery . where . fieldFilter ;
230+ const fieldPath = fieldFilter . field . fieldPath ;
231+ const operator = fieldFilter . op ;
232+ const value = fieldFilter . value ;
233+ query = applyFieldFilter ( query , fieldPath , operator , value ) ;
180234 }
181-
182- query = query . where ( fieldPath , firestoreOp , actualValue ) ;
183235 }
236+
237+ // Get the total count using aggregate query before applying pagination
238+ const totalCount = await query . count ( ) . get ( ) . then ( ( snapshot ) => snapshot . data ( ) . count ) ;
184239
185240 // Apply `orderBy`
186241 if ( structuredQuery . orderBy && Array . isArray ( structuredQuery . orderBy ) ) {
187- for ( const order of structuredQuery . orderBy ) {
242+ structuredQuery . orderBy . forEach ( ( order : { field : { fieldPath : string | FirebaseFirestore . FieldPath ; } ; direction : any ; } ) => {
188243 if ( order . field && order . field . fieldPath ) {
189244 query = query . orderBy (
190245 order . field . fieldPath ,
191- ( order . direction || "asc" ) as FirebaseFirestore . OrderByDirection
246+ ( order . direction || "asc" ) . toLowerCase ( ) as FirebaseFirestore . OrderByDirection
192247 ) ;
193248 }
194- }
249+ } ) ;
195250 }
196251
197252 // Apply `limit`
198253 if ( structuredQuery . limit ) {
199254 query = query . limit ( structuredQuery . limit ) ;
200255 }
201256
202- // Apply `offset` (Firestore SDK doesn't support offset directly; simulate it with startAfter )
257+ // Apply `offset` (simulate it using startAfter since Firestore SDK doesn't support offset directly)
203258 if ( structuredQuery . offset ) {
204259 const offsetSnapshot = await query . limit ( structuredQuery . offset ) . get ( ) ;
205260 const lastVisible = offsetSnapshot . docs [ offsetSnapshot . docs . length - 1 ] ;
@@ -208,28 +263,27 @@ export async function runFirebasePlugin(
208263 }
209264 }
210265
211- // Apply `startAt` and `endAt` cursors, checking for undefined values
266+ // Apply `startAt` and `endAt` cursors
212267 if ( structuredQuery . startAt && structuredQuery . startAt . values ) {
213- const startAtValues = structuredQuery . startAt . values . map ( ( v : { integerValue : any ; stringValue : any ; booleanValue : any ; doubleValue : any ; } ) => v . integerValue ?? v . stringValue ?? v . booleanValue ?? v . doubleValue ) . filter ( ( value : any ) => value !== undefined ) ;
268+ const startAtValues = extractCursorValues ( structuredQuery . startAt . values ) ;
214269 if ( startAtValues . length > 0 ) {
215270 query = query . startAt ( ...startAtValues ) ;
216271 }
217272 }
218-
219273 if ( structuredQuery . endAt && structuredQuery . endAt . values ) {
220- const endAtValues = structuredQuery . endAt . values . map ( ( v : { integerValue : any ; stringValue : any ; booleanValue : any ; doubleValue : any ; } ) => v . integerValue ?? v . stringValue ?? v . booleanValue ?? v . doubleValue ) . filter ( ( value : any ) => value !== undefined ) ;
274+ const endAtValues = extractCursorValues ( structuredQuery . endAt . values ) ;
221275 if ( endAtValues . length > 0 ) {
222276 query = query . endAt ( ...endAtValues ) ;
223277 }
224278 }
225279
226280 // Execute the query
227281 const snapshot = await query . get ( ) ;
228-
229282 if ( snapshot . empty ) {
230283 return [ ] ;
231284 }
232- return snapshot . docs . map ( ( doc ) => doc . data ( ) ) ;
285+ const documents = snapshot . empty ? [ ] : snapshot . docs . map ( ( doc ) => doc . data ( ) ) ;
286+ return { totalCount, documents } ;
233287 } ) ;
234288 return data ;
235289 }
0 commit comments