@umituz/react-native-firebase 2.6.0 → 2.6.2
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/index.ts +42 -0
- package/src/application/auth/ports/AuthPort.ts.bak +164 -0
- 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/SignInUseCase.ts.bak +253 -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/SignOutUseCase.ts.bak +288 -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/application/auth/use-cases/index.ts +26 -0
- package/src/domains/account-deletion/domain/index.ts +15 -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.bak +286 -0
- 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/index.ts +43 -6
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts.bak +230 -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.ts.bak +174 -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.ts.bak +266 -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/AccountDeletionTypes.ts +33 -0
- package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +39 -227
- 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/domain.ts +16 -0
- package/src/domains/auth/index.ts +7 -148
- package/src/domains/auth/infrastructure.ts.bak +156 -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.ts.bak +247 -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/auth/presentation/hooks/useGoogleOAuth.ts +49 -103
- package/src/domains/auth/presentation.ts +25 -0
- package/src/domains/firestore/domain/entities/Collection.ts +128 -0
- 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/Document.ts.bak +233 -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 +65 -0
- package/src/domains/firestore/domain/services/QueryService.ts.bak +182 -0
- package/src/domains/firestore/domain/services/QueryServiceAnalysis.ts.bak +169 -0
- 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.ts.bak +151 -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.bak +191 -0
- package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization.ts.bak +207 -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.ts.bak +182 -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.bak +299 -0
- 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/WhereClause_part_aa +150 -0
- package/src/domains/firestore/domain/value-objects/WhereClause_part_ab +149 -0
- package/src/domains/firestore/index.ts +9 -6
- package/src/index.ts +25 -0
- package/src/shared/domain/utils/error-handlers/error-messages.ts +11 -0
- package/src/shared/infrastructure/base/ErrorHandler.ts.bak +189 -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.ts.bak +220 -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/base/TypedGuard.ts +131 -0
- package/src/shared/infrastructure/base/index.ts +34 -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/domains/account-deletion/infrastructure/services/{reauthentication.service.ts → reauthentication.service.ts.bak} +0 -0
- /package/src/shared/infrastructure/config/base/{ServiceClientSingleton.ts → ServiceClientSingleton.ts.bak} +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
withData<K extends keyof T>(field: K, value: T[K]): Document<T> {
|
|
2
|
+
if (!this.data) {
|
|
3
|
+
throw new Error('Cannot update null document data');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const newData = { ...this.data, [field]: value };
|
|
7
|
+
return new Document(
|
|
8
|
+
{
|
|
9
|
+
id: this.id,
|
|
10
|
+
exists: true,
|
|
11
|
+
data() {
|
|
12
|
+
return newData;
|
|
13
|
+
},
|
|
14
|
+
} as unknown as DocumentSnapshot<T>,
|
|
15
|
+
this.path
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate document data against required fields
|
|
21
|
+
*/
|
|
22
|
+
validateRequiredFields(requiredFields: (keyof T)[]): { valid: boolean; missing: (keyof T)[] } {
|
|
23
|
+
if (!this.data) {
|
|
24
|
+
return { valid: false, missing: requiredFields };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const missing = requiredFields.filter(field => !this.hasField(field));
|
|
28
|
+
return {
|
|
29
|
+
valid: missing.length === 0,
|
|
30
|
+
missing,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if document matches filter criteria
|
|
36
|
+
*/
|
|
37
|
+
matches(filter: Partial<T>): boolean {
|
|
38
|
+
if (!this.data) return false;
|
|
39
|
+
|
|
40
|
+
return Object.entries(filter).every(([key, value]) => {
|
|
41
|
+
const fieldValue = this.data![key as keyof T];
|
|
42
|
+
return fieldValue === value;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if document matches filter criteria
|
|
48
|
+
*/
|
|
49
|
+
matches(filter: Partial<T>): boolean {
|
|
50
|
+
if (!this.data) return false;
|
|
51
|
+
|
|
52
|
+
return Object.entries(filter).every(([key, value]) => {
|
|
53
|
+
return this.data[key as keyof T] === value;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get document size in bytes (approximate)
|
|
59
|
+
* Useful for quota management
|
|
60
|
+
*/
|
|
61
|
+
getSize(): number {
|
|
62
|
+
if (!this.data) return 0;
|
|
63
|
+
|
|
64
|
+
return JSON.stringify(this.data).length;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if document size exceeds limit
|
|
69
|
+
*/
|
|
70
|
+
exceedsSizeLimit(maxBytes: number): boolean {
|
|
71
|
+
return this.getSize() > maxBytes;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Factory function to create document entity
|
|
77
|
+
*/
|
|
78
|
+
export function createDocument<T extends DocumentData>(
|
|
79
|
+
snapshot: DocumentSnapshot<T>,
|
|
80
|
+
path: string
|
|
81
|
+
): Document<T> {
|
|
82
|
+
return Document.fromSnapshot(snapshot, path);
|
|
83
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firestore Domain Layer
|
|
3
|
+
* Domain-Driven Design (DDD) - Domain Exports
|
|
4
|
+
*
|
|
5
|
+
* Pure domain logic without infrastructure concerns.
|
|
6
|
+
* Exports entities, value objects, and domain services.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Domain Errors
|
|
10
|
+
export {
|
|
11
|
+
FirebaseFirestoreError,
|
|
12
|
+
FirebaseFirestoreInitializationError,
|
|
13
|
+
FirebaseFirestoreQuotaError,
|
|
14
|
+
} from './errors/FirebaseFirestoreError';
|
|
15
|
+
|
|
16
|
+
// Domain Entities
|
|
17
|
+
export { Collection, createCollection, fromReference, fromQuery } from './entities/Collection';
|
|
18
|
+
export type { CollectionMetadata } from './entities/Collection';
|
|
19
|
+
|
|
20
|
+
// Document entity (if exists)
|
|
21
|
+
// export { Document, createDocument } from './entities/Document';
|
|
22
|
+
// export type { DocumentMetadata } from './entities/Document';
|
|
23
|
+
|
|
24
|
+
// Collection Entity Helpers
|
|
25
|
+
export {
|
|
26
|
+
isParentOf,
|
|
27
|
+
getCollectionId,
|
|
28
|
+
collectionsEqual,
|
|
29
|
+
getCollectionInfo,
|
|
30
|
+
collectionFromPath,
|
|
31
|
+
getSubCollectionNames,
|
|
32
|
+
buildCollectionPath,
|
|
33
|
+
parseCollectionPath,
|
|
34
|
+
} from './entities/CollectionHelpers';
|
|
35
|
+
|
|
36
|
+
// Collection Utilities
|
|
37
|
+
export {
|
|
38
|
+
getCollectionDepth,
|
|
39
|
+
collectionToObject,
|
|
40
|
+
isQueryReference,
|
|
41
|
+
} from './entities/CollectionUtils';
|
|
42
|
+
|
|
43
|
+
// Collection Validation
|
|
44
|
+
export {
|
|
45
|
+
isValidCollectionName,
|
|
46
|
+
isValidCollectionPath,
|
|
47
|
+
isValidCollection,
|
|
48
|
+
extractCollectionNameFromPath,
|
|
49
|
+
extractParentCollectionPath,
|
|
50
|
+
isDocumentPath,
|
|
51
|
+
isCollectionPath,
|
|
52
|
+
isUserCollectionPath,
|
|
53
|
+
extractUserIdFromPath,
|
|
54
|
+
createSubCollectionPath,
|
|
55
|
+
} from './entities/CollectionValidation';
|
|
56
|
+
|
|
57
|
+
// Domain Value Objects
|
|
58
|
+
// export { QueryOptions, createQueryOptions } from './value-objects/QueryOptions';
|
|
59
|
+
// export { WhereClause, createWhereClause, where } from './value-objects/WhereClause';
|
|
60
|
+
// export type { SortOptions, DateRangeOptions, PaginationOptions } from './value-objects/QueryOptions';
|
|
61
|
+
// export type { WhereOperator } from './value-objects/WhereClause';
|
|
62
|
+
|
|
63
|
+
// Domain Services
|
|
64
|
+
// export { QueryService, createQueryService } from './services/QueryService';
|
|
65
|
+
// export type { QueryBuilderResult } from './services/QueryService';
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Service (Main Builder)
|
|
3
|
+
* Single Responsibility: Build and validate Firestore queries
|
|
4
|
+
*
|
|
5
|
+
* Domain service that encapsulates query building logic.
|
|
6
|
+
* Moves business logic from infrastructure to domain layer.
|
|
7
|
+
*
|
|
8
|
+
* Max lines: 150 (enforced for maintainability)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Query, Firestore, WhereFilterOp } from 'firebase/firestore';
|
|
12
|
+
import { collection, query, where, orderBy, limit, startAfter, startAt } from 'firebase/firestore';
|
|
13
|
+
import { QueryOptions, createQueryOptions } from '../value-objects/QueryOptions';
|
|
14
|
+
import { WhereClause } from '../value-objects/WhereClause';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Query builder result
|
|
18
|
+
*/
|
|
19
|
+
export interface QueryBuilderResult {
|
|
20
|
+
readonly query: Query;
|
|
21
|
+
readonly options: QueryOptions;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Query service
|
|
26
|
+
* Provides query building and validation functionality
|
|
27
|
+
*/
|
|
28
|
+
export class QueryService {
|
|
29
|
+
private readonly db: Firestore;
|
|
30
|
+
|
|
31
|
+
constructor(db: Firestore) {
|
|
32
|
+
this.db = db;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build a query from options
|
|
37
|
+
* Main method for query construction
|
|
38
|
+
*/
|
|
39
|
+
buildQuery(collectionPath: string, options: QueryOptions): QueryBuilderResult {
|
|
40
|
+
const validation = options.validate();
|
|
41
|
+
if (!validation.valid) {
|
|
42
|
+
throw new Error(`Invalid query options: ${validation.errors.join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const collectionRef = collection(this.db, collectionPath);
|
|
46
|
+
let q: Query = collectionRef;
|
|
47
|
+
|
|
48
|
+
// Apply where clauses
|
|
49
|
+
for (const clause of options.whereClauses) {
|
|
50
|
+
q = query(q, where(clause.field, clause.operator, clause.value));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Apply sort options
|
|
54
|
+
for (const sort of options.sortOptions) {
|
|
55
|
+
q = query(q, orderBy(sort.field, sort.direction));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Apply date range
|
|
59
|
+
if (options.dateRange) {
|
|
60
|
+
const { field, startDate, endDate } = options.dateRange;
|
|
61
|
+
if (startDate) {
|
|
62
|
+
q = query(q, where(field, '>=', startDate));
|
|
63
|
+
}
|
|
64
|
+
if (endDate) {
|
|
65
|
+
q = query(q, where(field, '<=', endDate));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Apply pagination
|
|
70
|
+
if (options.pagination) {
|
|
71
|
+
const { cursor, limit: limitValue, startAfter: startAfterValue, startAt: startAtValue } = options.pagination;
|
|
72
|
+
|
|
73
|
+
if (startAfterValue !== undefined) {
|
|
74
|
+
q = query(q, startAfter(startAfterValue));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (startAtValue !== undefined) {
|
|
78
|
+
q = query(q, startAt(startAtValue));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (limitValue !== undefined) {
|
|
82
|
+
q = query(q, limit(limitValue));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { query: q, options };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build simple query with single where clause
|
|
91
|
+
*/
|
|
92
|
+
buildSimpleQuery(
|
|
93
|
+
collectionPath: string,
|
|
94
|
+
field: string,
|
|
95
|
+
operator: WhereFilterOp,
|
|
96
|
+
value: unknown
|
|
97
|
+
): Query {
|
|
98
|
+
const options = createQueryOptions({
|
|
99
|
+
where: [WhereClause.create(field, operator, value)],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return this.buildQuery(collectionPath, options).query;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build query with equality filter
|
|
107
|
+
*/
|
|
108
|
+
buildEqualsQuery(collectionPath: string, field: string, value: unknown): Query {
|
|
109
|
+
return this.buildSimpleQuery(collectionPath, field, '==', value);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Build query with multiple equality filters
|
|
114
|
+
*/
|
|
115
|
+
buildMultiEqualsQuery(
|
|
116
|
+
collectionPath: string,
|
|
117
|
+
filters: Record<string, unknown>
|
|
118
|
+
): Query {
|
|
119
|
+
const whereClauses = Object.entries(filters).map(([field, value]) =>
|
|
120
|
+
WhereClause.equals(field, value)
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const options = createQueryOptions({ where: whereClauses });
|
|
124
|
+
return this.buildQuery(collectionPath, options).query;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build query with sorting
|
|
129
|
+
*/
|
|
130
|
+
buildSortedQuery(
|
|
131
|
+
collectionPath: string,
|
|
132
|
+
sortField: string,
|
|
133
|
+
direction: 'asc' | 'desc' = 'asc'
|
|
134
|
+
): Query {
|
|
135
|
+
const options = createQueryOptions({
|
|
136
|
+
sort: [{ field: sortField, direction }],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return this.buildQuery(collectionPath, options).query;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build query with limit
|
|
144
|
+
*/
|
|
145
|
+
buildLimitedQuery(collectionPath: string, limitValue: number): Query {
|
|
146
|
+
const options = createQueryOptions({
|
|
147
|
+
pagination: { limit: limitValue },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return this.buildQuery(collectionPath, options).query;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Build query with date range
|
|
155
|
+
*/
|
|
156
|
+
buildDateRangeQuery(
|
|
157
|
+
collectionPath: string,
|
|
158
|
+
field: string,
|
|
159
|
+
startDate?: Date,
|
|
160
|
+
endDate?: Date
|
|
161
|
+
): Query {
|
|
162
|
+
const options = createQueryOptions({
|
|
163
|
+
dateRange: { field, startDate, endDate },
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return this.buildQuery(collectionPath, options).query;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Validate query options
|
|
171
|
+
*/
|
|
172
|
+
validateOptions(options: QueryOptions): { valid: boolean; errors: string[] } {
|
|
173
|
+
return options.validate();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if query is empty (no filters)
|
|
178
|
+
*/
|
|
179
|
+
isEmptyQuery(options: QueryOptions): boolean {
|
|
180
|
+
return options.isEmpty();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Service Analysis
|
|
3
|
+
* Single Responsibility: Analyze query complexity and metadata
|
|
4
|
+
*
|
|
5
|
+
* Provides analysis functionality for query options.
|
|
6
|
+
* Extracts field information and calculates complexity scores.
|
|
7
|
+
*
|
|
8
|
+
* Max lines: 150 (enforced for maintainability)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { QueryOptions } from '../value-objects/QueryOptions';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract field names from query options
|
|
15
|
+
*/
|
|
16
|
+
export function extractFields(options: QueryOptions): string[] {
|
|
17
|
+
const fields = new Set<string>();
|
|
18
|
+
|
|
19
|
+
for (const clause of options.whereClauses) {
|
|
20
|
+
fields.add(clause.field);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const sort of options.sortOptions) {
|
|
24
|
+
fields.add(sort.field);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (options.dateRange) {
|
|
28
|
+
fields.add(options.dateRange.field);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Array.from(fields);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if query uses array operators
|
|
36
|
+
*/
|
|
37
|
+
export function hasArrayOperators(options: QueryOptions): boolean {
|
|
38
|
+
return options.whereClauses.some(clause => clause.isArrayOperator());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if query uses membership operators
|
|
43
|
+
*/
|
|
44
|
+
export function hasMembershipOperators(options: QueryOptions): boolean {
|
|
45
|
+
return options.whereClauses.some(clause => clause.isMembership());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get query complexity score
|
|
50
|
+
* Higher score = more complex query
|
|
51
|
+
*/
|
|
52
|
+
export function getComplexityScore(options: QueryOptions): number {
|
|
53
|
+
let score = 0;
|
|
54
|
+
|
|
55
|
+
score += options.whereClauses.length * 1;
|
|
56
|
+
score += options.sortOptions.length * 2;
|
|
57
|
+
score += options.dateRange ? 3 : 0;
|
|
58
|
+
score += options.pagination?.limit ? 1 : 0;
|
|
59
|
+
|
|
60
|
+
// Array and membership operators add complexity
|
|
61
|
+
if (hasArrayOperators(options)) score += 5;
|
|
62
|
+
if (hasMembershipOperators(options)) score += 5;
|
|
63
|
+
|
|
64
|
+
return score;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if query is too complex
|
|
69
|
+
*/
|
|
70
|
+
export function isTooComplex(options: QueryOptions, maxScore: number = 20): boolean {
|
|
71
|
+
return getComplexityScore(options) > maxScore;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get query statistics
|
|
76
|
+
*/
|
|
77
|
+
export function getQueryStats(options: QueryOptions): {
|
|
78
|
+
readonly whereClauseCount: number;
|
|
79
|
+
readonly sortOptionCount: number;
|
|
80
|
+
readonly hasDateRange: boolean;
|
|
81
|
+
readonly hasPagination: boolean;
|
|
82
|
+
readonly complexityScore: number;
|
|
83
|
+
readonly fieldCount: number;
|
|
84
|
+
} {
|
|
85
|
+
return {
|
|
86
|
+
whereClauseCount: options.whereClauses.length,
|
|
87
|
+
sortOptionCount: options.sortOptions.length,
|
|
88
|
+
hasDateRange: options.hasDateRange(),
|
|
89
|
+
hasPagination: options.hasPagination(),
|
|
90
|
+
complexityScore: getComplexityScore(options),
|
|
91
|
+
fieldCount: extractFields(options).length,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if query is simple (low complexity)
|
|
97
|
+
*/
|
|
98
|
+
export function isSimpleQuery(options: QueryOptions, maxScore: number = 5): boolean {
|
|
99
|
+
return getComplexityScore(options) <= maxScore;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if query is moderate complexity
|
|
104
|
+
*/
|
|
105
|
+
export function isModerateQuery(options: QueryOptions): boolean {
|
|
106
|
+
const score = getComplexityScore(options);
|
|
107
|
+
return score > 5 && score <= 15;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if query is high complexity
|
|
112
|
+
*/
|
|
113
|
+
export function isComplexQuery(options: QueryOptions): boolean {
|
|
114
|
+
return getComplexityScore(options) > 15;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get query complexity level
|
|
119
|
+
*/
|
|
120
|
+
export function getComplexityLevel(options: QueryOptions): 'simple' | 'moderate' | 'complex' {
|
|
121
|
+
if (isSimpleQuery(options)) {
|
|
122
|
+
return 'simple';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isModerateQuery(options)) {
|
|
126
|
+
return 'moderate';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return 'complex';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Compare two queries by complexity
|
|
134
|
+
*/
|
|
135
|
+
export function compareComplexity(options1: QueryOptions, options2: QueryOptions): number {
|
|
136
|
+
const score1 = getComplexityScore(options1);
|
|
137
|
+
const score2 = getComplexityScore(options2);
|
|
138
|
+
|
|
139
|
+
return score1 - score2;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get query performance hints
|
|
144
|
+
*/
|
|
145
|
+
export function getPerformanceHints(options: QueryOptions): string[] {
|
|
146
|
+
const hints: string[] = [];
|
|
147
|
+
|
|
148
|
+
if (hasArrayOperators(options)) {
|
|
149
|
+
hints.push('Array operators can impact performance');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (hasMembershipOperators(options)) {
|
|
153
|
+
hints.push('Membership operators (in, not-in) are limited to 10 values');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (options.whereClauses.length > 5) {
|
|
157
|
+
hints.push('Consider reducing number of where clauses');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!options.pagination?.limit && options.sortOptions.length > 0) {
|
|
161
|
+
hints.push('Add limit to prevent full collection scans');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (options.dateRange && !options.sortOptions.some(s => s.field === options.dateRange?.field)) {
|
|
165
|
+
hints.push('Add sort on date range field for better performance');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return hints;
|
|
169
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Service Analysis
|
|
3
|
+
* Single Responsibility: Analyze query complexity and metadata
|
|
4
|
+
*
|
|
5
|
+
* Provides analysis functionality for query options.
|
|
6
|
+
* Extracts field information and calculates complexity scores.
|
|
7
|
+
*
|
|
8
|
+
* Max lines: 150 (enforced for maintainability)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { QueryOptions } from '../value-objects/QueryOptions';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract field names from query options
|
|
15
|
+
*/
|
|
16
|
+
export function extractFields(options: QueryOptions): string[] {
|
|
17
|
+
const fields = new Set<string>();
|
|
18
|
+
|
|
19
|
+
for (const clause of options.whereClauses) {
|
|
20
|
+
fields.add(clause.field);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const sort of options.sortOptions) {
|
|
24
|
+
fields.add(sort.field);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (options.dateRange) {
|
|
28
|
+
fields.add(options.dateRange.field);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Array.from(fields);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if query uses array operators
|
|
36
|
+
*/
|
|
37
|
+
export function hasArrayOperators(options: QueryOptions): boolean {
|
|
38
|
+
return options.whereClauses.some(clause => clause.isArrayOperator());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if query uses membership operators
|
|
43
|
+
*/
|
|
44
|
+
export function hasMembershipOperators(options: QueryOptions): boolean {
|
|
45
|
+
return options.whereClauses.some(clause => clause.isMembership());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get query complexity score
|
|
50
|
+
* Higher score = more complex query
|
|
51
|
+
*/
|
|
52
|
+
export function getComplexityScore(options: QueryOptions): number {
|
|
53
|
+
let score = 0;
|
|
54
|
+
|
|
55
|
+
score += options.whereClauses.length * 1;
|
|
56
|
+
score += options.sortOptions.length * 2;
|
|
57
|
+
score += options.dateRange ? 3 : 0;
|
|
58
|
+
score += options.pagination?.limit ? 1 : 0;
|
|
59
|
+
|
|
60
|
+
// Array and membership operators add complexity
|
|
61
|
+
if (hasArrayOperators(options)) score += 5;
|
|
62
|
+
if (hasMembershipOperators(options)) score += 5;
|
|
63
|
+
|
|
64
|
+
return score;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if query is too complex
|
|
69
|
+
*/
|
|
70
|
+
export function isTooComplex(options: QueryOptions, maxScore: number = 20): boolean {
|
|
71
|
+
return getComplexityScore(options) > maxScore;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get query statistics
|
|
76
|
+
*/
|
|
77
|
+
export function getQueryStats(options: QueryOptions): {
|
|
78
|
+
readonly whereClauseCount: number;
|
|
79
|
+
readonly sortOptionCount: number;
|
|
80
|
+
readonly hasDateRange: boolean;
|
|
81
|
+
readonly hasPagination: boolean;
|
|
82
|
+
readonly complexityScore: number;
|
|
83
|
+
readonly fieldCount: number;
|
|
84
|
+
} {
|
|
85
|
+
return {
|
|
86
|
+
whereClauseCount: options.whereClauses.length,
|
|
87
|
+
sortOptionCount: options.sortOptions.length,
|
|
88
|
+
hasDateRange: options.hasDateRange(),
|
|
89
|
+
hasPagination: options.hasPagination(),
|
|
90
|
+
complexityScore: getComplexityScore(options),
|
|
91
|
+
fieldCount: extractFields(options).length,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if query is simple (low complexity)
|
|
97
|
+
*/
|
|
98
|
+
export function isSimpleQuery(options: QueryOptions, maxScore: number = 5): boolean {
|
|
99
|
+
return getComplexityScore(options) <= maxScore;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if query is moderate complexity
|
|
104
|
+
*/
|
|
105
|
+
export function isModerateQuery(options: QueryOptions): boolean {
|
|
106
|
+
const score = getComplexityScore(options);
|
|
107
|
+
return score > 5 && score <= 15;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if query is high complexity
|
|
112
|
+
*/
|
|
113
|
+
export function isComplexQuery(options: QueryOptions): boolean {
|
|
114
|
+
return getComplexityScore(options) > 15;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get query complexity level
|
|
119
|
+
*/
|
|
120
|
+
export function getComplexityLevel(options: QueryOptions): 'simple' | 'moderate' | 'complex' {
|
|
121
|
+
if (isSimpleQuery(options)) {
|
|
122
|
+
return 'simple';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isModerateQuery(options)) {
|
|
126
|
+
return 'moderate';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return 'complex';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Compare two queries by complexity
|
|
134
|
+
*/
|
|
135
|
+
export function compareComplexity(options1: QueryOptions, options2: QueryOptions): number {
|
|
136
|
+
const score1 = getComplexityScore(options1);
|
|
137
|
+
const score2 = getComplexityScore(options2);
|
|
138
|
+
|
|
139
|
+
return score1 - score2;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get query performance hints
|
|
144
|
+
*/
|
|
145
|
+
export function getPerformanceHints(options: QueryOptions): string[] {
|
|
146
|
+
const hints: string[] = [];
|
|
147
|
+
|
|
148
|
+
if (hasArrayOperators(options)) {
|
|
149
|
+
hints.push('Array operators can impact performance');
|
|
150
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
if (hasMembershipOperators(options)) {
|
|
3
|
+
hints.push('Membership operators (in, not-in) are limited to 10 values');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (options.whereClauses.length > 5) {
|
|
7
|
+
hints.push('Consider reducing number of where clauses');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!options.pagination?.limit && options.sortOptions.length > 0) {
|
|
11
|
+
hints.push('Add limit to prevent full collection scans');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (options.dateRange && !options.sortOptions.some(s => s.field === options.dateRange?.field)) {
|
|
15
|
+
hints.push('Add sort on date range field for better performance');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return hints;
|
|
19
|
+
}
|