@umituz/react-native-firebase 3.0.5 → 3.0.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-firebase",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -175,7 +175,16 @@ async function attemptReauth(user: User, options: AccountDeletionOptions, origin
175
175
  if (res.success) {
176
176
  try {
177
177
  const postReauthAuth = getFirebaseAuth();
178
- const currentUser = postReauthAuth?.currentUser || user;
178
+ // FIX: Explicit null check - don't fallback to user if auth state changed
179
+ if (!postReauthAuth?.currentUser) {
180
+ return {
181
+ success: false,
182
+ error: { code: 'auth/user-changed', message: 'User changed during reauthentication' },
183
+ requiresReauth: false
184
+ };
185
+ }
186
+
187
+ const currentUser = postReauthAuth.currentUser;
179
188
 
180
189
  if (originalUserId) {
181
190
  const validationCheck = validateUserUnchanged(postReauthAuth, originalUserId);
@@ -82,7 +82,10 @@ export const useFirebaseAuthStore = createStore<AuthState, AuthActions>({
82
82
  unsubscribe = null;
83
83
  }
84
84
  setupInProgress = false;
85
- activeComponentCount--;
85
+ // Only decrement if we successfully incremented (wasn't already set up)
86
+ if (state.listenerSetup === false && !unsubscribe) {
87
+ activeComponentCount--;
88
+ }
86
89
  set({ listenerSetup: false, loading: false });
87
90
  throw error; // Re-throw to allow caller to handle
88
91
  }
@@ -87,9 +87,11 @@ export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
87
87
 
88
88
  try {
89
89
  // Listen to auth state changes
90
- unsubscribeRef.current = onAuthStateChanged(auth, (user) => {
90
+ // FIX: Capture return value for proper cleanup
91
+ const unsubscribe = onAuthStateChanged(auth, (user) => {
91
92
  handleAuthStateChange(user);
92
93
  });
94
+ unsubscribeRef.current = unsubscribe;
93
95
  } catch (err) {
94
96
  const authError = err instanceof Error ? err : new Error('Auth listener setup failed');
95
97
  setError(authError);
@@ -30,17 +30,11 @@ export function createAuthStateChangeHandler(
30
30
  const { setAuthState, setLoading, setError } = params;
31
31
 
32
32
  return (user: User | null) => {
33
- try {
34
- const authState = createAuthCheckResult(user);
35
- setAuthState(authState);
36
- setError(null);
37
- } catch (err) {
38
- const authError =
39
- err instanceof Error ? err : new Error('Auth state check failed');
40
- setError(authError);
41
- } finally {
42
- setLoading(false);
43
- }
33
+ // FIX: Removed unnecessary try-catch - createAuthCheckResult cannot throw
34
+ const authState = createAuthCheckResult(user);
35
+ setAuthState(authState);
36
+ setError(null);
37
+ setLoading(false);
44
38
  };
45
39
  }
46
40
 
@@ -89,7 +89,7 @@ export {
89
89
  isRetryableError,
90
90
  } from '../../shared/domain/utils/error-handlers/error-checkers';
91
91
  export {
92
- getQuotaErrorMessage,
92
+ ERROR_MESSAGES,
93
93
  } from '../../shared/domain/utils/error-handlers/error-messages';
94
94
 
95
95
  // Middleware
@@ -97,7 +97,6 @@ export {
97
97
  QueryDeduplicationMiddleware,
98
98
  queryDeduplicationMiddleware,
99
99
  syncDeduplicationWithQuota,
100
- useDeduplicationWithQuota,
101
100
  } from './infrastructure/middleware/QueryDeduplicationMiddleware';
102
101
  export type {
103
102
  QueryDeduplicationConfig,
@@ -304,9 +304,3 @@ export function syncDeduplicationWithQuota(
304
304
  const quotaPercentage = counts.reads / quotaLimits.dailyReadLimit;
305
305
  deduplication.adjustWindowForQuota(quotaPercentage);
306
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;
@@ -43,11 +43,10 @@ export class QuotaTrackingMiddleware {
43
43
 
44
44
  /**
45
45
  * Track read operation
46
- * @param _collection - Collection name (reserved for future per-collection tracking)
47
46
  * @param count - Number of documents read
48
47
  * @param cached - Whether result was from cache
49
48
  */
50
- trackRead(_collection: string, count: number = 1, cached: boolean = false): void {
49
+ trackRead(count: number = 1, cached: boolean = false): void {
51
50
  if (!cached) {
52
51
  this.readCount += count;
53
52
  }
@@ -55,19 +54,17 @@ export class QuotaTrackingMiddleware {
55
54
 
56
55
  /**
57
56
  * Track write operation
58
- * @param _collection - Collection name (reserved for future per-collection tracking)
59
57
  * @param count - Number of documents written
60
58
  */
61
- trackWrite(_collection: string, count: number = 1): void {
59
+ trackWrite(count: number = 1): void {
62
60
  this.writeCount += count;
63
61
  }
64
62
 
65
63
  /**
66
64
  * Track delete operation
67
- * @param _collection - Collection name (reserved for future per-collection tracking)
68
65
  * @param count - Number of documents deleted
69
66
  */
70
- trackDelete(_collection: string, count: number = 1): void {
67
+ trackDelete(count: number = 1): void {
71
68
  this.deleteCount += count;
72
69
  }
73
70
 
@@ -78,7 +78,9 @@ export abstract class BasePaginatedRepository extends BaseQueryRepository {
78
78
  }
79
79
 
80
80
  // Generate a unique key for deduplication (after cursor is resolved)
81
- const uniqueKey = `${collectionName}_list_${orderByField}_${orderDirection}_${fetchLimit}_${cursorKey}`;
81
+ // FIX: Escape cursor to prevent key collisions from special characters
82
+ const escapedCursor = cursorKey.replace(/[|]/g, '_');
83
+ const uniqueKey = `${collectionName}_list_${orderByField}_${orderDirection}_${fetchLimit}_${escapedCursor}`;
82
84
 
83
85
  return this.executeQuery(
84
86
  collectionName,
@@ -120,7 +120,7 @@ export abstract class BaseRepository implements IPathResolver {
120
120
  /**
121
121
  * Track read operation for quota monitoring
122
122
  *
123
- * @param collection - Collection name
123
+ * @param collection - Collection name (for documentation purposes)
124
124
  * @param count - Number of documents read
125
125
  * @param cached - Whether the result is from cache
126
126
  */
@@ -129,33 +129,33 @@ export abstract class BaseRepository implements IPathResolver {
129
129
  count: number = 1,
130
130
  cached: boolean = false,
131
131
  ): void {
132
- quotaTrackingMiddleware.trackRead(collection, count, cached);
132
+ quotaTrackingMiddleware.trackRead(count, cached);
133
133
  }
134
134
 
135
135
  /**
136
136
  * Track write operation for quota monitoring
137
137
  *
138
- * @param collection - Collection name
138
+ * @param collection - Collection name (for documentation purposes)
139
139
  * @param count - Number of documents written
140
140
  */
141
141
  protected trackWrite(
142
142
  collection: string,
143
143
  count: number = 1,
144
144
  ): void {
145
- quotaTrackingMiddleware.trackWrite(collection, count);
145
+ quotaTrackingMiddleware.trackWrite(count);
146
146
  }
147
147
 
148
148
  /**
149
149
  * Track delete operation for quota monitoring
150
150
  *
151
- * @param collection - Collection name
151
+ * @param collection - Collection name (for documentation purposes)
152
152
  * @param count - Number of documents deleted
153
153
  */
154
154
  protected trackDelete(
155
155
  collection: string,
156
156
  count: number = 1,
157
157
  ): void {
158
- quotaTrackingMiddleware.trackDelete(collection, count);
158
+ quotaTrackingMiddleware.trackDelete(count);
159
159
  }
160
160
 
161
161
  /**
@@ -43,9 +43,11 @@ export class RequestLoggerService {
43
43
 
44
44
  /**
45
45
  * Get all logs
46
+ * PERF: Return array directly without copy - logs is private and immutable from outside
47
+ * Callers should not modify the returned array.
46
48
  */
47
49
  getLogs(): RequestLog[] {
48
- return [...this.logs];
50
+ return this.logs;
49
51
  }
50
52
 
51
53
  /**
@@ -144,9 +146,10 @@ export class RequestLoggerService {
144
146
 
145
147
  /**
146
148
  * Notify all listeners
149
+ * PERF: Use for...of instead of forEach for better performance
147
150
  */
148
151
  private notifyListeners(log: RequestLog): void {
149
- this.listeners.forEach((listener) => {
152
+ for (const listener of this.listeners) {
150
153
  try {
151
154
  listener(log);
152
155
  } catch (error) {
@@ -157,7 +160,7 @@ export class RequestLoggerService {
157
160
  console.warn(`${RequestLoggerService.LISTENER_ERROR_PREFIX} ${errorMessage}`);
158
161
  }
159
162
  }
160
- });
163
+ }
161
164
  }
162
165
  }
163
166
 
@@ -50,7 +50,8 @@ export function useFirestoreSnapshot<TData>(
50
50
  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
51
51
 
52
52
  // Stabilize queryKey to prevent unnecessary listener re-subscriptions
53
- const stableKeyString = JSON.stringify(queryKey);
53
+ // PERF: Memoize stringified key to avoid expensive JSON.stringify on every render
54
+ const stableKeyString = useMemo(() => JSON.stringify(queryKey), [queryKey]);
54
55
  const stableQueryKey = useMemo(() => queryKey, [stableKeyString]);
55
56
 
56
57
  useEffect(() => {
@@ -119,7 +119,8 @@ export function useSmartFirestoreSnapshot<TData>(
119
119
  });
120
120
 
121
121
  // Stabilize queryKey to prevent unnecessary listener re-subscriptions
122
- const stableKeyString = JSON.stringify(queryKey);
122
+ // PERF: Memoize stringified key to avoid expensive JSON.stringify on every render
123
+ const stableKeyString = useMemo(() => JSON.stringify(queryKey), [queryKey]);
123
124
  const stableQueryKey = useMemo(() => queryKey, [stableKeyString]);
124
125
 
125
126
  /**
@@ -38,7 +38,9 @@ export class PendingQueryManager {
38
38
  const pending = this.pendingQueries.get(key);
39
39
  if (!pending) return false;
40
40
 
41
- const age = Date.now() - pending.timestamp;
41
+ // PERF: Cache Date.now() to avoid multiple calls in hot path
42
+ const now = Date.now();
43
+ const age = now - pending.timestamp;
42
44
  if (age > this.deduplicationWindowMs) {
43
45
  this.pendingQueries.delete(key);
44
46
  return false;
@@ -61,17 +63,22 @@ export class PendingQueryManager {
61
63
  * Also attaches cleanup handlers to prevent memory leaks.
62
64
  */
63
65
  add(key: string, promise: Promise<unknown>): void {
66
+ // PERF: Cache timestamp to avoid multiple Date.now() calls
67
+ const now = Date.now();
68
+
69
+ // Set first, then attach cleanup handler to prevent race condition
70
+ // where immediately-resolved promise triggers cleanup before set()
71
+ this.pendingQueries.set(key, {
72
+ promise,
73
+ timestamp: now,
74
+ });
75
+
64
76
  // Attach cleanup handler to ensure query is removed from map
65
77
  // even if caller's finally block doesn't execute (e.g., unhandled rejection)
66
78
  promise.finally(() => {
67
79
  // Immediate cleanup - no delay needed for better performance
68
80
  this.pendingQueries.delete(key);
69
81
  });
70
-
71
- this.pendingQueries.set(key, {
72
- promise,
73
- timestamp: Date.now(),
74
- });
75
82
  }
76
83
 
77
84
  /**
@@ -13,9 +13,16 @@ export interface QueryKey {
13
13
  /**
14
14
  * Escape special characters in query key components
15
15
  * Prevents key collisions when filter strings contain separator characters
16
+ * FIX: Escape ALL special characters that could cause issues, not just % and |
16
17
  */
17
18
  function escapeKeyComponent(component: string): string {
18
- return component.replace(/%/g, '%25').replace(/\|/g, '%7C');
19
+ return component
20
+ .replace(/%/g, '%25')
21
+ .replace(/\|/g, '%7C')
22
+ .replace(/\n/g, '%0A')
23
+ .replace(/\r/g, '%0D')
24
+ .replace(/\0/g, '%00')
25
+ .replace(/\//g, '%2F');
19
26
  }
20
27
 
21
28
  /**
@@ -49,8 +49,11 @@ export class PaginationHelper<T> {
49
49
  let nextCursor: string | null = null;
50
50
  if (hasMoreValue && resultItems.length > 0) {
51
51
  // Access is safe because we checked length > 0
52
- const lastItem = resultItems[resultItems.length - 1]!;
53
- nextCursor = getCursor(lastItem);
52
+ const lastIndex = resultItems.length - 1;
53
+ const lastItem = resultItems[lastIndex];
54
+ if (lastItem) {
55
+ nextCursor = getCursor(lastItem);
56
+ }
54
57
  }
55
58
 
56
59
  return {
@@ -31,10 +31,16 @@ export async function runTransaction<T>(
31
31
  const errorCode = hasCodeProperty(error) ? error.code : 'unknown';
32
32
 
33
33
  if (isQuotaError(error)) {
34
- throw new Error(`[runTransaction] ${ERROR_MESSAGES.FIRESTORE.QUOTA_EXCEEDED}: ${errorMessage} (Code: ${errorCode})`);
34
+ // FIX: Preserve original error by adding it as a custom property
35
+ const quotaError = new Error(`[runTransaction] ${ERROR_MESSAGES.FIRESTORE.QUOTA_EXCEEDED}: ${errorMessage} (Code: ${errorCode})`);
36
+ (quotaError as any).originalError = error;
37
+ throw quotaError;
35
38
  }
36
39
 
37
- throw new Error(`[runTransaction] Transaction failed: ${errorMessage} (Code: ${errorCode})`);
40
+ // FIX: Preserve original error for debugging
41
+ const transactionError = new Error(`[runTransaction] Transaction failed: ${errorMessage} (Code: ${errorCode})`);
42
+ (transactionError as any).originalError = error;
43
+ throw transactionError;
38
44
  }
39
45
  }
40
46
 
package/src/index.ts CHANGED
@@ -46,12 +46,332 @@ export {
46
46
  getSafeErrorCode,
47
47
  } from "./shared/domain/guards/firebase-error.guard";
48
48
 
49
- // Domain Exports
50
- export * from "./domains/auth";
51
- export * from "./domains/account-deletion";
49
+ // =============================================================================
50
+ // AUTH DOMAIN EXPORTS
51
+ // =============================================================================
52
52
 
53
- // Firestore Module Exports
54
- export * from "./domains/firestore";
53
+ // Domain Layer
54
+ export type { FirebaseAuthConfig } from './domains/auth/domain/value-objects/FirebaseAuthConfig';
55
+ export {
56
+ isAnonymousUser,
57
+ } from './domains/auth/domain/entities/AnonymousUser';
58
+ export type { AnonymousUser } from './domains/auth/domain/entities/AnonymousUser';
59
+
60
+ // Infrastructure Layer - Config
61
+ export {
62
+ getFirebaseAuth,
63
+ isFirebaseAuthInitialized,
64
+ getFirebaseAuthInitializationError,
65
+ resetFirebaseAuthClient,
66
+ firebaseAuthClient,
67
+ initializeFirebaseAuth,
68
+ } from './domains/auth/infrastructure/config/FirebaseAuthClient';
69
+ export type { Auth } from './domains/auth/infrastructure/config/FirebaseAuthClient';
70
+
71
+ // Infrastructure Layer - Services
72
+ export {
73
+ checkAuthState,
74
+ isAuthenticated,
75
+ isAnonymous,
76
+ getCurrentUserId,
77
+ getCurrentUser,
78
+ getCurrentUserIdFromGlobal,
79
+ getCurrentUserFromGlobal,
80
+ isCurrentUserAuthenticated,
81
+ isCurrentUserAnonymous,
82
+ verifyUserId,
83
+ isValidUser,
84
+ } from './domains/auth/infrastructure/services/auth-utils.service';
85
+ export type { AuthCheckResult } from './domains/auth/infrastructure/services/auth-utils.service';
86
+
87
+ export {
88
+ AnonymousAuthService,
89
+ anonymousAuthService,
90
+ } from './domains/auth/infrastructure/services/anonymous-auth.service';
91
+ export type {
92
+ AnonymousAuthResult,
93
+ AnonymousAuthServiceInterface,
94
+ } from './domains/auth/infrastructure/services/anonymous-auth.service';
95
+
96
+ export {
97
+ shouldSkipFirestoreQuery,
98
+ createFirestoreQueryOptions,
99
+ } from './domains/auth/infrastructure/services/firestore-utils.service';
100
+ export type {
101
+ FirestoreQueryOptions,
102
+ FirestoreQueryResult,
103
+ FirestoreQuerySkipReason,
104
+ } from './domains/auth/infrastructure/services/firestore-utils.service';
105
+
106
+ // Social Auth Services
107
+ export {
108
+ GoogleAuthService,
109
+ googleAuthService,
110
+ } from './domains/auth/infrastructure/services/google-auth.service';
111
+ export type {
112
+ GoogleAuthConfig,
113
+ GoogleAuthResult,
114
+ } from './domains/auth/infrastructure/services/google-auth.types';
115
+
116
+ export {
117
+ GoogleOAuthService,
118
+ googleOAuthService,
119
+ } from './domains/auth/infrastructure/services/google-oauth.service';
120
+ export type { GoogleOAuthConfig } from './domains/auth/infrastructure/services/google-oauth.service';
121
+
122
+ export {
123
+ AppleAuthService,
124
+ appleAuthService,
125
+ } from './domains/auth/infrastructure/services/apple-auth.service';
126
+ export type { AppleAuthResult } from './domains/auth/infrastructure/services/apple-auth.types';
127
+
128
+ // Password & Email/Password Auth
129
+ export {
130
+ updateUserPassword,
131
+ } from './domains/auth/infrastructure/services/password.service';
132
+
133
+ export {
134
+ signInWithEmail,
135
+ signUpWithEmail,
136
+ signOut,
137
+ linkAnonymousWithEmail,
138
+ } from './domains/auth/infrastructure/services/email-auth.service';
139
+ export type {
140
+ EmailCredentials,
141
+ EmailAuthResult,
142
+ } from './domains/auth/infrastructure/services/email-auth.service';
143
+
144
+ // Auth Listener
145
+ export {
146
+ setupAuthListener,
147
+ } from './domains/auth/infrastructure/services/auth-listener.service';
148
+ export type {
149
+ AuthListenerConfig,
150
+ AuthListenerResult,
151
+ } from './domains/auth/infrastructure/services/auth-listener.service';
152
+
153
+ // User Document Service
154
+ export {
155
+ ensureUserDocument,
156
+ markUserDeleted,
157
+ configureUserDocumentService,
158
+ } from './domains/auth/infrastructure/services/user-document.service';
159
+ export type {
160
+ UserDocumentUser,
161
+ UserDocumentConfig,
162
+ UserDocumentExtras,
163
+ } from './domains/auth/infrastructure/services/user-document.types';
164
+
165
+ // Presentation Layer - Hooks
166
+ export { useFirebaseAuth } from './domains/auth/presentation/hooks/useFirebaseAuth';
167
+ export type { UseFirebaseAuthResult } from './domains/auth/presentation/hooks/useFirebaseAuth';
168
+
169
+ export { useAnonymousAuth } from './domains/auth/presentation/hooks/useAnonymousAuth';
170
+ export type { UseAnonymousAuthResult } from './domains/auth/presentation/hooks/useAnonymousAuth';
171
+
172
+ export { useSocialAuth } from './domains/auth/presentation/hooks/useSocialAuth';
173
+ export type {
174
+ SocialAuthConfig,
175
+ SocialAuthResult,
176
+ UseSocialAuthResult,
177
+ } from './domains/auth/presentation/hooks/useSocialAuth';
178
+
179
+ export { useGoogleOAuth } from './domains/auth/presentation/hooks/useGoogleOAuth';
180
+ export type { UseGoogleOAuthResult } from './domains/auth/presentation/hooks/useGoogleOAuth';
181
+
182
+ // =============================================================================
183
+ // ACCOUNT DELETION DOMAIN EXPORTS
184
+ // =============================================================================
185
+
186
+ export {
187
+ deleteCurrentUser,
188
+ deleteUserAccount,
189
+ } from './domains/account-deletion/infrastructure/services/account-deletion.service';
190
+ export type { AccountDeletionResult } from './domains/account-deletion/infrastructure/services/account-deletion.service';
191
+
192
+ export type {
193
+ AccountDeletionOptions,
194
+ ReauthenticationResult,
195
+ AuthProviderType,
196
+ ReauthCredentialResult,
197
+ } from './domains/account-deletion/application/ports/reauthentication.types';
198
+
199
+ export {
200
+ getUserAuthProvider,
201
+ reauthenticateWithPassword,
202
+ reauthenticateWithGoogle,
203
+ reauthenticateWithApple,
204
+ getAppleReauthCredential,
205
+ } from './domains/account-deletion/infrastructure/services/reauthentication.service';
206
+
207
+ // =============================================================================
208
+ // FIRESTORE DOMAIN EXPORTS
209
+ // =============================================================================
210
+
211
+ // Domain Errors
212
+ export {
213
+ FirebaseFirestoreError,
214
+ FirebaseFirestoreInitializationError,
215
+ FirebaseFirestoreQuotaError,
216
+ } from './domains/firestore/domain/errors/FirebaseFirestoreError';
217
+
218
+ // Firestore Client
219
+ export {
220
+ initializeFirestore,
221
+ getFirestore,
222
+ isFirestoreInitialized,
223
+ getFirestoreInitializationError,
224
+ resetFirestoreClient,
225
+ firestoreClient,
226
+ } from './domains/firestore/infrastructure/config/FirestoreClient';
227
+ export type { Firestore } from './domains/firestore/infrastructure/config/FirestoreClient';
228
+
229
+ // Repositories
230
+ export { BaseRepository } from './domains/firestore/infrastructure/repositories/BaseRepository';
231
+ export type { IPathResolver } from './domains/firestore/infrastructure/repositories/BaseRepository';
232
+ export { BaseQueryRepository } from './domains/firestore/infrastructure/repositories/BaseQueryRepository';
233
+ export { BasePaginatedRepository } from './domains/firestore/infrastructure/repositories/BasePaginatedRepository';
234
+
235
+ // Date Utilities
236
+ export {
237
+ isoToTimestamp,
238
+ timestampToISO,
239
+ timestampToDate,
240
+ getCurrentISOString,
241
+ formatRelativeTime,
242
+ } from './domains/firestore/utils/dateUtils';
243
+ export type { RelativeTimeLabels } from './domains/firestore/utils/dateUtils';
244
+
245
+ // Query Builder
246
+ export {
247
+ buildQuery,
248
+ createInFilter,
249
+ createEqualFilter,
250
+ } from './domains/firestore/utils/query-builder';
251
+ export type {
252
+ QueryBuilderOptions,
253
+ FieldFilter,
254
+ } from './domains/firestore/utils/query-builder';
255
+
256
+ // Pagination
257
+ export {
258
+ PaginationHelper,
259
+ createPaginationHelper,
260
+ } from './domains/firestore/utils/pagination.helper';
261
+ export type {
262
+ PaginatedResult,
263
+ PaginationParams,
264
+ } from './domains/firestore/types/pagination.types';
265
+ export { EMPTY_PAGINATED_RESULT } from './domains/firestore/types/pagination.types';
266
+
267
+ // Domain Constants
268
+ export {
269
+ FREE_TIER_LIMITS,
270
+ QUOTA_THRESHOLDS,
271
+ calculateQuotaUsage,
272
+ isQuotaThresholdReached,
273
+ getRemainingQuota,
274
+ } from './domains/firestore/domain/constants/QuotaLimits';
275
+
276
+ // Domain Entities
277
+ export type {
278
+ QuotaMetrics,
279
+ QuotaLimits,
280
+ QuotaStatus,
281
+ } from './domains/firestore/domain/entities/QuotaMetrics';
282
+ export type {
283
+ RequestLog,
284
+ RequestStats,
285
+ RequestType,
286
+ } from './domains/firestore/domain/entities/RequestLog';
287
+
288
+ // Domain Services
289
+ export { QuotaCalculator } from './domains/firestore/domain/services/QuotaCalculator';
290
+
291
+ // Quota Error Detection
292
+ export {
293
+ isQuotaError,
294
+ isRetryableError,
295
+ } from './shared/domain/utils/error-handlers/error-checkers';
296
+ export {
297
+ ERROR_MESSAGES,
298
+ } from './shared/domain/utils/error-handlers/error-messages';
299
+
300
+ // Middleware
301
+ export {
302
+ QueryDeduplicationMiddleware,
303
+ queryDeduplicationMiddleware,
304
+ syncDeduplicationWithQuota,
305
+ } from './domains/firestore/infrastructure/middleware/QueryDeduplicationMiddleware';
306
+ export type {
307
+ QueryDeduplicationConfig,
308
+ DeduplicationStatistics,
309
+ } from './domains/firestore/infrastructure/middleware/QueryDeduplicationMiddleware';
310
+ export {
311
+ QuotaTrackingMiddleware,
312
+ quotaTrackingMiddleware,
313
+ } from './domains/firestore/infrastructure/middleware/QuotaTrackingMiddleware';
314
+
315
+ // Services
316
+ export {
317
+ RequestLoggerService,
318
+ requestLoggerService,
319
+ } from './domains/firestore/infrastructure/services/RequestLoggerService';
320
+
321
+ // Firestore Helper Utilities
322
+ export {
323
+ withFirestore,
324
+ withFirestoreVoid,
325
+ withFirestoreBool,
326
+ } from './domains/firestore/utils/operation/operation-executor.util';
327
+ export {
328
+ runTransaction,
329
+ serverTimestamp,
330
+ } from './domains/firestore/utils/transaction/transaction.util';
331
+ export {
332
+ createErrorResult,
333
+ createSuccessResult,
334
+ } from './domains/firestore/utils/result/result.util';
335
+ export type { NoDbResult } from './domains/firestore/utils/result/result.util';
336
+
337
+ // Validation Utilities
338
+ export {
339
+ isValidCursor,
340
+ validateCursorOrThrow,
341
+ CursorValidationError,
342
+ } from './domains/firestore/utils/validation/cursor-validator.util';
343
+ export {
344
+ isValidFieldName,
345
+ } from './domains/firestore/utils/validation/field-validator.util';
346
+ export {
347
+ isValidDateRange,
348
+ validateDateRangeOrThrow,
349
+ } from './domains/firestore/utils/validation/date-validator.util';
350
+
351
+ // Presentation — TanStack Query integration
352
+ export { useFirestoreQuery } from './domains/firestore/presentation/hooks/useFirestoreQuery';
353
+ export { useFirestoreMutation } from './domains/firestore/presentation/hooks/useFirestoreMutation';
354
+ export { useFirestoreSnapshot } from './domains/firestore/presentation/hooks/useFirestoreSnapshot';
355
+ export { useSmartFirestoreSnapshot, useSmartListenerControl } from './domains/firestore/presentation/hooks/useSmartFirestoreSnapshot';
356
+ export { createFirestoreKeys } from './domains/firestore/presentation/query-keys/createFirestoreKeys';
357
+
358
+ export type { UseFirestoreQueryOptions } from './domains/firestore/presentation/hooks/useFirestoreQuery';
359
+ export type { UseFirestoreMutationOptions } from './domains/firestore/presentation/hooks/useFirestoreMutation';
360
+ export type { UseFirestoreSnapshotOptions } from './domains/firestore/presentation/hooks/useFirestoreSnapshot';
361
+ export type { UseSmartFirestoreSnapshotOptions, BackgroundStrategy } from './domains/firestore/presentation/hooks/useSmartFirestoreSnapshot';
362
+
363
+ // Firebase Types
364
+ export { Timestamp } from 'firebase/firestore';
365
+ export type {
366
+ CollectionReference,
367
+ QueryDocumentSnapshot,
368
+ DocumentData,
369
+ Transaction,
370
+ DocumentReference,
371
+ WriteBatch,
372
+ DocumentSnapshot,
373
+ QuerySnapshot,
374
+ } from 'firebase/firestore';
55
375
 
56
376
  // Init Module Factory
57
377
  export {
@@ -43,7 +43,3 @@ export const ERROR_MESSAGES = {
43
43
  },
44
44
  } as const;
45
45
 
46
- export function getQuotaErrorMessage(): string {
47
- return ERROR_MESSAGES.FIRESTORE.QUOTA_EXCEEDED;
48
- }
49
-