@@ -11,6 +11,59 @@ import {
11
11
import { DataSourceDataType } from "./dataSourceConfig" ;
12
12
import { ActionDataType } from "./queryConfig" ;
13
13
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
+
14
67
export async function runFirebasePlugin (
15
68
actionData : ActionDataType ,
16
69
dataSourceConfig : DataSourceDataType
@@ -139,67 +192,69 @@ export async function runFirebasePlugin(
139
192
140
193
let query : FirebaseFirestore . Query < FirebaseFirestore . DocumentData > = ref ;
141
194
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
+ }
148
200
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 ;
152
208
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
+ }
176
212
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 ) ;
180
234
}
181
-
182
- query = query . where ( fieldPath , firestoreOp , actualValue ) ;
183
235
}
236
+
237
+ // Get the total count using aggregate query before applying pagination
238
+ const totalCount = await query . count ( ) . get ( ) . then ( ( snapshot ) => snapshot . data ( ) . count ) ;
184
239
185
240
// Apply `orderBy`
186
241
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 ; } ) => {
188
243
if ( order . field && order . field . fieldPath ) {
189
244
query = query . orderBy (
190
245
order . field . fieldPath ,
191
- ( order . direction || "asc" ) as FirebaseFirestore . OrderByDirection
246
+ ( order . direction || "asc" ) . toLowerCase ( ) as FirebaseFirestore . OrderByDirection
192
247
) ;
193
248
}
194
- }
249
+ } ) ;
195
250
}
196
251
197
252
// Apply `limit`
198
253
if ( structuredQuery . limit ) {
199
254
query = query . limit ( structuredQuery . limit ) ;
200
255
}
201
256
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)
203
258
if ( structuredQuery . offset ) {
204
259
const offsetSnapshot = await query . limit ( structuredQuery . offset ) . get ( ) ;
205
260
const lastVisible = offsetSnapshot . docs [ offsetSnapshot . docs . length - 1 ] ;
@@ -208,28 +263,27 @@ export async function runFirebasePlugin(
208
263
}
209
264
}
210
265
211
- // Apply `startAt` and `endAt` cursors, checking for undefined values
266
+ // Apply `startAt` and `endAt` cursors
212
267
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 ) ;
214
269
if ( startAtValues . length > 0 ) {
215
270
query = query . startAt ( ...startAtValues ) ;
216
271
}
217
272
}
218
-
219
273
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 ) ;
221
275
if ( endAtValues . length > 0 ) {
222
276
query = query . endAt ( ...endAtValues ) ;
223
277
}
224
278
}
225
279
226
280
// Execute the query
227
281
const snapshot = await query . get ( ) ;
228
-
229
282
if ( snapshot . empty ) {
230
283
return [ ] ;
231
284
}
232
- return snapshot . docs . map ( ( doc ) => doc . data ( ) ) ;
285
+ const documents = snapshot . empty ? [ ] : snapshot . docs . map ( ( doc ) => doc . data ( ) ) ;
286
+ return { totalCount, documents } ;
233
287
} ) ;
234
288
return data ;
235
289
}
0 commit comments