@umituz/react-native-firebase 2.4.100 → 2.5.1

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.
@@ -1,312 +0,0 @@
1
- /**
2
- * Query Deduplication Middleware (Enhanced)
3
- *
4
- * Prevents duplicate Firestore queries within a configurable time window
5
- * with quota-aware adaptive deduplication
6
- *
7
- * FEATURES:
8
- * - Configurable deduplication window (default: 10s, was 1s)
9
- * - Quota-aware adaptive window adjustment
10
- * - Statistics and monitoring
11
- * - Memory leak prevention
12
- * - Automatic cleanup optimization
13
- *
14
- * COST SAVINGS: ~90% reduction in duplicate query reads
15
- */
16
-
17
- import type { QueryKey } from '../../utils/deduplication/query-key-generator.util';
18
- import { generateQueryKey } from '../../utils/deduplication/query-key-generator.util';
19
- import { PendingQueryManager } from '../../utils/deduplication/pending-query-manager.util';
20
- import { TimerManager } from '../../utils/deduplication/timer-manager.util';
21
-
22
- /**
23
- * Default configuration
24
- * Optimized for cost savings while maintaining freshness
25
- */
26
- const DEFAULT_DEDUPLICATION_WINDOW_MS = 10000; // 10s (was 1s)
27
- const DEFAULT_CLEANUP_INTERVAL_MS = 15000; // 15s (was 3s)
28
-
29
- /**
30
- * Quota-based window adjustment thresholds
31
- */
32
- const QUOTA_THRESHOLDS = {
33
- HIGH_USAGE: 0.80, // 80% - extend window to 60s (1 min)
34
- MEDIUM_USAGE: 0.60, // 60% - extend window to 20s
35
- NORMAL: 0.50, // < 50% - use default 10s
36
- } as const;
37
-
38
- /**
39
- * Deduplication statistics
40
- */
41
- export interface DeduplicationStatistics {
42
- /** Total queries processed */
43
- totalQueries: number;
44
- /** Queries served from cache (deduplicated) */
45
- cachedQueries: number;
46
- /** Queries executed (not cached) */
47
- executedQueries: number;
48
- /** Current deduplication window in ms */
49
- currentWindowMs: number;
50
- /** Cache hit rate (0-1) */
51
- cacheHitRate: number;
52
- /** Memory usage (number of cached queries) */
53
- pendingQueries: number;
54
- }
55
-
56
- /**
57
- * Configuration options for deduplication middleware
58
- */
59
- export interface QueryDeduplicationConfig {
60
- /** Base deduplication window in ms (default: 10000) */
61
- baseWindowMs?: number;
62
- /** Cleanup interval in ms (default: 15000) */
63
- cleanupIntervalMs?: number;
64
- /** Enable quota-aware adaptive window (default: true) */
65
- quotaAware?: boolean;
66
- /** Maximum window size in ms (default: 60000 = 1 minute) */
67
- maxWindowMs?: number;
68
- /** Minimum window size in ms (default: 1000 = 1 second) */
69
- minWindowMs?: number;
70
- }
71
-
72
- /**
73
- * Enhanced Query Deduplication Middleware
74
- * Prevents duplicate queries with adaptive quota-aware behavior
75
- */
76
- export class QueryDeduplicationMiddleware {
77
- private readonly queryManager: PendingQueryManager;
78
- private readonly timerManager: TimerManager;
79
- private readonly baseWindowMs: number;
80
- private readonly maxWindowMs: number;
81
- private readonly minWindowMs: number;
82
- private readonly quotaAware: boolean;
83
- private destroyed = false;
84
-
85
- // Statistics tracking
86
- private stats: DeduplicationStatistics = {
87
- totalQueries: 0,
88
- cachedQueries: 0,
89
- executedQueries: 0,
90
- currentWindowMs: DEFAULT_DEDUPLICATION_WINDOW_MS,
91
- cacheHitRate: 0,
92
- pendingQueries: 0,
93
- };
94
-
95
- constructor(config: QueryDeduplicationConfig = {}) {
96
- this.baseWindowMs = config.baseWindowMs ?? DEFAULT_DEDUPLICATION_WINDOW_MS;
97
- this.maxWindowMs = config.maxWindowMs ?? 60000; // 1 minute max
98
- this.minWindowMs = config.minWindowMs ?? 1000; // 1 second min
99
- this.quotaAware = config.quotaAware ?? true;
100
-
101
- const cleanupIntervalMs = config.cleanupIntervalMs ?? DEFAULT_CLEANUP_INTERVAL_MS;
102
-
103
- this.queryManager = new PendingQueryManager(this.baseWindowMs);
104
- this.timerManager = new TimerManager({
105
- cleanupIntervalMs,
106
- onCleanup: () => {
107
- if (!this.destroyed) {
108
- this.queryManager.cleanup();
109
- this.updateStats();
110
- }
111
- },
112
- });
113
- this.timerManager.start();
114
- }
115
-
116
- /**
117
- * Execute query with deduplication
118
- * Returns cached result if available within window, otherwise executes
119
- */
120
- async deduplicate<T>(
121
- queryKey: QueryKey,
122
- queryFn: () => Promise<T>,
123
- ): Promise<T> {
124
- if (this.destroyed) {
125
- // If middleware is destroyed, execute query directly without deduplication
126
- return queryFn();
127
- }
128
-
129
- this.stats.totalQueries++;
130
- const key = generateQueryKey(queryKey);
131
-
132
- // Check for existing promise (atomic get-or-create pattern)
133
- const existingPromise = this.queryManager.get(key);
134
- if (existingPromise) {
135
- this.stats.cachedQueries++;
136
- this.updateCacheHitRate();
137
- return existingPromise as Promise<T>;
138
- }
139
-
140
- // Create promise with cleanup on completion
141
- this.stats.executedQueries++;
142
- const promise = (async () => {
143
- try {
144
- return await queryFn();
145
- } finally {
146
- // Immediate cleanup after completion (success or error)
147
- this.queryManager.remove(key);
148
- this.stats.pendingQueries = this.queryManager.size();
149
- }
150
- })();
151
-
152
- // Add before any await - this prevents race between check and add
153
- this.queryManager.add(key, promise);
154
- this.stats.pendingQueries = this.queryManager.size();
155
-
156
- return promise;
157
- }
158
-
159
- /**
160
- * Adjust deduplication window based on quota usage
161
- * Call this periodically with current quota percentage
162
- *
163
- * @param quotaPercentage - Current quota usage (0-1)
164
- *
165
- * @example
166
- * ```typescript
167
- * const quotaStatus = getQuotaStatus();
168
- * middleware.adjustWindowForQuota(quotaStatus.readPercentage / 100);
169
- * ```
170
- */
171
- adjustWindowForQuota(quotaPercentage: number): void {
172
- if (!this.quotaAware || this.destroyed) {
173
- return;
174
- }
175
-
176
- let newWindowMs: number;
177
-
178
- if (quotaPercentage >= QUOTA_THRESHOLDS.HIGH_USAGE) {
179
- // High usage: extend window to maximum (1 minute)
180
- newWindowMs = this.maxWindowMs;
181
- } else if (quotaPercentage >= QUOTA_THRESHOLDS.MEDIUM_USAGE) {
182
- // Medium usage: extend window to 20s
183
- newWindowMs = Math.min(20000, this.maxWindowMs);
184
- } else {
185
- // Normal usage: use base window (10s)
186
- newWindowMs = this.baseWindowMs;
187
- }
188
-
189
- // Clamp to min/max bounds
190
- newWindowMs = Math.max(this.minWindowMs, Math.min(newWindowMs, this.maxWindowMs));
191
-
192
- // Only update if changed
193
- if (newWindowMs !== this.stats.currentWindowMs) {
194
- this.queryManager.setWindow(newWindowMs);
195
- this.stats.currentWindowMs = newWindowMs;
196
-
197
- if (__DEV__) {
198
- console.log(
199
- `[Deduplication] Adjusted window to ${newWindowMs}ms ` +
200
- `(quota: ${(quotaPercentage * 100).toFixed(1)}%)`
201
- );
202
- }
203
- }
204
- }
205
-
206
- /**
207
- * Get current deduplication statistics
208
- */
209
- getStatistics(): DeduplicationStatistics {
210
- return { ...this.stats };
211
- }
212
-
213
- /**
214
- * Reset statistics
215
- */
216
- resetStatistics(): void {
217
- this.stats = {
218
- totalQueries: 0,
219
- cachedQueries: 0,
220
- executedQueries: 0,
221
- currentWindowMs: this.stats.currentWindowMs,
222
- cacheHitRate: 0,
223
- pendingQueries: this.queryManager.size(),
224
- };
225
- }
226
-
227
- /**
228
- * Update cache hit rate
229
- */
230
- private updateCacheHitRate(): void {
231
- this.stats.cacheHitRate =
232
- this.stats.totalQueries > 0
233
- ? this.stats.cachedQueries / this.stats.totalQueries
234
- : 0;
235
- }
236
-
237
- /**
238
- * Update statistics
239
- */
240
- private updateStats(): void {
241
- this.stats.pendingQueries = this.queryManager.size();
242
- this.updateCacheHitRate();
243
- }
244
-
245
- /**
246
- * Clear all cached queries
247
- */
248
- clear(): void {
249
- this.queryManager.clear();
250
- this.stats.pendingQueries = 0;
251
- }
252
-
253
- /**
254
- * Destroy middleware and cleanup resources
255
- */
256
- destroy(): void {
257
- this.destroyed = true;
258
- this.timerManager.destroy();
259
- this.queryManager.clear();
260
- this.stats.pendingQueries = 0;
261
- }
262
-
263
- /**
264
- * Get number of pending queries
265
- */
266
- getPendingCount(): number {
267
- return this.queryManager.size();
268
- }
269
- }
270
-
271
- /**
272
- * Default singleton instance with recommended settings
273
- */
274
- export const queryDeduplicationMiddleware = new QueryDeduplicationMiddleware({
275
- baseWindowMs: DEFAULT_DEDUPLICATION_WINDOW_MS,
276
- cleanupIntervalMs: DEFAULT_CLEANUP_INTERVAL_MS,
277
- quotaAware: true,
278
- maxWindowMs: 60000, // 1 minute
279
- minWindowMs: 1000, // 1 second
280
- });
281
-
282
- /**
283
- * Helper function to integrate deduplication with quota tracking
284
- * Automatically adjusts window based on quota usage
285
- *
286
- * Note: This is NOT a React hook, but a helper function.
287
- * Call this from your own hook or effect as needed.
288
- *
289
- * @example
290
- * ```typescript
291
- * // In your own hook or component:
292
- * useEffect(() => {
293
- * syncDeduplicationWithQuota(queryDeduplicationMiddleware, quotaMiddleware, quotaLimits);
294
- * }, [quotaMiddleware.getCounts().reads]);
295
- * ```
296
- */
297
- export function syncDeduplicationWithQuota(
298
- deduplication: QueryDeduplicationMiddleware,
299
- quotaMiddleware: { getCounts: () => { reads: number; writes: number; deletes: number } },
300
- quotaLimits: { dailyReadLimit: number }
301
- ): void {
302
- // Adjust deduplication window based on quota
303
- const counts = quotaMiddleware.getCounts();
304
- const quotaPercentage = counts.reads / quotaLimits.dailyReadLimit;
305
- deduplication.adjustWindowForQuota(quotaPercentage);
306
- }
307
-
308
- /**
309
- * @deprecated Use syncDeduplicationWithQuota instead (not a hook)
310
- * This will be removed in a future version
311
- */
312
- export const useDeduplicationWithQuota = syncDeduplicationWithQuota;
@@ -1,95 +0,0 @@
1
- /**
2
- * Quota Tracking Middleware
3
- * Tracks Firestore operations for quota monitoring
4
- */
5
-
6
- interface OperationInfo {
7
- type: 'read' | 'write' | 'delete';
8
- collection: string;
9
- count: number;
10
- cached: boolean;
11
- }
12
-
13
- export class QuotaTrackingMiddleware {
14
- private readCount = 0;
15
- private writeCount = 0;
16
- private deleteCount = 0;
17
-
18
- /**
19
- * Track a Firestore operation
20
- */
21
- async trackOperation<T>(
22
- info: OperationInfo,
23
- operation: () => Promise<T>
24
- ): Promise<T> {
25
- try {
26
- return await operation();
27
- } finally {
28
- switch (info.type) {
29
- case 'read':
30
- if (!info.cached) {
31
- this.readCount += info.count;
32
- }
33
- break;
34
- case 'write':
35
- this.writeCount += info.count;
36
- break;
37
- case 'delete':
38
- this.deleteCount += info.count;
39
- break;
40
- }
41
- }
42
- }
43
-
44
- /**
45
- * Track read operation
46
- * @param _collection - Collection name (reserved for future per-collection tracking)
47
- * @param count - Number of documents read
48
- * @param cached - Whether result was from cache
49
- */
50
- trackRead(_collection: string, count: number = 1, cached: boolean = false): void {
51
- if (!cached) {
52
- this.readCount += count;
53
- }
54
- }
55
-
56
- /**
57
- * Track write operation
58
- * @param _collection - Collection name (reserved for future per-collection tracking)
59
- * @param count - Number of documents written
60
- */
61
- trackWrite(_collection: string, count: number = 1): void {
62
- this.writeCount += count;
63
- }
64
-
65
- /**
66
- * Track delete operation
67
- * @param _collection - Collection name (reserved for future per-collection tracking)
68
- * @param count - Number of documents deleted
69
- */
70
- trackDelete(_collection: string, count: number = 1): void {
71
- this.deleteCount += count;
72
- }
73
-
74
- /**
75
- * Get current counts
76
- */
77
- getCounts(): { reads: number; writes: number; deletes: number } {
78
- return {
79
- reads: this.readCount,
80
- writes: this.writeCount,
81
- deletes: this.deleteCount,
82
- };
83
- }
84
-
85
- /**
86
- * Reset counts
87
- */
88
- reset(): void {
89
- this.readCount = 0;
90
- this.writeCount = 0;
91
- this.deleteCount = 0;
92
- }
93
- }
94
-
95
- export const quotaTrackingMiddleware = new QuotaTrackingMiddleware();
@@ -1,165 +0,0 @@
1
- /**
2
- * Request Logger Service
3
- * Infrastructure service for logging Firestore requests
4
- */
5
-
6
- import type { RequestLog, RequestStats, RequestType } from '../../domain/entities/RequestLog';
7
- import { generateUUID } from '@umituz/react-native-design-system/uuid';
8
-
9
- /**
10
- * Maximum number of logs to keep in memory
11
- * Prevents unbounded memory growth
12
- */
13
- export const DEFAULT_MAX_LOGS = 1000;
14
-
15
- export class RequestLoggerService {
16
- private logs: RequestLog[] = [];
17
- private readonly maxLogs: number;
18
- private listeners: Set<(log: RequestLog) => void> = new Set();
19
- private static readonly LISTENER_ERROR_PREFIX = '[RequestLoggerService] Listener error:';
20
-
21
- constructor(maxLogs: number = DEFAULT_MAX_LOGS) {
22
- this.maxLogs = maxLogs;
23
- }
24
-
25
- /**
26
- * Log a request
27
- */
28
- logRequest(log: Omit<RequestLog, 'id' | 'timestamp'>): void {
29
- const fullLog: RequestLog = {
30
- ...log,
31
- id: generateUUID(),
32
- timestamp: Date.now(),
33
- };
34
-
35
- this.logs.push(fullLog);
36
-
37
- if (this.logs.length > this.maxLogs) {
38
- this.logs.shift();
39
- }
40
-
41
- this.notifyListeners(fullLog);
42
- }
43
-
44
- /**
45
- * Get all logs
46
- */
47
- getLogs(): RequestLog[] {
48
- return [...this.logs];
49
- }
50
-
51
- /**
52
- * Get logs by type
53
- * Optimized: Return empty array early if no logs
54
- */
55
- getLogsByType(type: RequestType): RequestLog[] {
56
- if (this.logs.length === 0) return [];
57
- return this.logs.filter((log) => log.type === type);
58
- }
59
-
60
- /**
61
- * Get request statistics
62
- * Optimized: Single-pass calculation O(n) instead of O(7n)
63
- */
64
- getStats(): RequestStats {
65
- let readRequests = 0;
66
- let writeRequests = 0;
67
- let deleteRequests = 0;
68
- let listenerRequests = 0;
69
- let cachedRequests = 0;
70
- let failedRequests = 0;
71
- let durationSum = 0;
72
- let durationCount = 0;
73
-
74
- // Single pass through logs for all statistics
75
- for (const log of this.logs) {
76
- switch (log.type) {
77
- case 'read':
78
- readRequests++;
79
- break;
80
- case 'write':
81
- writeRequests++;
82
- break;
83
- case 'delete':
84
- deleteRequests++;
85
- break;
86
- case 'listener':
87
- listenerRequests++;
88
- break;
89
- }
90
-
91
- if (log.cached) cachedRequests++;
92
- if (!log.success) failedRequests++;
93
-
94
- if (log.duration !== undefined) {
95
- durationSum += log.duration;
96
- durationCount++;
97
- }
98
- }
99
-
100
- return {
101
- totalRequests: this.logs.length,
102
- readRequests,
103
- writeRequests,
104
- deleteRequests,
105
- listenerRequests,
106
- cachedRequests,
107
- failedRequests,
108
- averageDuration: durationCount > 0 ? durationSum / durationCount : 0,
109
- };
110
- }
111
-
112
- /**
113
- * Clear all logs
114
- */
115
- clearLogs(): void {
116
- this.logs = [];
117
- }
118
-
119
- /**
120
- * Add log listener
121
- */
122
- addListener(listener: (log: RequestLog) => void): () => void {
123
- this.listeners.add(listener);
124
- return () => {
125
- this.listeners.delete(listener);
126
- };
127
- }
128
-
129
- /**
130
- * Remove all listeners
131
- * Prevents memory leaks when service is destroyed
132
- */
133
- removeAllListeners(): void {
134
- this.listeners.clear();
135
- }
136
-
137
- /**
138
- * Destroy service and cleanup resources
139
- */
140
- destroy(): void {
141
- this.removeAllListeners();
142
- this.clearLogs();
143
- }
144
-
145
- /**
146
- * Notify all listeners
147
- */
148
- private notifyListeners(log: RequestLog): void {
149
- this.listeners.forEach((listener) => {
150
- try {
151
- listener(log);
152
- } catch (error) {
153
- // Log listener errors in development to help debugging
154
- // In production, silently ignore to prevent crashing the app
155
- if (__DEV__) {
156
- const errorMessage = error instanceof Error ? error.message : String(error);
157
- console.warn(`${RequestLoggerService.LISTENER_ERROR_PREFIX} ${errorMessage}`);
158
- }
159
- }
160
- });
161
- }
162
- }
163
-
164
- export const requestLoggerService = new RequestLoggerService();
165
-