@umituz/react-native-firebase 2.4.97 → 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.
- package/package.json +1 -1
- package/src/domains/auth/domain/value-objects/FirebaseAuthConfig.ts +1 -1
- package/src/domains/auth/infrastructure/config/initializers/FirebaseAuthInitializer.ts +5 -41
- package/src/domains/firestore/index.ts +0 -58
- package/src/domains/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +4 -255
- package/src/domains/firestore/infrastructure/repositories/BasePaginatedRepository.ts +1 -7
- package/src/domains/firestore/infrastructure/repositories/BaseQueryRepository.ts +8 -34
- package/src/domains/firestore/infrastructure/repositories/BaseRepository.ts +9 -48
- package/src/domains/firestore/presentation/hooks/index.ts +0 -10
- package/src/index.ts +1 -1
- package/src/shared/infrastructure/config/clients/FirebaseClientSingleton.ts +1 -1
- package/src/shared/infrastructure/config/services/FirebaseInitializationService.ts +1 -1
- package/src/shared/infrastructure/config/state/FirebaseClientState.ts +1 -1
- package/src/domains/firestore/domain/constants/QuotaLimits.ts +0 -101
- package/src/domains/firestore/domain/entities/QuotaMetrics.ts +0 -26
- package/src/domains/firestore/domain/entities/RequestLog.ts +0 -28
- package/src/domains/firestore/domain/services/QuotaCalculator.ts +0 -71
- package/src/domains/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +0 -312
- package/src/domains/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +0 -95
- package/src/domains/firestore/infrastructure/services/RequestLoggerService.ts +0 -165
- package/src/domains/firestore/presentation/hooks/useSmartFirestoreSnapshot.ts +0 -361
- package/src/domains/firestore/presentation/query-keys/createFirestoreKeys.ts +0 -32
- package/src/domains/firestore/presentation/query-keys/index.ts +0 -1
- package/src/domains/firestore/utils/deduplication/pending-query-manager.util.ts +0 -119
- package/src/domains/firestore/utils/deduplication/query-key-generator.util.ts +0 -34
- package/src/domains/firestore/utils/deduplication/timer-manager.util.ts +0 -83
- package/src/shared/infrastructure/config/initializers/FirebaseAppInitializer.ts +0 -9
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quota Limit Constants
|
|
3
|
-
* Domain layer - Default Firestore quota limits
|
|
4
|
-
*
|
|
5
|
-
* General-purpose constants for any app using Firestore
|
|
6
|
-
* Based on Firestore free tier and pricing documentation
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { calculatePercentage, calculateRemaining } from '../../../../shared/domain/utils/calculation.util';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Firestore free tier daily limits
|
|
13
|
-
* https://firebase.google.com/docs/firestore/quotas
|
|
14
|
-
*/
|
|
15
|
-
export const FREE_TIER_LIMITS = {
|
|
16
|
-
/**
|
|
17
|
-
* Daily read operations (documents)
|
|
18
|
-
* Free tier: 50,000 reads/day
|
|
19
|
-
*/
|
|
20
|
-
DAILY_READS: 50_000,
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Daily write operations (documents)
|
|
24
|
-
* Free tier: 20,000 writes/day
|
|
25
|
-
*/
|
|
26
|
-
DAILY_WRITES: 20_000,
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Daily delete operations (documents)
|
|
30
|
-
* Free tier: 20,000 deletes/day
|
|
31
|
-
*/
|
|
32
|
-
DAILY_DELETES: 20_000,
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Stored data (GB)
|
|
36
|
-
* Free tier: 1 GB
|
|
37
|
-
*/
|
|
38
|
-
STORAGE_GB: 1,
|
|
39
|
-
} as const;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Quota warning thresholds (percentage of limit)
|
|
43
|
-
* Apps can use these to show warnings before hitting limits
|
|
44
|
-
*/
|
|
45
|
-
export const QUOTA_THRESHOLDS = {
|
|
46
|
-
/**
|
|
47
|
-
* Warning threshold (80% of limit)
|
|
48
|
-
* Show warning to user
|
|
49
|
-
*/
|
|
50
|
-
WARNING: 0.8,
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Critical threshold (95% of limit)
|
|
54
|
-
* Show critical alert, consider disabling features
|
|
55
|
-
*/
|
|
56
|
-
CRITICAL: 0.95,
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Emergency threshold (98% of limit)
|
|
60
|
-
* Disable non-essential features
|
|
61
|
-
*/
|
|
62
|
-
EMERGENCY: 0.98,
|
|
63
|
-
} as const;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Calculate quota usage percentage
|
|
67
|
-
* Optimized: Uses centralized calculation utility
|
|
68
|
-
* @param current - Current usage count
|
|
69
|
-
* @param limit - Total limit
|
|
70
|
-
* @returns Percentage (0-1)
|
|
71
|
-
*/
|
|
72
|
-
export function calculateQuotaUsage(current: number, limit: number): number {
|
|
73
|
-
return calculatePercentage(current, limit);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Check if quota threshold is reached
|
|
78
|
-
* @param current - Current usage count
|
|
79
|
-
* @param limit - Total limit
|
|
80
|
-
* @param threshold - Threshold to check (0-1)
|
|
81
|
-
* @returns true if threshold is reached
|
|
82
|
-
*/
|
|
83
|
-
export function isQuotaThresholdReached(
|
|
84
|
-
current: number,
|
|
85
|
-
limit: number,
|
|
86
|
-
threshold: number,
|
|
87
|
-
): boolean {
|
|
88
|
-
const usage = calculateQuotaUsage(current, limit);
|
|
89
|
-
return usage >= threshold;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Get remaining quota
|
|
94
|
-
* Optimized: Uses centralized calculation utility
|
|
95
|
-
* @param current - Current usage count
|
|
96
|
-
* @param limit - Total limit
|
|
97
|
-
* @returns Remaining quota count
|
|
98
|
-
*/
|
|
99
|
-
export function getRemainingQuota(current: number, limit: number): number {
|
|
100
|
-
return calculateRemaining(current, limit);
|
|
101
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quota Metrics Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface QuotaMetrics {
|
|
6
|
-
readCount: number;
|
|
7
|
-
writeCount: number;
|
|
8
|
-
deleteCount: number;
|
|
9
|
-
lastResetDate: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface QuotaLimits {
|
|
13
|
-
dailyReadLimit: number;
|
|
14
|
-
dailyWriteLimit: number;
|
|
15
|
-
dailyDeleteLimit: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface QuotaStatus {
|
|
19
|
-
metrics: QuotaMetrics;
|
|
20
|
-
limits: QuotaLimits;
|
|
21
|
-
readPercentage: number;
|
|
22
|
-
writePercentage: number;
|
|
23
|
-
deletePercentage: number;
|
|
24
|
-
isNearLimit: boolean;
|
|
25
|
-
isOverLimit: boolean;
|
|
26
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Request Log Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export type RequestType = 'read' | 'write' | 'delete' | 'listener';
|
|
6
|
-
|
|
7
|
-
export interface RequestLog {
|
|
8
|
-
id: string;
|
|
9
|
-
type: RequestType;
|
|
10
|
-
collection: string;
|
|
11
|
-
documentId?: string;
|
|
12
|
-
cached: boolean;
|
|
13
|
-
success: boolean;
|
|
14
|
-
error?: string;
|
|
15
|
-
duration?: number;
|
|
16
|
-
timestamp: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface RequestStats {
|
|
20
|
-
totalRequests: number;
|
|
21
|
-
readRequests: number;
|
|
22
|
-
writeRequests: number;
|
|
23
|
-
deleteRequests: number;
|
|
24
|
-
listenerRequests: number;
|
|
25
|
-
cachedRequests: number;
|
|
26
|
-
failedRequests: number;
|
|
27
|
-
averageDuration: number;
|
|
28
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quota Calculator Service
|
|
3
|
-
* Domain service for calculating quota usage and status
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { QuotaMetrics, QuotaLimits, QuotaStatus } from '../entities/QuotaMetrics';
|
|
7
|
-
import { FREE_TIER_LIMITS, QUOTA_THRESHOLDS, calculateQuotaUsage } from '../constants/QuotaLimits';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Default quota limits (Firebase Spark Plan)
|
|
11
|
-
* Can be overridden via configuration
|
|
12
|
-
*/
|
|
13
|
-
const DEFAULT_QUOTA_LIMITS: QuotaLimits = {
|
|
14
|
-
dailyReadLimit: FREE_TIER_LIMITS.DAILY_READS,
|
|
15
|
-
dailyWriteLimit: FREE_TIER_LIMITS.DAILY_WRITES,
|
|
16
|
-
dailyDeleteLimit: FREE_TIER_LIMITS.DAILY_DELETES,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export class QuotaCalculator {
|
|
20
|
-
/**
|
|
21
|
-
* Calculate quota status from metrics and limits
|
|
22
|
-
*/
|
|
23
|
-
static calculateStatus(
|
|
24
|
-
metrics: QuotaMetrics,
|
|
25
|
-
limits: QuotaLimits = DEFAULT_QUOTA_LIMITS,
|
|
26
|
-
): QuotaStatus {
|
|
27
|
-
const readPercentage = calculateQuotaUsage(metrics.readCount, limits.dailyReadLimit) * 100;
|
|
28
|
-
const writePercentage = calculateQuotaUsage(metrics.writeCount, limits.dailyWriteLimit) * 100;
|
|
29
|
-
const deletePercentage = calculateQuotaUsage(metrics.deleteCount, limits.dailyDeleteLimit) * 100;
|
|
30
|
-
|
|
31
|
-
const warningThreshold = QUOTA_THRESHOLDS.WARNING * 100;
|
|
32
|
-
const isNearLimit =
|
|
33
|
-
readPercentage >= warningThreshold ||
|
|
34
|
-
writePercentage >= warningThreshold ||
|
|
35
|
-
deletePercentage >= warningThreshold;
|
|
36
|
-
|
|
37
|
-
const isOverLimit =
|
|
38
|
-
readPercentage >= 100 ||
|
|
39
|
-
writePercentage >= 100 ||
|
|
40
|
-
deletePercentage >= 100;
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
metrics,
|
|
44
|
-
limits,
|
|
45
|
-
readPercentage,
|
|
46
|
-
writePercentage,
|
|
47
|
-
deletePercentage,
|
|
48
|
-
isNearLimit,
|
|
49
|
-
isOverLimit,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get default quota limits
|
|
55
|
-
*/
|
|
56
|
-
static getDefaultLimits(): QuotaLimits {
|
|
57
|
-
return { ...DEFAULT_QUOTA_LIMITS };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Check if metrics are within limits
|
|
62
|
-
*/
|
|
63
|
-
static isWithinLimits(
|
|
64
|
-
metrics: QuotaMetrics,
|
|
65
|
-
limits: QuotaLimits = DEFAULT_QUOTA_LIMITS,
|
|
66
|
-
): boolean {
|
|
67
|
-
const status = this.calculateStatus(metrics, limits);
|
|
68
|
-
return !status.isOverLimit;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
@@ -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();
|