@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.
Files changed (27) hide show
  1. package/package.json +7 -2
  2. package/src/firestore/__tests__/BaseRepository.test.ts +133 -0
  3. package/src/firestore/__tests__/QueryDeduplicationMiddleware.test.ts +147 -0
  4. package/src/firestore/__tests__/mocks/react-native-firebase.ts +23 -0
  5. package/src/firestore/__tests__/setup.ts +36 -0
  6. package/src/firestore/domain/constants/QuotaLimits.ts +97 -0
  7. package/src/firestore/domain/entities/QuotaMetrics.ts +28 -0
  8. package/src/firestore/domain/entities/RequestLog.ts +30 -0
  9. package/src/firestore/domain/errors/FirebaseFirestoreError.ts +52 -0
  10. package/src/firestore/domain/services/QuotaCalculator.ts +70 -0
  11. package/src/firestore/index.ts +174 -0
  12. package/src/firestore/infrastructure/config/FirestoreClient.ts +181 -0
  13. package/src/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +46 -0
  14. package/src/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +153 -0
  15. package/src/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +165 -0
  16. package/src/firestore/infrastructure/repositories/BasePaginatedRepository.ts +90 -0
  17. package/src/firestore/infrastructure/repositories/BaseQueryRepository.ts +80 -0
  18. package/src/firestore/infrastructure/repositories/BaseRepository.ts +147 -0
  19. package/src/firestore/infrastructure/services/QuotaMonitorService.ts +108 -0
  20. package/src/firestore/infrastructure/services/RequestLoggerService.ts +139 -0
  21. package/src/firestore/types/pagination.types.ts +60 -0
  22. package/src/firestore/utils/dateUtils.ts +31 -0
  23. package/src/firestore/utils/document-mapper.helper.ts +145 -0
  24. package/src/firestore/utils/pagination.helper.ts +93 -0
  25. package/src/firestore/utils/query-builder.ts +188 -0
  26. package/src/firestore/utils/quota-error-detector.util.ts +100 -0
  27. package/src/index.ts +8 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * React Native Firestore Module
3
+ * Domain-Driven Design (DDD) Architecture
4
+ *
5
+ * This is the SINGLE SOURCE OF TRUTH for all Firestore operations.
6
+ * ALL imports from the Firestore module MUST go through this file.
7
+ *
8
+ * Architecture:
9
+ * - domain: Errors, Constants, Entities, Services
10
+ * - infrastructure: Firestore client, BaseRepository, utilities
11
+ * - utils: Date utilities, timestamp conversion, query builders
12
+ *
13
+ * This module is designed to be used across hundreds of apps.
14
+ * It provides a consistent interface for Firestore operations.
15
+ */
16
+
17
+ // =============================================================================
18
+ // DOMAIN LAYER - Errors
19
+ // =============================================================================
20
+
21
+ export {
22
+ FirebaseFirestoreError,
23
+ FirebaseFirestoreInitializationError,
24
+ FirebaseFirestoreQuotaError,
25
+ } from './domain/errors/FirebaseFirestoreError';
26
+
27
+ // =============================================================================
28
+ // INFRASTRUCTURE LAYER - Firestore Client
29
+ // =============================================================================
30
+
31
+ export {
32
+ initializeFirestore,
33
+ getFirestore,
34
+ isFirestoreInitialized,
35
+ getFirestoreInitializationError,
36
+ resetFirestoreClient,
37
+ firestoreClient,
38
+ } from './infrastructure/config/FirestoreClient';
39
+
40
+ export type { Firestore } from './infrastructure/config/FirestoreClient';
41
+
42
+ // =============================================================================
43
+ // INFRASTRUCTURE LAYER - BaseRepository
44
+ // =============================================================================
45
+
46
+ export { BaseRepository } from './infrastructure/repositories/BaseRepository';
47
+ export { BaseQueryRepository } from './infrastructure/repositories/BaseQueryRepository';
48
+ export { BasePaginatedRepository } from './infrastructure/repositories/BasePaginatedRepository';
49
+
50
+ // =============================================================================
51
+ // UTILS - Date Utilities
52
+ // =============================================================================
53
+
54
+ export {
55
+ isoToTimestamp,
56
+ timestampToISO,
57
+ timestampToDate,
58
+ getCurrentISOString,
59
+ } from './utils/dateUtils';
60
+
61
+ // =============================================================================
62
+ // UTILS - Query Builder
63
+ // =============================================================================
64
+
65
+ export {
66
+ buildQuery,
67
+ createInFilter,
68
+ createEqualFilter,
69
+ } from './utils/query-builder';
70
+
71
+ export type {
72
+ QueryBuilderOptions,
73
+ FieldFilter,
74
+ } from './utils/query-builder';
75
+
76
+ // =============================================================================
77
+ // UTILS - Pagination
78
+ // =============================================================================
79
+
80
+ export {
81
+ PaginationHelper,
82
+ createPaginationHelper,
83
+ } from './utils/pagination.helper';
84
+
85
+ export type {
86
+ PaginatedResult,
87
+ PaginationParams,
88
+ } from './types/pagination.types';
89
+
90
+ export { EMPTY_PAGINATED_RESULT } from './types/pagination.types';
91
+
92
+ // =============================================================================
93
+ // UTILS - Document Mapper
94
+ // =============================================================================
95
+
96
+ export {
97
+ DocumentMapperHelper,
98
+ createDocumentMapper,
99
+ } from './utils/document-mapper.helper';
100
+
101
+ // =============================================================================
102
+ // UTILS - Quota Error Detection
103
+ // =============================================================================
104
+
105
+ export {
106
+ isQuotaError,
107
+ isRetryableError,
108
+ getQuotaErrorMessage,
109
+ } from './utils/quota-error-detector.util';
110
+
111
+ // =============================================================================
112
+ // DOMAIN LAYER - Constants
113
+ // =============================================================================
114
+
115
+ export {
116
+ FREE_TIER_LIMITS,
117
+ QUOTA_THRESHOLDS,
118
+ calculateQuotaUsage,
119
+ isQuotaThresholdReached,
120
+ getRemainingQuota,
121
+ } from './domain/constants/QuotaLimits';
122
+
123
+ // =============================================================================
124
+ // DOMAIN LAYER - Entities
125
+ // =============================================================================
126
+
127
+ export type {
128
+ QuotaMetrics,
129
+ QuotaLimits,
130
+ QuotaStatus,
131
+ } from './domain/entities/QuotaMetrics';
132
+
133
+ export type {
134
+ RequestLog,
135
+ RequestStats,
136
+ RequestType,
137
+ } from './domain/entities/RequestLog';
138
+
139
+ // =============================================================================
140
+ // DOMAIN LAYER - Services
141
+ // =============================================================================
142
+
143
+ export { QuotaCalculator } from './domain/services/QuotaCalculator';
144
+
145
+ // =============================================================================
146
+ // INFRASTRUCTURE LAYER - Middleware
147
+ // =============================================================================
148
+
149
+ export {
150
+ QueryDeduplicationMiddleware,
151
+ queryDeduplicationMiddleware,
152
+ } from './infrastructure/middleware/QueryDeduplicationMiddleware';
153
+
154
+ export {
155
+ QuotaTrackingMiddleware,
156
+ quotaTrackingMiddleware,
157
+ } from './infrastructure/middleware/QuotaTrackingMiddleware';
158
+
159
+ // =============================================================================
160
+ // INFRASTRUCTURE LAYER - Services
161
+ // =============================================================================
162
+
163
+ export {
164
+ QuotaMonitorService,
165
+ quotaMonitorService,
166
+ } from './infrastructure/services/QuotaMonitorService';
167
+
168
+ export {
169
+ RequestLoggerService,
170
+ requestLoggerService,
171
+ } from './infrastructure/services/RequestLoggerService';
172
+
173
+ // Re-export Firestore types for convenience
174
+ export type { Timestamp } from 'firebase/firestore';
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Firestore Client - Infrastructure Layer
3
+ *
4
+ * Domain-Driven Design: Infrastructure implementation of Firestore client
5
+ * Singleton pattern for managing Firestore instance
6
+ *
7
+ * IMPORTANT: This package requires Firebase App to be initialized first.
8
+ * Use @umituz/react-native-firebase to initialize Firebase App.
9
+ *
10
+ * SOLID Principles:
11
+ * - Single Responsibility: Only manages Firestore initialization
12
+ * - Open/Closed: Extensible through configuration, closed for modification
13
+ * - Dependency Inversion: Depends on Firebase App from @umituz/react-native-firebase
14
+ */
15
+
16
+ import type { Firestore } from 'firebase/firestore';
17
+ import { getFirebaseApp } from '../../../infrastructure/config/FirebaseClient';
18
+ import { FirebaseFirestoreInitializer } from './initializers/FirebaseFirestoreInitializer';
19
+
20
+ /**
21
+ * Firestore Client Singleton
22
+ * Manages Firestore initialization
23
+ */
24
+ class FirestoreClientSingleton {
25
+ private static instance: FirestoreClientSingleton | null = null;
26
+ private firestore: Firestore | null = null;
27
+ private initializationError: string | null = null;
28
+
29
+ private constructor() {
30
+ // Private constructor to enforce singleton pattern
31
+ }
32
+
33
+ /**
34
+ * Get singleton instance
35
+ */
36
+ static getInstance(): FirestoreClientSingleton {
37
+ if (!FirestoreClientSingleton.instance) {
38
+ FirestoreClientSingleton.instance = new FirestoreClientSingleton();
39
+ }
40
+ return FirestoreClientSingleton.instance;
41
+ }
42
+
43
+ /**
44
+ * Initialize Firestore
45
+ * Requires Firebase App to be initialized first via @umituz/react-native-firebase
46
+ *
47
+ * @returns Firestore instance or null if initialization fails
48
+ */
49
+ initialize(): Firestore | null {
50
+ if (this.firestore) {
51
+ return this.firestore;
52
+ }
53
+ if (this.initializationError) {
54
+ return null;
55
+ }
56
+ try {
57
+ const app = getFirebaseApp(); // Get the core Firebase App
58
+
59
+ // Return null if Firebase App is not available (offline mode)
60
+ if (!app) {
61
+ return null;
62
+ }
63
+
64
+ this.firestore = FirebaseFirestoreInitializer.initialize(app);
65
+ return this.firestore;
66
+ } catch (error) {
67
+ this.initializationError =
68
+ error instanceof Error
69
+ ? error.message
70
+ : 'Failed to initialize Firestore client';
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get Firestore instance
77
+ * Auto-initializes if Firebase App is available
78
+ * Returns null if config is not available (offline mode - no error)
79
+ * @returns Firestore instance or null if not initialized
80
+ */
81
+ getFirestore(): Firestore | null {
82
+ // Auto-initialize if not already initialized
83
+ if (!this.firestore && !this.initializationError) {
84
+ try {
85
+ // Try to get Firebase App (will auto-initialize if config is available)
86
+ const app = getFirebaseApp();
87
+ if (app) {
88
+ this.initialize();
89
+ }
90
+ } catch {
91
+ // Firebase App not available, return null (offline mode)
92
+ return null;
93
+ }
94
+ }
95
+
96
+ // Return null if not initialized (offline mode - no error)
97
+ return this.firestore || null;
98
+ }
99
+
100
+ /**
101
+ * Check if Firestore is initialized
102
+ */
103
+ isInitialized(): boolean {
104
+ return this.firestore !== null;
105
+ }
106
+
107
+ /**
108
+ * Get initialization error if any
109
+ */
110
+ getInitializationError(): string | null {
111
+ return this.initializationError;
112
+ }
113
+
114
+ /**
115
+ * Reset Firestore client instance
116
+ * Useful for testing
117
+ */
118
+ reset(): void {
119
+ this.firestore = null;
120
+ this.initializationError = null;
121
+ }
122
+ }
123
+
124
+ export const firestoreClient = FirestoreClientSingleton.getInstance();
125
+
126
+ /**
127
+ * Initialize Firestore
128
+ * Requires Firebase App to be initialized first via @umituz/react-native-firebase
129
+ *
130
+ * @returns Firestore instance or null if initialization fails
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * import { initializeFirebase } from '@umituz/react-native-firebase';
135
+ * import { initializeFirestore } from '@umituz/react-native-firestore';
136
+ *
137
+ * // Initialize Firebase App first
138
+ * const app = initializeFirebase(config);
139
+ *
140
+ * // Then initialize Firestore
141
+ * const db = initializeFirestore();
142
+ * ```
143
+ */
144
+ export function initializeFirestore(): Firestore | null {
145
+ return firestoreClient.initialize();
146
+ }
147
+
148
+ /**
149
+ * Get Firestore instance
150
+ * Auto-initializes if Firebase App is available
151
+ * Returns null if config is not available (offline mode - no error)
152
+ * @returns Firestore instance or null if not initialized
153
+ */
154
+ export function getFirestore(): Firestore | null {
155
+ return firestoreClient.getFirestore();
156
+ }
157
+
158
+ /**
159
+ * Check if Firestore is initialized
160
+ */
161
+ export function isFirestoreInitialized(): boolean {
162
+ return firestoreClient.isInitialized();
163
+ }
164
+
165
+ /**
166
+ * Get Firestore initialization error if any
167
+ */
168
+ export function getFirestoreInitializationError(): string | null {
169
+ return firestoreClient.getInitializationError();
170
+ }
171
+
172
+ /**
173
+ * Reset Firestore client instance
174
+ * Useful for testing
175
+ */
176
+ export function resetFirestoreClient(): void {
177
+ firestoreClient.reset();
178
+ }
179
+
180
+ export type { Firestore } from 'firebase/firestore';
181
+
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Firebase Firestore Initializer
3
+ *
4
+ * Single Responsibility: Initialize Firestore instance
5
+ *
6
+ * NOTE: React Native does not support IndexedDB (browser API), so we use
7
+ * memoryLocalCache instead of persistentLocalCache. For client-side caching,
8
+ * use TanStack Query which works on all platforms.
9
+ */
10
+
11
+ import {
12
+ getFirestore,
13
+ initializeFirestore,
14
+ memoryLocalCache,
15
+ } from 'firebase/firestore';
16
+ import type { Firestore } from 'firebase/firestore';
17
+ import type { FirebaseApp } from 'firebase/app';
18
+
19
+ /**
20
+ * Initializes Firestore
21
+ * Platform-agnostic: Works on all platforms (Web, iOS, Android)
22
+ */
23
+ export class FirebaseFirestoreInitializer {
24
+ /**
25
+ * Initialize Firestore with memory cache configuration
26
+ * React Native does not support IndexedDB, so we use memory cache
27
+ * For offline persistence, use TanStack Query with AsyncStorage
28
+ */
29
+ static initialize(app: FirebaseApp): Firestore {
30
+ try {
31
+ // Use memory cache for React Native compatibility
32
+ // IndexedDB (persistentLocalCache) is not available in React Native
33
+ return initializeFirestore(app, {
34
+ localCache: memoryLocalCache(),
35
+ });
36
+ } catch (error: any) {
37
+ // If already initialized, get existing instance
38
+ if (error.code === 'failed-precondition') {
39
+ return getFirestore(app);
40
+ }
41
+
42
+ return getFirestore(app);
43
+ }
44
+ }
45
+ }
46
+
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Query Deduplication Middleware
3
+ * Prevents duplicate Firestore queries within a short time window
4
+ */
5
+
6
+ interface PendingQuery {
7
+ promise: Promise<unknown>;
8
+ timestamp: number;
9
+ }
10
+
11
+ interface QueryKey {
12
+ collection: string;
13
+ filters: string;
14
+ limit?: number;
15
+ orderBy?: string;
16
+ }
17
+
18
+ export class QueryDeduplicationMiddleware {
19
+ private pendingQueries = new Map<string, PendingQuery>();
20
+ private readonly DEDUPLICATION_WINDOW_MS = 1000; // 1 second
21
+ private readonly CLEANUP_INTERVAL_MS = 5000; // 5 seconds
22
+ private cleanupTimer: NodeJS.Timeout | null = null;
23
+
24
+ constructor() {
25
+ this.startCleanupTimer();
26
+ }
27
+
28
+ /**
29
+ * Start cleanup timer to prevent memory leaks
30
+ */
31
+ private startCleanupTimer(): void {
32
+ if (this.cleanupTimer) {
33
+ clearInterval(this.cleanupTimer);
34
+ }
35
+
36
+ this.cleanupTimer = setInterval(() => {
37
+ this.cleanupExpiredQueries();
38
+ }, this.CLEANUP_INTERVAL_MS);
39
+ }
40
+
41
+ /**
42
+ * Clean up expired queries to prevent memory leaks
43
+ */
44
+ private cleanupExpiredQueries(): void {
45
+ const now = Date.now();
46
+ for (const [key, query] of this.pendingQueries.entries()) {
47
+ if (now - query.timestamp > this.DEDUPLICATION_WINDOW_MS) {
48
+ this.pendingQueries.delete(key);
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Generate query key from query parameters
55
+ */
56
+ private generateQueryKey(key: QueryKey): string {
57
+ const parts = [
58
+ key.collection,
59
+ key.filters,
60
+ key.limit?.toString() || '',
61
+ key.orderBy || '',
62
+ ];
63
+ return parts.join('|');
64
+ }
65
+
66
+ /**
67
+ * Check if query is already pending
68
+ */
69
+ private isQueryPending(key: string): boolean {
70
+ const pending = this.pendingQueries.get(key);
71
+ if (!pending) return false;
72
+
73
+ const age = Date.now() - pending.timestamp;
74
+ if (age > this.DEDUPLICATION_WINDOW_MS) {
75
+ this.pendingQueries.delete(key);
76
+ return false;
77
+ }
78
+
79
+ return true;
80
+ }
81
+
82
+ /**
83
+ * Get pending query promise
84
+ */
85
+ private getPendingQuery(key: string): Promise<unknown> | null {
86
+ const pending = this.pendingQueries.get(key);
87
+ return pending ? pending.promise : null;
88
+ }
89
+
90
+ /**
91
+ * Add query to pending list
92
+ */
93
+ private addPendingQuery(key: string, promise: Promise<unknown>): void {
94
+ this.pendingQueries.set(key, {
95
+ promise,
96
+ timestamp: Date.now(),
97
+ });
98
+
99
+ promise.finally(() => {
100
+ this.pendingQueries.delete(key);
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Deduplicate a query
106
+ */
107
+ async deduplicate<T>(
108
+ queryKey: QueryKey,
109
+ queryFn: () => Promise<T>,
110
+ ): Promise<T> {
111
+ const key = this.generateQueryKey(queryKey);
112
+
113
+ if (this.isQueryPending(key)) {
114
+ const pendingPromise = this.getPendingQuery(key);
115
+ if (pendingPromise) {
116
+ return pendingPromise as Promise<T>;
117
+ }
118
+ }
119
+
120
+ const promise = queryFn();
121
+ this.addPendingQuery(key, promise);
122
+
123
+ return promise;
124
+ }
125
+
126
+ /**
127
+ * Clear all pending queries
128
+ */
129
+ clear(): void {
130
+ this.pendingQueries.clear();
131
+ }
132
+
133
+ /**
134
+ * Destroy middleware and cleanup resources
135
+ */
136
+ destroy(): void {
137
+ if (this.cleanupTimer) {
138
+ clearInterval(this.cleanupTimer);
139
+ this.cleanupTimer = null;
140
+ }
141
+ this.pendingQueries.clear();
142
+ }
143
+
144
+ /**
145
+ * Get pending queries count
146
+ */
147
+ getPendingCount(): number {
148
+ return this.pendingQueries.size;
149
+ }
150
+ }
151
+
152
+ export const queryDeduplicationMiddleware = new QueryDeduplicationMiddleware();
153
+
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Quota Tracking Middleware
3
+ * Tracks Firestore operations for quota monitoring
4
+ */
5
+
6
+ import { quotaMonitorService } from '../services/QuotaMonitorService';
7
+ import { requestLoggerService } from '../services/RequestLoggerService';
8
+ import type { RequestType } from '../../domain/entities/RequestLog';
9
+
10
+ interface TrackedOperation {
11
+ type: RequestType;
12
+ collection: string;
13
+ documentId?: string;
14
+ count: number;
15
+ cached?: boolean;
16
+ }
17
+
18
+ export class QuotaTrackingMiddleware {
19
+ /**
20
+ * Track a read operation
21
+ */
22
+ trackRead(collection: string, count: number = 1, cached: boolean = false): void {
23
+ quotaMonitorService.incrementRead(count);
24
+ requestLoggerService.logRequest({
25
+ type: 'read',
26
+ collection,
27
+ success: true,
28
+ cached,
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Track a write operation
34
+ */
35
+ trackWrite(
36
+ collection: string,
37
+ documentId?: string,
38
+ count: number = 1,
39
+ ): void {
40
+ quotaMonitorService.incrementWrite(count);
41
+ requestLoggerService.logRequest({
42
+ type: 'write',
43
+ collection,
44
+ documentId,
45
+ success: true,
46
+ cached: false,
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Track a delete operation
52
+ */
53
+ trackDelete(
54
+ collection: string,
55
+ documentId?: string,
56
+ count: number = 1,
57
+ ): void {
58
+ quotaMonitorService.incrementDelete(count);
59
+ requestLoggerService.logRequest({
60
+ type: 'delete',
61
+ collection,
62
+ documentId,
63
+ success: true,
64
+ cached: false,
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Track a listener operation
70
+ */
71
+ trackListener(collection: string, documentId?: string): void {
72
+ requestLoggerService.logRequest({
73
+ type: 'listener',
74
+ collection,
75
+ documentId,
76
+ success: true,
77
+ cached: false,
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Track a failed operation
83
+ */
84
+ trackError(
85
+ type: RequestType,
86
+ collection: string,
87
+ error: string,
88
+ documentId?: string,
89
+ ): void {
90
+ requestLoggerService.logRequest({
91
+ type,
92
+ collection,
93
+ documentId,
94
+ success: false,
95
+ error,
96
+ cached: false,
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Track operation with timing
102
+ */
103
+ async trackOperation<T>(
104
+ operation: TrackedOperation,
105
+ operationFn: () => Promise<T>,
106
+ ): Promise<T> {
107
+ const startTime = Date.now();
108
+
109
+ try {
110
+ const result = await operationFn();
111
+ const duration = Date.now() - startTime;
112
+
113
+ if (operation.type === 'read') {
114
+ quotaMonitorService.incrementRead(operation.count);
115
+ requestLoggerService.logRequest({
116
+ type: 'read',
117
+ collection: operation.collection,
118
+ documentId: operation.documentId,
119
+ success: true,
120
+ cached: operation.cached || false,
121
+ duration,
122
+ });
123
+ } else if (operation.type === 'write') {
124
+ quotaMonitorService.incrementWrite(operation.count);
125
+ requestLoggerService.logRequest({
126
+ type: 'write',
127
+ collection: operation.collection,
128
+ documentId: operation.documentId,
129
+ success: true,
130
+ cached: false,
131
+ duration,
132
+ });
133
+ } else if (operation.type === 'delete') {
134
+ quotaMonitorService.incrementDelete(operation.count);
135
+ requestLoggerService.logRequest({
136
+ type: 'delete',
137
+ collection: operation.collection,
138
+ documentId: operation.documentId,
139
+ success: true,
140
+ cached: false,
141
+ duration,
142
+ });
143
+ }
144
+
145
+ return result;
146
+ } catch (error) {
147
+ const duration = Date.now() - startTime;
148
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
149
+ requestLoggerService.logRequest({
150
+ type: operation.type,
151
+ collection: operation.collection,
152
+ documentId: operation.documentId,
153
+ success: false,
154
+ error: errorMessage,
155
+ cached: false,
156
+ duration,
157
+ });
158
+
159
+ throw error;
160
+ }
161
+ }
162
+ }
163
+
164
+ export const quotaTrackingMiddleware = new QuotaTrackingMiddleware();
165
+