@@ -30,7 +30,6 @@ import type {
3030import type {
3131 DocumentReferenceDeleteInternal ,
3232 DocumentReferenceGetInternal ,
33- QueryConstraintWithApplyInternal ,
3433 QueryFilterConstraintWithFilterInternal ,
3534 QueryInternal ,
3635 QueryWithMethodInternal ,
@@ -39,11 +38,63 @@ import type {
3938} from '../types/internal' ;
4039import type { FieldPath } from './FieldPath' ;
4140
42- export abstract class QueryConstraint {
41+ /**
42+ * An `AppliableConstraint` is an abstraction of a constraint that can be applied
43+ * to a Firestore query.
44+ */
45+ export abstract class AppliableConstraint {
46+ /**
47+ * Takes the provided {@link Query} and returns a copy of the {@link Query} with this
48+ * {@link AppliableConstraint} applied.
49+ */
50+ abstract _apply < AppModelType = DocumentData , DbModelType extends DocumentData = DocumentData > (
51+ query : Query < AppModelType , DbModelType > ,
52+ ) : Query < AppModelType , DbModelType > ;
53+ }
54+
55+ /**
56+ * A `QueryConstraint` is used to narrow the set of documents returned by a
57+ * Firestore query. `QueryConstraint`s are created by invoking {@link where},
58+ * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link endBefore},
59+ * {@link endAt}, {@link limit}, {@link limitToLast} and can then be passed to
60+ * {@link query} to create a new query instance that also contains this `QueryConstraint`.
61+ *
62+ * @remarks
63+ * Matches Firebase JS SDK API. We use QueryConstraintBase for shared implementation
64+ * because Query objects are native module wrappers that expose methods matching
65+ * constraint types (orderBy, limit, etc.), allowing dynamic dispatch via `query[type]()`.
66+ */
67+ export abstract class QueryConstraint extends AppliableConstraint {
68+ /** The type of this query constraint */
69+ abstract readonly type : QueryConstraintType ;
70+
71+ /**
72+ * Takes the provided {@link Query} and returns a copy of the {@link Query} with this
73+ * {@link QueryConstraint} applied.
74+ */
75+ _apply < AppModelType = DocumentData , DbModelType extends DocumentData = DocumentData > (
76+ _queryRef : Query < AppModelType , DbModelType > ,
77+ ) : Query < AppModelType , DbModelType > {
78+ // This method is implemented in subclasses (QueryConstraintBase provides the default implementation)
79+ // Making it non-abstract ensures it appears in .d.ts so subclasses inherit it
80+ throw new Error ( '_apply must be implemented by subclass' ) ;
81+ }
82+ }
83+
84+ /**
85+ * Base implementation for orderBy/limit/startAt/endAt/where constraints.
86+ *
87+ * @remarks
88+ * Differs from JS SDK (where each constraint implements its own _apply) because
89+ * our Query objects are native wrappers. All constraints use the same pattern:
90+ * `query[this.type](...args)`, so we share implementation via this base class.
91+ */
92+ export abstract class QueryConstraintBase extends QueryConstraint {
4393 abstract readonly type : QueryConstraintType ;
4494 private readonly _args : unknown [ ] ;
4595
4696 protected constructor ( ...args : unknown [ ] ) {
97+ super ( ) ;
4798 this . _args = args ;
4899 }
49100
@@ -60,32 +111,70 @@ export abstract class QueryConstraint {
60111 }
61112}
62113
63- export class QueryCompositeFilterConstraint {
64- readonly type : 'or' | 'and' ;
65- readonly _filter : _Filter ;
114+ export class QueryCompositeFilterConstraint extends AppliableConstraint {
115+ /**
116+ * @internal
117+ */
118+ protected constructor (
119+ /** The type of this query constraint */
120+ readonly type : 'or' | 'and' ,
121+ private readonly _queryConstraints : QueryFilterConstraint [ ] ,
122+ ) {
123+ super ( ) ;
124+ }
66125
67- constructor ( type : 'or' | 'and' , filters : _Filter [ ] ) {
68- this . type = type ;
69- this . _filter = type === 'or' ? Filter . or ( ...filters ) : Filter . and ( ...filters ) ;
126+ static _create (
127+ type : 'or' | 'and' ,
128+ _queryConstraints : QueryFilterConstraint [ ] ,
129+ ) : QueryCompositeFilterConstraint {
130+ // Validate nested OR filters when creating the constraint
131+ if ( type === 'or' ) {
132+ const filters = _queryConstraints . map ( constraint => {
133+ if ( constraint instanceof QueryCompositeFilterConstraint ) {
134+ return constraint . _filter ;
135+ }
136+ return ( constraint as unknown as QueryFilterConstraintWithFilterInternal ) . _filter ;
137+ } ) ;
138+ // This will throw if nested OR filters are detected
139+ Filter . or ( ...filters ) ;
140+ }
141+ return new QueryCompositeFilterConstraint ( type , _queryConstraints ) ;
70142 }
71143
72144 _apply < AppModelType = DocumentData , DbModelType extends DocumentData = DocumentData > (
73145 query : Query < AppModelType , DbModelType > ,
74146 ) : Query < AppModelType , DbModelType > {
147+ const filters = this . _queryConstraints . map ( constraint => {
148+ if ( constraint instanceof QueryCompositeFilterConstraint ) {
149+ return constraint . _filter ;
150+ }
151+ return ( constraint as unknown as QueryFilterConstraintWithFilterInternal ) . _filter ;
152+ } ) ;
153+ const _filter = this . type === 'or' ? Filter . or ( ...filters ) : Filter . and ( ...filters ) ;
75154 const where = ( query as unknown as QueryWithWhereInternal < AppModelType , DbModelType > ) . where ;
76- return where . call ( query , this . _filter , MODULAR_DEPRECATION_ARG ) ;
155+ return where . call ( query , _filter , MODULAR_DEPRECATION_ARG ) ;
156+ }
157+
158+ get _filter ( ) : _Filter {
159+ const filters = this . _queryConstraints . map ( constraint => {
160+ if ( constraint instanceof QueryCompositeFilterConstraint ) {
161+ return constraint . _filter ;
162+ }
163+ return ( constraint as unknown as QueryFilterConstraintWithFilterInternal ) . _filter ;
164+ } ) ;
165+ return this . type === 'or' ? Filter . or ( ...filters ) : Filter . and ( ...filters ) ;
77166 }
78167}
79168
80- export class QueryOrderByConstraint extends QueryConstraint {
169+ export class QueryOrderByConstraint extends QueryConstraintBase {
81170 readonly type = 'orderBy' ;
82171
83172 constructor ( fieldPath : string | FieldPath , directionStr ?: OrderByDirection ) {
84173 super ( fieldPath , directionStr ) ;
85174 }
86175}
87176
88- export class QueryLimitConstraint extends QueryConstraint {
177+ export class QueryLimitConstraint extends QueryConstraintBase {
89178 readonly type : 'limit' | 'limitToLast' ;
90179
91180 constructor ( type : 'limit' | 'limitToLast' , limitValue : number ) {
@@ -94,7 +183,7 @@ export class QueryLimitConstraint extends QueryConstraint {
94183 }
95184}
96185
97- export class QueryStartAtConstraint extends QueryConstraint {
186+ export class QueryStartAtConstraint extends QueryConstraintBase {
98187 readonly type : 'startAt' | 'startAfter' ;
99188
100189 constructor ( type : 'startAt' | 'startAfter' , ...docOrFields : Array < unknown | DocumentSnapshot > ) {
@@ -103,7 +192,7 @@ export class QueryStartAtConstraint extends QueryConstraint {
103192 }
104193}
105194
106- export class QueryEndAtConstraint extends QueryConstraint {
195+ export class QueryEndAtConstraint extends QueryConstraintBase {
107196 readonly type : 'endAt' | 'endBefore' ;
108197
109198 constructor ( type : 'endAt' | 'endBefore' , ...fieldValues : unknown [ ] ) {
@@ -112,7 +201,7 @@ export class QueryEndAtConstraint extends QueryConstraint {
112201 }
113202}
114203
115- export class QueryFieldFilterConstraint extends QueryConstraint {
204+ export class QueryFieldFilterConstraint extends QueryConstraintBase {
116205 readonly type = 'where' ;
117206 readonly _filter : _Filter ;
118207
@@ -140,20 +229,23 @@ export function query<AppModelType = DocumentData, DbModelType extends DocumentD
140229) : Query < AppModelType , DbModelType > ;
141230export function query < AppModelType = DocumentData , DbModelType extends DocumentData = DocumentData > (
142231 queryRef : Query < AppModelType , DbModelType > ,
143- ...queryConstraints : Array < QueryCompositeFilterConstraint | QueryConstraint >
232+ queryConstraint : QueryCompositeFilterConstraint | QueryConstraint | undefined ,
233+ ...additionalQueryConstraints : Array < QueryConstraint | QueryNonFilterConstraint >
144234) : Query < AppModelType , DbModelType > {
235+ let queryConstraints : AppliableConstraint [ ] = [ ] ;
236+
237+ if ( queryConstraint instanceof AppliableConstraint ) {
238+ queryConstraints . push ( queryConstraint ) ;
239+ }
240+
241+ queryConstraints = queryConstraints . concat ( additionalQueryConstraints ) ;
242+
145243 let constrainedQuery = queryRef ;
146244 for ( const constraint of queryConstraints ) {
147245 if ( ! constraint ) {
148246 continue ;
149247 }
150- const apply = (
151- constraint as unknown as QueryConstraintWithApplyInternal < AppModelType , DbModelType >
152- ) . _apply ;
153- if ( ! apply ) {
154- continue ;
155- }
156- constrainedQuery = apply . call ( constraint , constrainedQuery ) ;
248+ constrainedQuery = constraint . _apply ( constrainedQuery ) ;
157249 }
158250 return constrainedQuery ;
159251}
@@ -166,22 +258,12 @@ export function where(
166258 return new QueryFieldFilterConstraint ( fieldPath , opStr , value ) ;
167259}
168260
169- function toFilter ( queryConstraint : QueryFilterConstraint ) : _Filter {
170- if ( queryConstraint instanceof QueryCompositeFilterConstraint ) {
171- return ( queryConstraint as unknown as QueryFilterConstraintWithFilterInternal ) . _filter ;
172- }
173- if ( queryConstraint instanceof QueryFieldFilterConstraint ) {
174- return ( queryConstraint as unknown as QueryFilterConstraintWithFilterInternal ) . _filter ;
175- }
176- throw new Error ( 'Invalid query constraint: expected filter constraint' ) ;
177- }
178-
179261export function or ( ...queryConstraints : QueryFilterConstraint [ ] ) : QueryCompositeFilterConstraint {
180- return new QueryCompositeFilterConstraint ( 'or' , queryConstraints . map ( toFilter ) ) ;
262+ return QueryCompositeFilterConstraint . _create ( 'or' , queryConstraints ) ;
181263}
182264
183265export function and ( ...queryConstraints : QueryFilterConstraint [ ] ) : QueryCompositeFilterConstraint {
184- return new QueryCompositeFilterConstraint ( 'and' , queryConstraints . map ( toFilter ) ) ;
266+ return QueryCompositeFilterConstraint . _create ( 'and' , queryConstraints ) ;
185267}
186268
187269export function orderBy (
0 commit comments