@umituz/react-native-firebase 2.6.1 → 2.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/application/auth/ports/AuthPort_part_aa +150 -0
- package/src/application/auth/ports/AuthPort_part_ab +14 -0
- package/src/application/auth/use-cases/SignInUseCaseHelpers.ts +0 -0
- package/src/application/auth/use-cases/SignInUseCaseMain.ts +0 -0
- package/src/application/auth/use-cases/SignInUseCase_part_aa +150 -0
- package/src/application/auth/use-cases/SignInUseCase_part_ab +103 -0
- package/src/application/auth/use-cases/SignOutUseCaseCleanup.ts +0 -0
- package/src/application/auth/use-cases/SignOutUseCaseMain.ts +0 -0
- package/src/application/auth/use-cases/SignOutUseCase_part_aa +150 -0
- package/src/application/auth/use-cases/SignOutUseCase_part_ab +138 -0
- package/src/domains/account-deletion/domain/services/UserValidationHelpers.ts.bak +181 -0
- package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_aa +150 -0
- package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_ab +31 -0
- package/src/domains/account-deletion/domain/services/{UserValidationService.ts → UserValidationService.ts.bak} +1 -10
- package/src/domains/account-deletion/domain/services/UserValidationService_part_aa +150 -0
- package/src/domains/account-deletion/domain/services/UserValidationService_part_ab +136 -0
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_aa +150 -0
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_ab +80 -0
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_aa +150 -0
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_ab +24 -0
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_aa +150 -0
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_ab +116 -0
- package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_aa +150 -0
- package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_ab +10 -0
- package/src/domains/auth/infrastructure_part_aa +150 -0
- package/src/domains/auth/infrastructure_part_ab +6 -0
- package/src/domains/auth/presentation/hooks/GoogleOAuthHelpers.ts +0 -0
- package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_aa +150 -0
- package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_ab +97 -0
- package/src/domains/auth/presentation/hooks/GoogleOAuthService.ts +0 -0
- package/src/domains/firestore/domain/entities/Collection.ts +31 -191
- package/src/domains/firestore/domain/entities/Collection.ts.bak +288 -0
- package/src/domains/firestore/domain/entities/CollectionFactory.ts +55 -0
- package/src/domains/firestore/domain/entities/CollectionHelpers.ts +143 -0
- package/src/domains/firestore/domain/entities/CollectionUtils.ts +72 -0
- package/src/domains/firestore/domain/entities/CollectionValidation.ts +138 -0
- package/src/domains/firestore/domain/entities/Collection_part_aa +150 -0
- package/src/domains/firestore/domain/entities/Collection_part_ab +138 -0
- package/src/domains/firestore/domain/entities/DocumentHelpers.ts +0 -0
- package/src/domains/firestore/domain/entities/DocumentMain.ts +0 -0
- package/src/domains/firestore/domain/entities/Document_part_aa +150 -0
- package/src/domains/firestore/domain/entities/Document_part_ab +83 -0
- package/src/domains/firestore/domain/index.ts +35 -8
- package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_aa +150 -0
- package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_ab +19 -0
- package/src/domains/firestore/domain/services/QueryServiceHelpers_part_aa +150 -0
- package/src/domains/firestore/domain/services/QueryServiceHelpers_part_ab +1 -0
- package/src/domains/firestore/domain/services/QueryService_part_aa +150 -0
- package/src/domains/firestore/domain/services/QueryService_part_ab +32 -0
- package/src/domains/firestore/domain/value-objects/QueryOptions.ts +20 -68
- package/src/domains/firestore/domain/value-objects/QueryOptions.ts.bak +6 -135
- package/src/domains/firestore/domain/value-objects/QueryOptionsFactory.ts +95 -0
- package/src/domains/firestore/domain/value-objects/QueryOptionsHelpers.ts +110 -0
- package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_aa +150 -0
- package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_ab +57 -0
- package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_aa +150 -0
- package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_ab +32 -0
- package/src/domains/firestore/domain/value-objects/QueryOptions_part_aa +150 -0
- package/src/domains/firestore/domain/value-objects/QueryOptions_part_ab +41 -0
- package/src/domains/firestore/domain/value-objects/WhereClause.ts +35 -205
- package/src/domains/firestore/domain/value-objects/WhereClause.ts.bak +299 -0
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts +44 -150
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts.bak +207 -0
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_aa +150 -0
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_ab +57 -0
- package/src/domains/firestore/domain/value-objects/WhereClauseHelpers.ts +123 -0
- package/src/domains/firestore/domain/value-objects/WhereClauseValidation.ts +83 -0
- package/src/domains/firestore/domain/value-objects/WhereClause_part_aa +150 -0
- package/src/domains/firestore/domain/value-objects/WhereClause_part_ab +149 -0
- package/src/shared/infrastructure/base/ErrorHandler_part_aa +150 -0
- package/src/shared/infrastructure/base/ErrorHandler_part_ab +39 -0
- package/src/shared/infrastructure/base/ServiceBase_part_aa +150 -0
- package/src/shared/infrastructure/base/ServiceBase_part_ab +70 -0
- package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_aa +150 -0
- package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_ab +5 -0
- /package/src/application/auth/ports/{AuthPort.ts → AuthPort.ts.bak} +0 -0
- /package/src/application/auth/use-cases/{SignInUseCase.ts → SignInUseCase.ts.bak} +0 -0
- /package/src/application/auth/use-cases/{SignOutUseCase.ts → SignOutUseCase.ts.bak} +0 -0
- /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionExecutor.ts → AccountDeletionExecutor.ts.bak} +0 -0
- /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionReauthHandler.ts → AccountDeletionReauthHandler.ts.bak} +0 -0
- /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionRepository.ts → AccountDeletionRepository.ts.bak} +0 -0
- /package/src/domains/account-deletion/infrastructure/services/{reauthentication.service.ts → reauthentication.service.ts.bak} +0 -0
- /package/src/domains/auth/{infrastructure.ts → infrastructure.ts.bak} +0 -0
- /package/src/domains/auth/presentation/hooks/{GoogleOAuthHookService.ts → GoogleOAuthHookService.ts.bak} +0 -0
- /package/src/domains/firestore/domain/entities/{Document.ts → Document.ts.bak} +0 -0
- /package/src/domains/firestore/domain/services/{QueryService.ts → QueryService.ts.bak} +0 -0
- /package/src/domains/firestore/domain/services/{QueryServiceAnalysis.ts → QueryServiceAnalysis.ts.bak} +0 -0
- /package/src/domains/firestore/domain/services/{QueryServiceHelpers.ts → QueryServiceHelpers.ts.bak} +0 -0
- /package/src/domains/firestore/domain/value-objects/{QueryOptionsSerialization.ts → QueryOptionsSerialization.ts.bak} +0 -0
- /package/src/domains/firestore/domain/value-objects/{QueryOptionsValidation.ts → QueryOptionsValidation.ts.bak} +0 -0
- /package/src/shared/infrastructure/base/{ErrorHandler.ts → ErrorHandler.ts.bak} +0 -0
- /package/src/shared/infrastructure/base/{ServiceBase.ts → ServiceBase.ts.bak} +0 -0
- /package/src/shared/infrastructure/config/base/{ServiceClientSingleton.ts → ServiceClientSingleton.ts.bak} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Where Clause Value Object
|
|
2
|
+
* Where Clause Value Object (Main)
|
|
3
3
|
* Single Responsibility: Encapsulate where clause conditions
|
|
4
4
|
*
|
|
5
5
|
* Value object that represents a single where clause condition.
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { WhereFilterOp } from 'firebase/firestore';
|
|
12
|
+
import * as Validation from './WhereClauseValidation';
|
|
13
|
+
import * as Helpers from './WhereClauseHelpers';
|
|
14
|
+
import * as Factory from './WhereClauseFactory';
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* Valid where operators for Firestore queries
|
|
@@ -34,266 +37,93 @@ export class WhereClause {
|
|
|
34
37
|
readonly operator: WhereFilterOp;
|
|
35
38
|
readonly value: unknown;
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
constructor(field: string, operator: WhereFilterOp, value: unknown) {
|
|
38
41
|
this.field = field;
|
|
39
42
|
this.operator = operator;
|
|
40
43
|
this.value = Object.freeze(value);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
* Create a where clause
|
|
45
|
-
*/
|
|
46
|
-
static create(field: string, operator: WhereFilterOp, value: unknown): WhereClause {
|
|
47
|
-
return new WhereClause(field, operator, value);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Create equality clause (==)
|
|
52
|
-
*/
|
|
53
|
-
static equals(field: string, value: unknown): WhereClause {
|
|
54
|
-
return new WhereClause(field, '==', value);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Create inequality clause (!=)
|
|
59
|
-
*/
|
|
60
|
-
static notEquals(field: string, value: unknown): WhereClause {
|
|
61
|
-
return new WhereClause(field, '!=', value);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Create less than clause (<)
|
|
66
|
-
*/
|
|
67
|
-
static lessThan(field: string, value: unknown): WhereClause {
|
|
68
|
-
return new WhereClause(field, '<', value);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Create less than or equal clause (<=)
|
|
73
|
-
*/
|
|
74
|
-
static lessThanOrEqual(field: string, value: unknown): WhereClause {
|
|
75
|
-
return new WhereClause(field, '<=', value);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Create greater than clause (>)
|
|
80
|
-
*/
|
|
81
|
-
static greaterThan(field: string, value: unknown): WhereClause {
|
|
82
|
-
return new WhereClause(field, '>', value);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Create greater than or equal clause (>=)
|
|
87
|
-
*/
|
|
88
|
-
static greaterThanOrEqual(field: string, value: unknown): WhereClause {
|
|
89
|
-
return new WhereClause(field, '>=', value);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Create array contains clause
|
|
94
|
-
*/
|
|
95
|
-
static arrayContains(field: string, value: unknown): WhereClause {
|
|
96
|
-
return new WhereClause(field, 'array-contains', value);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Create array contains any clause
|
|
101
|
-
*/
|
|
102
|
-
static arrayContainsAny(field: string, values: unknown[]): WhereClause {
|
|
103
|
-
return new WhereClause(field, 'array-contains-any', values);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Create in clause
|
|
108
|
-
*/
|
|
109
|
-
static in(field: string, values: unknown[]): WhereClause {
|
|
110
|
-
return new WhereClause(field, 'in', values);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Create not-in clause
|
|
115
|
-
*/
|
|
116
|
-
static notIn(field: string, values: unknown[]): WhereClause {
|
|
117
|
-
return new WhereClause(field, 'not-in', values);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Validate where clause
|
|
122
|
-
*/
|
|
46
|
+
// Instance methods using helpers
|
|
123
47
|
validate(): { valid: boolean; errors: string[] } {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Validate field name
|
|
127
|
-
if (!this.field || typeof this.field !== 'string' || this.field.trim() === '') {
|
|
128
|
-
errors.push('Field name must be a non-empty string');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Validate operator
|
|
132
|
-
const validOperators: WhereFilterOp[] = [
|
|
133
|
-
'==', '!=', '<', '<=', '>', '>=',
|
|
134
|
-
'array-contains', 'array-contains-any', 'in', 'not-in'
|
|
135
|
-
];
|
|
136
|
-
if (!validOperators.includes(this.operator)) {
|
|
137
|
-
errors.push(`Invalid operator: ${this.operator}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Validate value based on operator
|
|
141
|
-
if (this.operator === 'array-contains-any' || this.operator === 'in' || this.operator === 'not-in') {
|
|
142
|
-
if (!Array.isArray(this.value)) {
|
|
143
|
-
errors.push(`Operator ${this.operator} requires an array value`);
|
|
144
|
-
} else if (this.value.length === 0) {
|
|
145
|
-
errors.push(`Operator ${this.operator} requires a non-empty array`);
|
|
146
|
-
} else if (this.value.length > 10) {
|
|
147
|
-
errors.push(`Operator ${this.operator} supports maximum 10 elements`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
valid: errors.length === 0,
|
|
153
|
-
errors,
|
|
154
|
-
};
|
|
48
|
+
return Validation.validateWhereClause(this);
|
|
155
49
|
}
|
|
156
50
|
|
|
157
|
-
/**
|
|
158
|
-
* Check if equals another where clause
|
|
159
|
-
*/
|
|
160
51
|
equals(other: WhereClause): boolean {
|
|
161
|
-
return (
|
|
162
|
-
this.field === other.field &&
|
|
163
|
-
this.operator === other.operator &&
|
|
164
|
-
JSON.stringify(this.value) === JSON.stringify(other.value)
|
|
165
|
-
);
|
|
52
|
+
return Helpers.whereClausesEqual(this, other);
|
|
166
53
|
}
|
|
167
54
|
|
|
168
|
-
/**
|
|
169
|
-
* Check if compatible for compound queries
|
|
170
|
-
* Some operator combinations are not allowed in Firestore
|
|
171
|
-
*/
|
|
172
55
|
isCompatibleWith(other: WhereClause): boolean {
|
|
173
|
-
|
|
174
|
-
// Array operators and membership operators have restrictions
|
|
175
|
-
if (
|
|
176
|
-
(this.isArrayOperator() || this.isMembership()) &&
|
|
177
|
-
(other.isArrayOperator() || other.isMembership())
|
|
178
|
-
) {
|
|
179
|
-
// Only one array/membership clause per query
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return true;
|
|
56
|
+
return Helpers.areClausesCompatible(this, other);
|
|
184
57
|
}
|
|
185
58
|
|
|
186
|
-
/**
|
|
187
|
-
* Get field path components
|
|
188
|
-
* Returns array of field path segments (for nested fields)
|
|
189
|
-
*/
|
|
190
59
|
getFieldPath(): string[] {
|
|
191
|
-
return
|
|
60
|
+
return Helpers.getFieldPath(this);
|
|
192
61
|
}
|
|
193
62
|
|
|
194
|
-
/**
|
|
195
|
-
* Check if field is nested (contains dots)
|
|
196
|
-
*/
|
|
197
63
|
isNestedField(): boolean {
|
|
198
|
-
return
|
|
64
|
+
return Helpers.isNestedField(this);
|
|
199
65
|
}
|
|
200
66
|
|
|
201
|
-
/**
|
|
202
|
-
* Get top-level field name
|
|
203
|
-
*/
|
|
204
67
|
getTopLevelField(): string {
|
|
205
|
-
return
|
|
68
|
+
return Helpers.getTopLevelField(this);
|
|
206
69
|
}
|
|
207
70
|
|
|
208
|
-
/**
|
|
209
|
-
* Get human-readable description
|
|
210
|
-
*/
|
|
211
71
|
getDescription(): string {
|
|
212
|
-
|
|
213
|
-
? `[${this.value.map(v => JSON.stringify(v)).join(', ')}]`
|
|
214
|
-
: JSON.stringify(this.value);
|
|
215
|
-
|
|
216
|
-
return `${this.field} ${this.operator} ${valueStr}`;
|
|
72
|
+
return Helpers.getDescription(this);
|
|
217
73
|
}
|
|
218
74
|
|
|
219
|
-
/**
|
|
220
|
-
* Convert to plain object (for serialization)
|
|
221
|
-
*/
|
|
222
75
|
toObject(): { field: string; operator: WhereFilterOp; value: unknown } {
|
|
223
|
-
return
|
|
224
|
-
field: this.field,
|
|
225
|
-
operator: this.operator,
|
|
226
|
-
value: this.value,
|
|
227
|
-
};
|
|
76
|
+
return Helpers.toObject(this);
|
|
228
77
|
}
|
|
229
78
|
|
|
230
|
-
/**
|
|
231
|
-
* Create from plain object
|
|
232
|
-
*/
|
|
233
|
-
static fromObject(obj: { field: string; operator: WhereFilterOp; value: unknown }): WhereClause {
|
|
234
|
-
return WhereClause.create(obj.field, obj.operator, obj.value);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Clone with new value
|
|
239
|
-
*/
|
|
240
79
|
withValue(newValue: unknown): WhereClause {
|
|
241
80
|
return new WhereClause(this.field, this.operator, newValue);
|
|
242
81
|
}
|
|
243
82
|
|
|
244
|
-
/**
|
|
245
|
-
* Clone with new field
|
|
246
|
-
*/
|
|
247
83
|
withField(newField: string): WhereClause {
|
|
248
84
|
return new WhereClause(newField, this.operator, this.value);
|
|
249
85
|
}
|
|
250
86
|
|
|
251
|
-
/**
|
|
252
|
-
* Clone with new operator
|
|
253
|
-
*/
|
|
254
87
|
withOperator(newOperator: WhereFilterOp): WhereClause {
|
|
255
88
|
return new WhereClause(this.field, newOperator, this.value);
|
|
256
89
|
}
|
|
257
90
|
|
|
258
|
-
/**
|
|
259
|
-
* Check if is equality operator
|
|
260
|
-
*/
|
|
261
91
|
isEquality(): boolean {
|
|
262
|
-
return this
|
|
92
|
+
return Helpers.isEqualityClause(this);
|
|
263
93
|
}
|
|
264
94
|
|
|
265
|
-
/**
|
|
266
|
-
* Check if is inequality operator
|
|
267
|
-
*/
|
|
268
95
|
isInequality(): boolean {
|
|
269
|
-
return this
|
|
96
|
+
return Helpers.isInequalityClause(this);
|
|
270
97
|
}
|
|
271
98
|
|
|
272
|
-
/**
|
|
273
|
-
* Check if is comparison operator (<, <=, >, >=)
|
|
274
|
-
*/
|
|
275
99
|
isComparison(): boolean {
|
|
276
|
-
return
|
|
100
|
+
return Helpers.isComparisonClause(this);
|
|
277
101
|
}
|
|
278
102
|
|
|
279
|
-
/**
|
|
280
|
-
* Check if is array operator
|
|
281
|
-
*/
|
|
282
103
|
isArrayOperator(): boolean {
|
|
283
|
-
return
|
|
104
|
+
return Helpers.isArrayClause(this);
|
|
284
105
|
}
|
|
285
106
|
|
|
286
|
-
/**
|
|
287
|
-
* Check if is membership operator (in, not-in)
|
|
288
|
-
*/
|
|
289
107
|
isMembership(): boolean {
|
|
290
|
-
return
|
|
108
|
+
return Helpers.isMembershipClause(this);
|
|
291
109
|
}
|
|
292
110
|
|
|
293
|
-
/**
|
|
294
|
-
* Check if operator requires array value
|
|
295
|
-
*/
|
|
296
111
|
requiresArrayValue(): boolean {
|
|
297
|
-
return
|
|
112
|
+
return Validation.requiresArrayValue(this.operator);
|
|
298
113
|
}
|
|
299
114
|
}
|
|
115
|
+
|
|
116
|
+
// Re-export factory functions for backward compatibility
|
|
117
|
+
export const create = Factory.where;
|
|
118
|
+
export const equals = Factory.equals;
|
|
119
|
+
export const notEquals = Factory.notEquals;
|
|
120
|
+
export const lessThan = Factory.lessThan;
|
|
121
|
+
export const lessThanOrEqual = Factory.lessThanOrEqual;
|
|
122
|
+
export const greaterThan = Factory.greaterThan;
|
|
123
|
+
export const greaterThanOrEqual = Factory.greaterThanOrEqual;
|
|
124
|
+
export const arrayContains = Factory.arrayContains;
|
|
125
|
+
export const inOp = Factory.inOp;
|
|
126
|
+
export const notIn = Factory.notIn;
|
|
127
|
+
export const arrayContainsAny = Factory.arrayContainsAny;
|
|
128
|
+
export const where = Factory.where;
|
|
129
|
+
export const fromObject = Factory.fromObject;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where Clause Value Object
|
|
3
|
+
* Single Responsibility: Encapsulate where clause conditions
|
|
4
|
+
*
|
|
5
|
+
* Value object that represents a single where clause condition.
|
|
6
|
+
* Provides validation and business logic for query filtering.
|
|
7
|
+
*
|
|
8
|
+
* Max lines: 150 (enforced for maintainability)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { WhereFilterOp } from 'firebase/firestore';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Valid where operators for Firestore queries
|
|
15
|
+
*/
|
|
16
|
+
export type WhereOperator =
|
|
17
|
+
| '=='
|
|
18
|
+
| '!='
|
|
19
|
+
| '<'
|
|
20
|
+
| '<='
|
|
21
|
+
| '>'
|
|
22
|
+
| '>='
|
|
23
|
+
| 'array-contains'
|
|
24
|
+
| 'array-contains-any'
|
|
25
|
+
| 'in'
|
|
26
|
+
| 'not-in';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Where clause value object
|
|
30
|
+
* Immutable representation of a single query condition
|
|
31
|
+
*/
|
|
32
|
+
export class WhereClause {
|
|
33
|
+
readonly field: string;
|
|
34
|
+
readonly operator: WhereFilterOp;
|
|
35
|
+
readonly value: unknown;
|
|
36
|
+
|
|
37
|
+
private constructor(field: string, operator: WhereFilterOp, value: unknown) {
|
|
38
|
+
this.field = field;
|
|
39
|
+
this.operator = operator;
|
|
40
|
+
this.value = Object.freeze(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a where clause
|
|
45
|
+
*/
|
|
46
|
+
static create(field: string, operator: WhereFilterOp, value: unknown): WhereClause {
|
|
47
|
+
return new WhereClause(field, operator, value);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create equality clause (==)
|
|
52
|
+
*/
|
|
53
|
+
static equals(field: string, value: unknown): WhereClause {
|
|
54
|
+
return new WhereClause(field, '==', value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create inequality clause (!=)
|
|
59
|
+
*/
|
|
60
|
+
static notEquals(field: string, value: unknown): WhereClause {
|
|
61
|
+
return new WhereClause(field, '!=', value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create less than clause (<)
|
|
66
|
+
*/
|
|
67
|
+
static lessThan(field: string, value: unknown): WhereClause {
|
|
68
|
+
return new WhereClause(field, '<', value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create less than or equal clause (<=)
|
|
73
|
+
*/
|
|
74
|
+
static lessThanOrEqual(field: string, value: unknown): WhereClause {
|
|
75
|
+
return new WhereClause(field, '<=', value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create greater than clause (>)
|
|
80
|
+
*/
|
|
81
|
+
static greaterThan(field: string, value: unknown): WhereClause {
|
|
82
|
+
return new WhereClause(field, '>', value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create greater than or equal clause (>=)
|
|
87
|
+
*/
|
|
88
|
+
static greaterThanOrEqual(field: string, value: unknown): WhereClause {
|
|
89
|
+
return new WhereClause(field, '>=', value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create array contains clause
|
|
94
|
+
*/
|
|
95
|
+
static arrayContains(field: string, value: unknown): WhereClause {
|
|
96
|
+
return new WhereClause(field, 'array-contains', value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create array contains any clause
|
|
101
|
+
*/
|
|
102
|
+
static arrayContainsAny(field: string, values: unknown[]): WhereClause {
|
|
103
|
+
return new WhereClause(field, 'array-contains-any', values);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create in clause
|
|
108
|
+
*/
|
|
109
|
+
static in(field: string, values: unknown[]): WhereClause {
|
|
110
|
+
return new WhereClause(field, 'in', values);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create not-in clause
|
|
115
|
+
*/
|
|
116
|
+
static notIn(field: string, values: unknown[]): WhereClause {
|
|
117
|
+
return new WhereClause(field, 'not-in', values);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validate where clause
|
|
122
|
+
*/
|
|
123
|
+
validate(): { valid: boolean; errors: string[] } {
|
|
124
|
+
const errors: string[] = [];
|
|
125
|
+
|
|
126
|
+
// Validate field name
|
|
127
|
+
if (!this.field || typeof this.field !== 'string' || this.field.trim() === '') {
|
|
128
|
+
errors.push('Field name must be a non-empty string');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Validate operator
|
|
132
|
+
const validOperators: WhereFilterOp[] = [
|
|
133
|
+
'==', '!=', '<', '<=', '>', '>=',
|
|
134
|
+
'array-contains', 'array-contains-any', 'in', 'not-in'
|
|
135
|
+
];
|
|
136
|
+
if (!validOperators.includes(this.operator)) {
|
|
137
|
+
errors.push(`Invalid operator: ${this.operator}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Validate value based on operator
|
|
141
|
+
if (this.operator === 'array-contains-any' || this.operator === 'in' || this.operator === 'not-in') {
|
|
142
|
+
if (!Array.isArray(this.value)) {
|
|
143
|
+
errors.push(`Operator ${this.operator} requires an array value`);
|
|
144
|
+
} else if (this.value.length === 0) {
|
|
145
|
+
errors.push(`Operator ${this.operator} requires a non-empty array`);
|
|
146
|
+
} else if (this.value.length > 10) {
|
|
147
|
+
errors.push(`Operator ${this.operator} supports maximum 10 elements`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
valid: errors.length === 0,
|
|
153
|
+
errors,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check if equals another where clause
|
|
159
|
+
*/
|
|
160
|
+
equals(other: WhereClause): boolean {
|
|
161
|
+
return (
|
|
162
|
+
this.field === other.field &&
|
|
163
|
+
this.operator === other.operator &&
|
|
164
|
+
JSON.stringify(this.value) === JSON.stringify(other.value)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check if compatible for compound queries
|
|
170
|
+
* Some operator combinations are not allowed in Firestore
|
|
171
|
+
*/
|
|
172
|
+
isCompatibleWith(other: WhereClause): boolean {
|
|
173
|
+
// Equality and inequality operators can be combined
|
|
174
|
+
// Array operators and membership operators have restrictions
|
|
175
|
+
if (
|
|
176
|
+
(this.isArrayOperator() || this.isMembership()) &&
|
|
177
|
+
(other.isArrayOperator() || other.isMembership())
|
|
178
|
+
) {
|
|
179
|
+
// Only one array/membership clause per query
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get field path components
|
|
188
|
+
* Returns array of field path segments (for nested fields)
|
|
189
|
+
*/
|
|
190
|
+
getFieldPath(): string[] {
|
|
191
|
+
return this.field.split('.');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Check if field is nested (contains dots)
|
|
196
|
+
*/
|
|
197
|
+
isNestedField(): boolean {
|
|
198
|
+
return this.field.includes('.');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get top-level field name
|
|
203
|
+
*/
|
|
204
|
+
getTopLevelField(): string {
|
|
205
|
+
return this.getFieldPath()[0];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get human-readable description
|
|
210
|
+
*/
|
|
211
|
+
getDescription(): string {
|
|
212
|
+
const valueStr = Array.isArray(this.value)
|
|
213
|
+
? `[${this.value.map(v => JSON.stringify(v)).join(', ')}]`
|
|
214
|
+
: JSON.stringify(this.value);
|
|
215
|
+
|
|
216
|
+
return `${this.field} ${this.operator} ${valueStr}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Convert to plain object (for serialization)
|
|
221
|
+
*/
|
|
222
|
+
toObject(): { field: string; operator: WhereFilterOp; value: unknown } {
|
|
223
|
+
return {
|
|
224
|
+
field: this.field,
|
|
225
|
+
operator: this.operator,
|
|
226
|
+
value: this.value,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Create from plain object
|
|
232
|
+
*/
|
|
233
|
+
static fromObject(obj: { field: string; operator: WhereFilterOp; value: unknown }): WhereClause {
|
|
234
|
+
return WhereClause.create(obj.field, obj.operator, obj.value);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Clone with new value
|
|
239
|
+
*/
|
|
240
|
+
withValue(newValue: unknown): WhereClause {
|
|
241
|
+
return new WhereClause(this.field, this.operator, newValue);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Clone with new field
|
|
246
|
+
*/
|
|
247
|
+
withField(newField: string): WhereClause {
|
|
248
|
+
return new WhereClause(newField, this.operator, this.value);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Clone with new operator
|
|
253
|
+
*/
|
|
254
|
+
withOperator(newOperator: WhereFilterOp): WhereClause {
|
|
255
|
+
return new WhereClause(this.field, newOperator, this.value);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if is equality operator
|
|
260
|
+
*/
|
|
261
|
+
isEquality(): boolean {
|
|
262
|
+
return this.operator === '==';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if is inequality operator
|
|
267
|
+
*/
|
|
268
|
+
isInequality(): boolean {
|
|
269
|
+
return this.operator === '!=';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Check if is comparison operator (<, <=, >, >=)
|
|
274
|
+
*/
|
|
275
|
+
isComparison(): boolean {
|
|
276
|
+
return ['<', '<=', '>', '>='].includes(this.operator);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Check if is array operator
|
|
281
|
+
*/
|
|
282
|
+
isArrayOperator(): boolean {
|
|
283
|
+
return ['array-contains', 'array-contains-any'].includes(this.operator);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Check if is membership operator (in, not-in)
|
|
288
|
+
*/
|
|
289
|
+
isMembership(): boolean {
|
|
290
|
+
return ['in', 'not-in'].includes(this.operator);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Check if operator requires array value
|
|
295
|
+
*/
|
|
296
|
+
requiresArrayValue(): boolean {
|
|
297
|
+
return ['array-contains-any', 'in', 'not-in'].includes(this.operator);
|
|
298
|
+
}
|
|
299
|
+
}
|