@umituz/react-native-firebase 1.13.3 → 1.13.4
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 +7 -2
- package/src/firestore/__tests__/BaseRepository.test.ts +133 -0
- package/src/firestore/__tests__/QueryDeduplicationMiddleware.test.ts +147 -0
- package/src/firestore/__tests__/mocks/react-native-firebase.ts +23 -0
- package/src/firestore/__tests__/setup.ts +36 -0
- package/src/firestore/domain/constants/QuotaLimits.ts +97 -0
- package/src/firestore/domain/entities/QuotaMetrics.ts +28 -0
- package/src/firestore/domain/entities/RequestLog.ts +30 -0
- package/src/firestore/domain/errors/FirebaseFirestoreError.ts +52 -0
- package/src/firestore/domain/services/QuotaCalculator.ts +70 -0
- package/src/firestore/index.ts +174 -0
- package/src/firestore/infrastructure/config/FirestoreClient.ts +181 -0
- package/src/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +46 -0
- package/src/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +153 -0
- package/src/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +165 -0
- package/src/firestore/infrastructure/repositories/BasePaginatedRepository.ts +90 -0
- package/src/firestore/infrastructure/repositories/BaseQueryRepository.ts +80 -0
- package/src/firestore/infrastructure/repositories/BaseRepository.ts +147 -0
- package/src/firestore/infrastructure/services/QuotaMonitorService.ts +108 -0
- package/src/firestore/infrastructure/services/RequestLoggerService.ts +139 -0
- package/src/firestore/types/pagination.types.ts +60 -0
- package/src/firestore/utils/dateUtils.ts +31 -0
- package/src/firestore/utils/document-mapper.helper.ts +145 -0
- package/src/firestore/utils/pagination.helper.ts +93 -0
- package/src/firestore/utils/query-builder.ts +188 -0
- package/src/firestore/utils/quota-error-detector.util.ts +100 -0
- package/src/index.ts +8 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Mapper Helper
|
|
3
|
+
*
|
|
4
|
+
* Utilities for batch document processing with enrichment.
|
|
5
|
+
* Handles document extraction, validation, and enrichment with related data.
|
|
6
|
+
*
|
|
7
|
+
* App-agnostic: Works with any document type and any enrichment logic.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { DocumentMapperHelper } from '@umituz/react-native-firestore';
|
|
12
|
+
*
|
|
13
|
+
* const mapper = new DocumentMapperHelper<Post, User, EnrichedPost>();
|
|
14
|
+
* const enriched = await mapper.mapWithEnrichment(
|
|
15
|
+
* postDocs,
|
|
16
|
+
* post => extractPost(post),
|
|
17
|
+
* post => post.userId,
|
|
18
|
+
* userId => userRepo.getById(userId),
|
|
19
|
+
* (post, user) => ({ ...post, user })
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { QueryDocumentSnapshot, DocumentData } from 'firebase/firestore';
|
|
25
|
+
|
|
26
|
+
export class DocumentMapperHelper<TSource, TEnrichment, TResult> {
|
|
27
|
+
/**
|
|
28
|
+
* Map documents with enrichment from related data
|
|
29
|
+
*
|
|
30
|
+
* Process flow:
|
|
31
|
+
* 1. Extract source data from document
|
|
32
|
+
* 2. Skip if extraction fails or source is invalid
|
|
33
|
+
* 3. Get enrichment key from source
|
|
34
|
+
* 4. Fetch enrichment data using the key
|
|
35
|
+
* 5. Skip if enrichment data not found
|
|
36
|
+
* 6. Combine source and enrichment into result
|
|
37
|
+
*
|
|
38
|
+
* @param docs - Firestore document snapshots
|
|
39
|
+
* @param extractSource - Extract source data from document
|
|
40
|
+
* @param getEnrichmentKey - Get enrichment key from source
|
|
41
|
+
* @param fetchEnrichment - Fetch enrichment data by key
|
|
42
|
+
* @param combineData - Combine source and enrichment into result
|
|
43
|
+
* @returns Array of enriched results
|
|
44
|
+
*/
|
|
45
|
+
async mapWithEnrichment(
|
|
46
|
+
docs: QueryDocumentSnapshot<DocumentData>[],
|
|
47
|
+
extractSource: (doc: QueryDocumentSnapshot<DocumentData>) => TSource | null,
|
|
48
|
+
getEnrichmentKey: (source: TSource) => string,
|
|
49
|
+
fetchEnrichment: (key: string) => Promise<TEnrichment | null>,
|
|
50
|
+
combineData: (source: TSource, enrichment: TEnrichment) => TResult,
|
|
51
|
+
): Promise<TResult[]> {
|
|
52
|
+
const results: TResult[] = [];
|
|
53
|
+
|
|
54
|
+
for (const doc of docs) {
|
|
55
|
+
const source = extractSource(doc);
|
|
56
|
+
if (!source) continue;
|
|
57
|
+
|
|
58
|
+
const enrichmentKey = getEnrichmentKey(source);
|
|
59
|
+
const enrichment = await fetchEnrichment(enrichmentKey);
|
|
60
|
+
if (!enrichment) continue;
|
|
61
|
+
|
|
62
|
+
results.push(combineData(source, enrichment));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Map documents with multiple enrichments
|
|
70
|
+
*
|
|
71
|
+
* Similar to mapWithEnrichment but supports multiple enrichment sources.
|
|
72
|
+
* Useful when result needs data from multiple related collections.
|
|
73
|
+
*
|
|
74
|
+
* @param docs - Firestore document snapshots
|
|
75
|
+
* @param extractSource - Extract source data from document
|
|
76
|
+
* @param getEnrichmentKeys - Get all enrichment keys from source
|
|
77
|
+
* @param fetchEnrichments - Fetch all enrichment data by keys
|
|
78
|
+
* @param combineData - Combine source and enrichments into result
|
|
79
|
+
* @returns Array of enriched results
|
|
80
|
+
*/
|
|
81
|
+
async mapWithMultipleEnrichments<TEnrichments extends Record<string, unknown>>(
|
|
82
|
+
docs: QueryDocumentSnapshot<DocumentData>[],
|
|
83
|
+
extractSource: (doc: QueryDocumentSnapshot<DocumentData>) => TSource | null,
|
|
84
|
+
getEnrichmentKeys: (source: TSource) => Record<string, string>,
|
|
85
|
+
fetchEnrichments: (keys: Record<string, string>) => Promise<TEnrichments | null>,
|
|
86
|
+
combineData: (source: TSource, enrichments: TEnrichments) => TResult,
|
|
87
|
+
): Promise<TResult[]> {
|
|
88
|
+
const results: TResult[] = [];
|
|
89
|
+
|
|
90
|
+
for (const doc of docs) {
|
|
91
|
+
const source = extractSource(doc);
|
|
92
|
+
if (!source) continue;
|
|
93
|
+
|
|
94
|
+
const enrichmentKeys = getEnrichmentKeys(source);
|
|
95
|
+
const enrichments = await fetchEnrichments(enrichmentKeys);
|
|
96
|
+
if (!enrichments) continue;
|
|
97
|
+
|
|
98
|
+
results.push(combineData(source, enrichments));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Simple document mapping without enrichment
|
|
106
|
+
*
|
|
107
|
+
* @param docs - Firestore document snapshots
|
|
108
|
+
* @param extractData - Extract data from document
|
|
109
|
+
* @returns Array of extracted data (nulls filtered out)
|
|
110
|
+
*/
|
|
111
|
+
map(
|
|
112
|
+
docs: QueryDocumentSnapshot<DocumentData>[],
|
|
113
|
+
extractData: (doc: QueryDocumentSnapshot<DocumentData>) => TResult | null,
|
|
114
|
+
): TResult[] {
|
|
115
|
+
const results: TResult[] = [];
|
|
116
|
+
|
|
117
|
+
for (const doc of docs) {
|
|
118
|
+
const data = extractData(doc);
|
|
119
|
+
if (data) {
|
|
120
|
+
results.push(data);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create document mapper helper
|
|
130
|
+
*
|
|
131
|
+
* @returns DocumentMapperHelper instance
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* const mapper = createDocumentMapper<Post, User, EnrichedPost>();
|
|
136
|
+
* const enriched = await mapper.mapWithEnrichment(...);
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export function createDocumentMapper<
|
|
140
|
+
TSource,
|
|
141
|
+
TEnrichment,
|
|
142
|
+
TResult,
|
|
143
|
+
>(): DocumentMapperHelper<TSource, TEnrichment, TResult> {
|
|
144
|
+
return new DocumentMapperHelper<TSource, TEnrichment, TResult>();
|
|
145
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination Helper
|
|
3
|
+
*
|
|
4
|
+
* Utilities for cursor-based pagination in Firestore.
|
|
5
|
+
* Handles pagination logic, cursor management, and hasMore detection.
|
|
6
|
+
*
|
|
7
|
+
* App-agnostic: Works with any document type and any collection.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { PaginationHelper } from '@umituz/react-native-firestore';
|
|
12
|
+
*
|
|
13
|
+
* const helper = new PaginationHelper<Post>();
|
|
14
|
+
* const result = helper.buildResult(posts, 10, post => post.id);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { PaginatedResult, PaginationParams } from '../types/pagination.types';
|
|
19
|
+
|
|
20
|
+
export class PaginationHelper<T> {
|
|
21
|
+
/**
|
|
22
|
+
* Build paginated result from items
|
|
23
|
+
*
|
|
24
|
+
* @param items - All items fetched (should be limit + 1)
|
|
25
|
+
* @param pageLimit - Requested page size
|
|
26
|
+
* @param getCursor - Function to extract cursor from item
|
|
27
|
+
* @returns Paginated result with hasMore and nextCursor
|
|
28
|
+
*/
|
|
29
|
+
buildResult(
|
|
30
|
+
items: T[],
|
|
31
|
+
pageLimit: number,
|
|
32
|
+
getCursor: (item: T) => string,
|
|
33
|
+
): PaginatedResult<T> {
|
|
34
|
+
const hasMore = items.length > pageLimit;
|
|
35
|
+
const resultItems = hasMore ? items.slice(0, pageLimit) : items;
|
|
36
|
+
const lastItem = resultItems[resultItems.length - 1];
|
|
37
|
+
const nextCursor = hasMore && lastItem
|
|
38
|
+
? getCursor(lastItem)
|
|
39
|
+
: null;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
items: resultItems,
|
|
43
|
+
nextCursor,
|
|
44
|
+
hasMore,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get default limit from params or use default
|
|
50
|
+
*
|
|
51
|
+
* @param params - Pagination params
|
|
52
|
+
* @param defaultLimit - Default limit if not specified
|
|
53
|
+
* @returns Page limit
|
|
54
|
+
*/
|
|
55
|
+
getLimit(params?: PaginationParams, defaultLimit: number = 10): number {
|
|
56
|
+
return params?.limit || defaultLimit;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Calculate fetch limit (page limit + 1 for hasMore detection)
|
|
61
|
+
*
|
|
62
|
+
* @param pageLimit - Requested page size
|
|
63
|
+
* @returns Fetch limit (pageLimit + 1)
|
|
64
|
+
*/
|
|
65
|
+
getFetchLimit(pageLimit: number): number {
|
|
66
|
+
return pageLimit + 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if params has cursor
|
|
71
|
+
*
|
|
72
|
+
* @param params - Pagination params
|
|
73
|
+
* @returns true if cursor exists
|
|
74
|
+
*/
|
|
75
|
+
hasCursor(params?: PaginationParams): boolean {
|
|
76
|
+
return !!params?.cursor;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create pagination helper for a specific type
|
|
82
|
+
*
|
|
83
|
+
* @returns PaginationHelper instance
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const helper = createPaginationHelper<Post>();
|
|
88
|
+
* const result = helper.buildResult(posts, 10, post => post.id);
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function createPaginationHelper<T>(): PaginationHelper<T> {
|
|
92
|
+
return new PaginationHelper<T>();
|
|
93
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Builder Utility
|
|
3
|
+
* Single Responsibility: Build Firestore queries with advanced filtering
|
|
4
|
+
*
|
|
5
|
+
* App-agnostic utility for building Firestore queries.
|
|
6
|
+
* Supports:
|
|
7
|
+
* - Firestore 'in' operator (up to 10 values)
|
|
8
|
+
* - Firestore 'or' operator (for >10 values via chunking)
|
|
9
|
+
* - Single value filtering
|
|
10
|
+
* - Multiple field filtering
|
|
11
|
+
* - Date range filtering
|
|
12
|
+
* - Sorting
|
|
13
|
+
* - Limiting
|
|
14
|
+
*
|
|
15
|
+
* This utility is designed to be used across hundreds of apps.
|
|
16
|
+
* It provides a consistent interface for Firestore query building.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
collection,
|
|
21
|
+
query,
|
|
22
|
+
where,
|
|
23
|
+
orderBy,
|
|
24
|
+
limit as limitQuery,
|
|
25
|
+
startAfter,
|
|
26
|
+
or,
|
|
27
|
+
type Firestore,
|
|
28
|
+
type Query,
|
|
29
|
+
Timestamp,
|
|
30
|
+
type WhereFilterOp,
|
|
31
|
+
} from "firebase/firestore";
|
|
32
|
+
|
|
33
|
+
export interface FieldFilter {
|
|
34
|
+
field: string;
|
|
35
|
+
operator: WhereFilterOp;
|
|
36
|
+
value: string | number | boolean | string[] | number[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface QueryBuilderOptions {
|
|
40
|
+
collectionName: string;
|
|
41
|
+
baseFilters?: FieldFilter[];
|
|
42
|
+
dateRange?: {
|
|
43
|
+
field: string;
|
|
44
|
+
startDate?: number;
|
|
45
|
+
endDate?: number;
|
|
46
|
+
};
|
|
47
|
+
sort?: {
|
|
48
|
+
field: string;
|
|
49
|
+
order?: "asc" | "desc";
|
|
50
|
+
};
|
|
51
|
+
limitValue?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Cursor value for pagination (timestamp in milliseconds)
|
|
54
|
+
* Used with startAfter for cursor-based pagination
|
|
55
|
+
*/
|
|
56
|
+
cursorValue?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const MAX_IN_OPERATOR_VALUES = 10;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build Firestore query with advanced filtering support
|
|
63
|
+
*
|
|
64
|
+
* @param db - Firestore database instance
|
|
65
|
+
* @param options - Query builder options
|
|
66
|
+
* @returns Firestore Query object
|
|
67
|
+
*/
|
|
68
|
+
export function buildQuery(
|
|
69
|
+
db: Firestore,
|
|
70
|
+
options: QueryBuilderOptions,
|
|
71
|
+
): Query {
|
|
72
|
+
const {
|
|
73
|
+
collectionName,
|
|
74
|
+
baseFilters = [],
|
|
75
|
+
dateRange,
|
|
76
|
+
sort,
|
|
77
|
+
limitValue,
|
|
78
|
+
cursorValue,
|
|
79
|
+
} = options;
|
|
80
|
+
|
|
81
|
+
const collectionRef = collection(db, collectionName);
|
|
82
|
+
let q: Query = collectionRef;
|
|
83
|
+
|
|
84
|
+
// Apply base filters
|
|
85
|
+
for (const filter of baseFilters) {
|
|
86
|
+
q = applyFieldFilter(q, filter);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Apply date range filters
|
|
90
|
+
if (dateRange) {
|
|
91
|
+
if (dateRange.startDate) {
|
|
92
|
+
q = query(
|
|
93
|
+
q,
|
|
94
|
+
where(
|
|
95
|
+
dateRange.field,
|
|
96
|
+
">=",
|
|
97
|
+
Timestamp.fromMillis(dateRange.startDate),
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (dateRange.endDate) {
|
|
102
|
+
q = query(
|
|
103
|
+
q,
|
|
104
|
+
where(
|
|
105
|
+
dateRange.field,
|
|
106
|
+
"<=",
|
|
107
|
+
Timestamp.fromMillis(dateRange.endDate),
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Apply sorting
|
|
114
|
+
if (sort) {
|
|
115
|
+
const sortOrder = sort.order || "desc";
|
|
116
|
+
q = query(q, orderBy(sort.field, sortOrder));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Apply cursor for pagination (must come after orderBy)
|
|
120
|
+
if (cursorValue !== undefined) {
|
|
121
|
+
q = query(q, startAfter(Timestamp.fromMillis(cursorValue)));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Apply limit
|
|
125
|
+
if (limitValue !== undefined) {
|
|
126
|
+
q = query(q, limitQuery(limitValue));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return q;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Apply field filter with support for 'in' operator and chunking
|
|
134
|
+
* Handles arrays by using 'in' operator (up to 10 values)
|
|
135
|
+
* For arrays >10 values, splits into chunks and uses 'or' operator
|
|
136
|
+
*/
|
|
137
|
+
function applyFieldFilter(q: Query, filter: FieldFilter): Query {
|
|
138
|
+
const { field, operator, value } = filter;
|
|
139
|
+
|
|
140
|
+
// Handle 'in' operator with array values
|
|
141
|
+
if (operator === "in" && Array.isArray(value)) {
|
|
142
|
+
// Firestore 'in' operator supports up to 10 values
|
|
143
|
+
if (value.length <= MAX_IN_OPERATOR_VALUES) {
|
|
144
|
+
return query(q, where(field, "in", value));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Split into chunks of 10 and use 'or' operator
|
|
148
|
+
const chunks: (string[] | number[])[] = [];
|
|
149
|
+
for (let i = 0; i < value.length; i += MAX_IN_OPERATOR_VALUES) {
|
|
150
|
+
chunks.push(value.slice(i, i + MAX_IN_OPERATOR_VALUES));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const orConditions = chunks.map((chunk) => where(field, "in", chunk));
|
|
154
|
+
return query(q, or(...orConditions));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Standard filter
|
|
158
|
+
return query(q, where(field, operator, value));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Helper: Create a field filter for 'in' operator
|
|
163
|
+
* Automatically handles chunking if array >10 values
|
|
164
|
+
*/
|
|
165
|
+
export function createInFilter(
|
|
166
|
+
field: string,
|
|
167
|
+
values: string[] | number[],
|
|
168
|
+
): FieldFilter {
|
|
169
|
+
return {
|
|
170
|
+
field,
|
|
171
|
+
operator: "in",
|
|
172
|
+
value: values,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Helper: Create a field filter for equality
|
|
178
|
+
*/
|
|
179
|
+
export function createEqualFilter(
|
|
180
|
+
field: string,
|
|
181
|
+
value: string | number | boolean,
|
|
182
|
+
): FieldFilter {
|
|
183
|
+
return {
|
|
184
|
+
field,
|
|
185
|
+
operator: "==",
|
|
186
|
+
value,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Error Detector Utility
|
|
3
|
+
* Single Responsibility: Detect Firebase quota errors
|
|
4
|
+
*
|
|
5
|
+
* Firebase quota limits:
|
|
6
|
+
* - Free tier: 50K reads/day, 20K writes/day, 20K deletes/day
|
|
7
|
+
* - Blaze plan: Pay as you go, higher limits
|
|
8
|
+
*
|
|
9
|
+
* Quota errors are NOT retryable - quota won't increase by retrying
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if error is a Firebase quota error
|
|
14
|
+
* Quota errors indicate daily read/write/delete limits are exceeded
|
|
15
|
+
*
|
|
16
|
+
* @param error - Error object to check
|
|
17
|
+
* @returns true if error is a quota error
|
|
18
|
+
*/
|
|
19
|
+
export function isQuotaError(error: unknown): boolean {
|
|
20
|
+
if (!error || typeof error !== "object") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const firebaseError = error as { code?: string; message?: string; name?: string };
|
|
25
|
+
|
|
26
|
+
// Check error code
|
|
27
|
+
if (firebaseError.code === "resource-exhausted") {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check error message
|
|
32
|
+
const errorMessage = firebaseError.message?.toLowerCase() || "";
|
|
33
|
+
if (
|
|
34
|
+
errorMessage.includes("quota") ||
|
|
35
|
+
errorMessage.includes("quota exceeded") ||
|
|
36
|
+
errorMessage.includes("resource-exhausted") ||
|
|
37
|
+
errorMessage.includes("daily limit")
|
|
38
|
+
) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check error name
|
|
43
|
+
const errorName = firebaseError.name?.toLowerCase() || "";
|
|
44
|
+
if (errorName.includes("quota") || errorName.includes("resource-exhausted")) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if error is retryable
|
|
53
|
+
* Quota errors are NOT retryable
|
|
54
|
+
*
|
|
55
|
+
* @param error - Error object to check
|
|
56
|
+
* @returns true if error is retryable
|
|
57
|
+
*/
|
|
58
|
+
export function isRetryableError(error: unknown): boolean {
|
|
59
|
+
// Quota errors are NOT retryable
|
|
60
|
+
if (isQuotaError(error)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!error || typeof error !== "object") {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const firebaseError = error as { code?: string; message?: string };
|
|
69
|
+
|
|
70
|
+
// Firestore transaction conflicts are retryable
|
|
71
|
+
if (firebaseError.code === "failed-precondition") {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Network errors are retryable
|
|
76
|
+
if (
|
|
77
|
+
firebaseError.code === "unavailable" ||
|
|
78
|
+
firebaseError.code === "deadline-exceeded"
|
|
79
|
+
) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Timeout errors are retryable
|
|
84
|
+
const errorMessage = firebaseError.message?.toLowerCase() || "";
|
|
85
|
+
if (errorMessage.includes("timeout")) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get user-friendly quota error message
|
|
94
|
+
*
|
|
95
|
+
* @returns User-friendly error message
|
|
96
|
+
*/
|
|
97
|
+
export function getQuotaErrorMessage(): string {
|
|
98
|
+
return "Firebase quota exceeded. Please try again later or upgrade your Firebase plan.";
|
|
99
|
+
}
|
|
100
|
+
|
package/src/index.ts
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
*
|
|
6
6
|
* This package provides Firebase App initialization and core services:
|
|
7
7
|
* - Auth
|
|
8
|
+
* - Firestore
|
|
8
9
|
* - Analytics
|
|
9
10
|
* - Crashlytics
|
|
10
11
|
*
|
|
11
12
|
* Usage:
|
|
12
13
|
* import { initializeFirebase, getFirebaseApp } from '@umituz/react-native-firebase';
|
|
13
14
|
* import { useFirebaseAuth } from '@umituz/react-native-firebase';
|
|
15
|
+
* import { getFirestore, BaseRepository } from '@umituz/react-native-firebase';
|
|
14
16
|
* import { firebaseAnalyticsService } from '@umituz/react-native-firebase';
|
|
15
17
|
*/
|
|
16
18
|
|
|
@@ -51,6 +53,12 @@ export type {
|
|
|
51
53
|
|
|
52
54
|
export * from './auth';
|
|
53
55
|
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// FIRESTORE MODULE
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
export * from './firestore';
|
|
61
|
+
|
|
54
62
|
// =============================================================================
|
|
55
63
|
// ANALYTICS MODULE
|
|
56
64
|
// =============================================================================
|