@umituz/react-native-firebase 3.0.3 → 3.0.5

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 (74) hide show
  1. package/package.json +7 -1
  2. package/src/domains/account-deletion/index.ts +15 -10
  3. package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +226 -26
  4. package/src/domains/account-deletion/infrastructure/services/reauthentication.service.ts +160 -0
  5. package/src/domains/auth/domain/value-objects/FirebaseAuthConfig.ts +1 -1
  6. package/src/domains/auth/index.ts +156 -6
  7. package/src/domains/auth/infrastructure/config/FirebaseAuthClient.ts +60 -48
  8. package/src/domains/auth/infrastructure/config/initializers/FirebaseAuthInitializer.ts +41 -5
  9. package/src/domains/auth/presentation/hooks/useGoogleOAuth.ts +115 -20
  10. package/src/domains/firestore/domain/constants/QuotaLimits.ts +101 -0
  11. package/src/domains/firestore/domain/entities/QuotaMetrics.ts +26 -0
  12. package/src/domains/firestore/domain/entities/RequestLog.ts +28 -0
  13. package/src/domains/firestore/domain/services/QuotaCalculator.ts +71 -0
  14. package/src/domains/firestore/index.ts +86 -31
  15. package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +82 -45
  16. package/src/domains/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +249 -4
  17. package/src/domains/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +312 -0
  18. package/src/domains/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +95 -0
  19. package/src/domains/firestore/infrastructure/repositories/BasePaginatedRepository.ts +7 -1
  20. package/src/domains/firestore/infrastructure/repositories/BaseQueryRepository.ts +34 -8
  21. package/src/domains/firestore/infrastructure/repositories/BaseRepository.ts +48 -9
  22. package/src/domains/firestore/infrastructure/services/RequestLoggerService.ts +165 -0
  23. package/src/domains/firestore/presentation/hooks/index.ts +10 -0
  24. package/src/domains/firestore/presentation/hooks/useFirestoreMutation.ts +1 -1
  25. package/src/domains/firestore/presentation/hooks/useFirestoreQuery.ts +1 -1
  26. package/src/domains/firestore/presentation/hooks/useSmartFirestoreSnapshot.ts +361 -0
  27. package/src/domains/firestore/presentation/query-keys/createFirestoreKeys.ts +32 -0
  28. package/src/domains/firestore/presentation/query-keys/index.ts +1 -0
  29. package/src/domains/firestore/utils/deduplication/pending-query-manager.util.ts +119 -0
  30. package/src/domains/firestore/utils/deduplication/query-key-generator.util.ts +34 -0
  31. package/src/domains/firestore/utils/deduplication/timer-manager.util.ts +83 -0
  32. package/src/index.ts +2 -30
  33. package/src/shared/domain/utils/calculation.util.ts +305 -17
  34. package/src/shared/domain/utils/error-handlers/error-messages.ts +0 -11
  35. package/src/shared/domain/utils/index.ts +5 -0
  36. package/src/shared/infrastructure/config/base/ClientStateManager.ts +82 -0
  37. package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts +136 -20
  38. package/src/shared/infrastructure/config/clients/FirebaseClientSingleton.ts +1 -1
  39. package/src/shared/infrastructure/config/initializers/FirebaseAppInitializer.ts +9 -0
  40. package/src/shared/infrastructure/config/services/FirebaseInitializationService.ts +1 -1
  41. package/src/shared/infrastructure/config/state/FirebaseClientState.ts +14 -36
  42. package/src/application/auth/index.ts +0 -10
  43. package/src/application/auth/use-cases/index.ts +0 -6
  44. package/src/domains/account-deletion/domain/index.ts +0 -8
  45. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts +0 -79
  46. package/src/domains/account-deletion/infrastructure/services/AccountDeletionTypes.ts +0 -32
  47. package/src/domains/auth/domain.ts +0 -16
  48. package/src/domains/auth/infrastructure/config/index.ts +0 -2
  49. package/src/domains/auth/infrastructure/config/initializers/index.ts +0 -1
  50. package/src/domains/auth/infrastructure/services/index.ts +0 -16
  51. package/src/domains/auth/infrastructure/services/utils/index.ts +0 -1
  52. package/src/domains/auth/infrastructure/stores/index.ts +0 -1
  53. package/src/domains/auth/infrastructure/utils/index.ts +0 -1
  54. package/src/domains/auth/infrastructure.ts +0 -11
  55. package/src/domains/auth/presentation/hooks/useAppleAuth.ts +0 -82
  56. package/src/domains/auth/presentation.ts +0 -31
  57. package/src/domains/firestore/domain/entities/Collection.ts +0 -122
  58. package/src/domains/firestore/domain/entities/CollectionFactory.ts +0 -55
  59. package/src/domains/firestore/domain/entities/CollectionHelpers.ts +0 -143
  60. package/src/domains/firestore/domain/entities/CollectionUtils.ts +0 -72
  61. package/src/domains/firestore/domain/entities/CollectionValidation.ts +0 -138
  62. package/src/domains/firestore/domain/index.ts +0 -61
  63. package/src/domains/firestore/domain/value-objects/QueryOptions.ts +0 -143
  64. package/src/domains/firestore/domain/value-objects/QueryOptionsFactory.ts +0 -95
  65. package/src/domains/firestore/domain/value-objects/QueryOptionsHelpers.ts +0 -110
  66. package/src/domains/firestore/domain/value-objects/WhereClause.ts +0 -114
  67. package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts +0 -101
  68. package/src/domains/firestore/domain/value-objects/WhereClauseHelpers.ts +0 -123
  69. package/src/domains/firestore/domain/value-objects/WhereClauseValidation.ts +0 -83
  70. package/src/shared/infrastructure/base/ErrorHandler.ts +0 -81
  71. package/src/shared/infrastructure/base/ServiceBase.ts +0 -62
  72. package/src/shared/infrastructure/base/TypedGuard.ts +0 -131
  73. package/src/shared/infrastructure/base/index.ts +0 -34
  74. package/src/shared/types/firebase.types.ts +0 -274
@@ -5,8 +5,8 @@
5
5
  */
6
6
 
7
7
  import type { Auth } from 'firebase/auth';
8
- import { getAuth as getFirebaseAuthFromFirebase } from 'firebase/auth';
9
8
  import { getFirebaseApp } from '../../../../shared/infrastructure/config/services/FirebaseInitializationService';
9
+ import { FirebaseAuthInitializer } from './initializers/FirebaseAuthInitializer';
10
10
  import type { FirebaseAuthConfig } from '../../domain/value-objects/FirebaseAuthConfig';
11
11
  import { ServiceClientSingleton } from '../../../../shared/infrastructure/config/base/ServiceClientSingleton';
12
12
 
@@ -15,64 +15,76 @@ import { ServiceClientSingleton } from '../../../../shared/infrastructure/config
15
15
  */
16
16
  class FirebaseAuthClientSingleton extends ServiceClientSingleton<Auth, FirebaseAuthConfig> {
17
17
  private constructor() {
18
- super();
18
+ super({
19
+ serviceName: 'FirebaseAuth',
20
+ initializer: (config?: FirebaseAuthConfig) => {
21
+ const app = getFirebaseApp();
22
+ if (!app) {
23
+ this.setError('Firebase App is not initialized');
24
+ return null;
25
+ }
26
+ const auth = FirebaseAuthInitializer.initialize(app, config);
27
+ if (!auth) {
28
+ this.setError('Auth initialization returned null');
29
+ }
30
+ return auth;
31
+ },
32
+ });
19
33
  }
20
34
 
21
- initialize(): Auth {
22
- try {
23
- const app = getFirebaseApp();
24
- if (!app) {
25
- this.setError('Firebase App is not initialized');
26
- throw new Error('Firebase App is not initialized');
27
- }
35
+ private static instance: FirebaseAuthClientSingleton | null = null;
28
36
 
29
- const auth = getFirebaseAuthFromFirebase(app);
30
- this.instance = auth;
31
- return auth;
32
- } catch (error) {
33
- const errorMessage = error instanceof Error ? error.message : 'Auth initialization failed';
34
- this.setError(errorMessage);
35
- throw error;
36
- }
37
+ static getInstance(): FirebaseAuthClientSingleton {
38
+ if (!this.instance) this.instance = new FirebaseAuthClientSingleton();
39
+ return this.instance;
37
40
  }
38
41
 
39
- getAuth(): Auth {
40
- if (!this.isInitialized()) {
41
- return this.initialize();
42
- }
43
- return this.getInstance();
42
+ /**
43
+ * Initialize Auth with optional configuration
44
+ */
45
+ override initialize(config?: FirebaseAuthConfig): Auth | null {
46
+ return super.initialize(config);
44
47
  }
45
48
 
46
- private static instance: FirebaseAuthClientSingleton | null = null;
47
-
48
- static getInstance(): FirebaseAuthClientSingleton {
49
- if (!this.instance) {
50
- this.instance = new FirebaseAuthClientSingleton();
49
+ /**
50
+ * Get Auth instance
51
+ */
52
+ getAuth(): Auth | null {
53
+ // Attempt initialization if not already initialized
54
+ if (!this.isInitialized() && !this.getInitializationError()) {
55
+ try {
56
+ const app = getFirebaseApp();
57
+ if (app) {
58
+ this.initialize();
59
+ }
60
+ } catch (error) {
61
+ // Silently handle auto-initialization errors
62
+ // The error will be stored in state for later retrieval
63
+ const errorMessage = error instanceof Error ? error.message : 'Auto-initialization failed';
64
+ this.setError(errorMessage);
65
+ }
51
66
  }
52
- return this.instance;
67
+ // Enable auto-initialization flag when getting instance
68
+ return this.getInstance(true);
53
69
  }
54
70
  }
55
71
 
56
- const firebaseAuthClientSingleton = FirebaseAuthClientSingleton.getInstance();
57
-
58
- export const initializeFirebaseAuth = (): Auth => {
59
- return firebaseAuthClientSingleton.initialize();
60
- };
61
-
62
- export const getFirebaseAuth = (): Auth => {
63
- return firebaseAuthClientSingleton.getAuth();
64
- };
65
-
66
- export const isFirebaseAuthInitialized = (): boolean => {
67
- return firebaseAuthClientSingleton.isInitialized();
68
- };
69
-
70
- export const getFirebaseAuthInitializationError = (): Error | null => {
71
- return firebaseAuthClientSingleton.getInitializationError();
72
- };
72
+ function getFirebaseAuthClientSafe(): FirebaseAuthClientSingleton | null {
73
+ try {
74
+ return FirebaseAuthClientSingleton.getInstance();
75
+ } catch {
76
+ if (__DEV__) {
77
+ console.warn('[Firebase] Could not create FirebaseAuth client singleton.');
78
+ }
79
+ return null;
80
+ }
81
+ }
73
82
 
74
- export const resetFirebaseAuthClient = (): void => {
75
- firebaseAuthClientSingleton.reset();
76
- };
83
+ export const firebaseAuthClient = getFirebaseAuthClientSafe();
84
+ export const initializeFirebaseAuth = (c?: FirebaseAuthConfig) => firebaseAuthClient?.initialize(c) ?? null;
85
+ export const getFirebaseAuth = () => firebaseAuthClient?.getAuth() ?? null;
86
+ export const isFirebaseAuthInitialized = () => firebaseAuthClient?.isInitialized() ?? false;
87
+ export const getFirebaseAuthInitializationError = () => firebaseAuthClient?.getInitializationError() ?? null;
88
+ export const resetFirebaseAuthClient = () => firebaseAuthClient?.reset();
77
89
 
78
90
  export type { Auth } from 'firebase/auth';
@@ -1,7 +1,15 @@
1
+ /**
2
+ * Firebase Auth Initializer
3
+ *
4
+ * Single Responsibility: Initialize Firebase Auth instance
5
+ * Platform-agnostic: Works on all platforms (Web, iOS, Android)
6
+ */
7
+
1
8
  import {
2
9
  initializeAuth,
3
10
  getAuth,
4
11
  // @ts-expect-error: getReactNativePersistence exists in the React Native bundle but missing from type definitions
12
+ // See: https://github.com/firebase/firebase-js-sdk/issues/9316
5
13
  getReactNativePersistence,
6
14
  } from 'firebase/auth';
7
15
  import type { Auth } from 'firebase/auth';
@@ -9,8 +17,40 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
9
17
  import type { FirebaseApp } from 'firebase/app';
10
18
  import type { FirebaseAuthConfig } from '../../../domain/value-objects/FirebaseAuthConfig';
11
19
 
20
+ /**
21
+ * Initializes Firebase Auth
22
+ * Platform-agnostic: Works on all platforms (Web, iOS, Android)
23
+ */
12
24
  export class FirebaseAuthInitializer {
25
+ /**
26
+ * Initialize Firebase Auth with persistence support
27
+ */
13
28
  static initialize(app: FirebaseApp, config?: FirebaseAuthConfig): Auth | null {
29
+ try {
30
+ const auth = this.initializeWithPersistence(app, config);
31
+ return auth;
32
+ } catch (error: unknown) {
33
+ return this.handleInitializationError(error, app);
34
+ }
35
+ }
36
+
37
+ private static handleInitializationError(error: unknown, app: FirebaseApp): Auth | null {
38
+ // Any initialization error: try to get existing auth instance
39
+ return this.getExistingAuth(app);
40
+ }
41
+
42
+ private static getExistingAuth(app: FirebaseApp): Auth | null {
43
+ try {
44
+ return getAuth(app);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ private static initializeWithPersistence(
51
+ app: FirebaseApp,
52
+ config?: FirebaseAuthConfig
53
+ ): Auth | null {
14
54
  try {
15
55
  const storage = config?.authStorage || {
16
56
  getItem: (key: string) => AsyncStorage.getItem(key),
@@ -22,11 +62,7 @@ export class FirebaseAuthInitializer {
22
62
  persistence: getReactNativePersistence(storage),
23
63
  });
24
64
  } catch {
25
- try {
26
- return getAuth(app);
27
- } catch {
28
- return null;
29
- }
65
+ return this.getExistingAuth(app);
30
66
  }
31
67
  }
32
68
  }
@@ -1,14 +1,37 @@
1
1
  /**
2
2
  * useGoogleOAuth Hook
3
3
  * Handles Google OAuth flow using expo-auth-session and Firebase auth
4
- *
5
- * Max lines: 150 (enforced for maintainability)
4
+ * This hook is optional and requires expo-auth-session to be installed
6
5
  */
7
6
 
8
- import { useState, useCallback } from 'react';
9
- import { getFirebaseAuth } from '../../infrastructure/config/FirebaseAuthClient';
10
- import type { GoogleOAuthConfig } from '../../infrastructure/services/google-oauth.service';
11
- import { GoogleOAuthService } from '../../infrastructure/services/google-oauth.service';
7
+ import { useState, useCallback, useEffect, useRef, useMemo } from "react";
8
+ import { googleOAuthService } from "../../infrastructure/services/google-oauth.service";
9
+ import { getFirebaseAuth } from "../../infrastructure/config/FirebaseAuthClient";
10
+ import type { GoogleOAuthConfig } from "../../infrastructure/services/google-oauth.service";
11
+ // Conditional import for expo-auth-session
12
+ interface AuthSessionResponse {
13
+ type: string;
14
+ authentication?: { idToken?: string } | null;
15
+ }
16
+
17
+ interface ExpoAuthSessionModule {
18
+ useAuthRequest: (config: {
19
+ iosClientId: string;
20
+ webClientId: string;
21
+ androidClientId: string;
22
+ }) => [unknown, AuthSessionResponse | null, (() => Promise<AuthSessionResponse>) | null];
23
+ }
24
+
25
+ let ExpoAuthSession: ExpoAuthSessionModule | null = null;
26
+ let isExpoAuthAvailable = false;
27
+
28
+ try {
29
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
30
+ ExpoAuthSession = require("expo-auth-session/providers/google") as ExpoAuthSessionModule;
31
+ isExpoAuthAvailable = true;
32
+ } catch {
33
+ // expo-auth-session not available - hook will return unavailable state
34
+ }
12
35
 
13
36
  export interface UseGoogleOAuthResult {
14
37
  signInWithGoogle: () => Promise<SocialAuthResult>;
@@ -26,41 +49,113 @@ interface SocialAuthResult {
26
49
 
27
50
  /**
28
51
  * Hook for Google OAuth authentication
52
+ * Requires expo-auth-session and expo-web-browser to be installed
29
53
  */
30
54
  export function useGoogleOAuth(config?: GoogleOAuthConfig): UseGoogleOAuthResult {
31
55
  const [isLoading, setIsLoading] = useState(false);
32
56
  const [googleError, setGoogleError] = useState<string | null>(null);
33
57
 
34
- const service = new GoogleOAuthService();
58
+ // Memoize service checks to avoid repeated calls on every render
59
+ const googleAvailable = useMemo(() => googleOAuthService.isAvailable(), []);
60
+ const googleConfigured = useMemo(() => googleOAuthService.isConfigured(config), [config]);
35
61
 
36
- const googleAvailable = service.isAvailable();
37
- const googleConfigured = service.isConfigured(config);
62
+ // Keep config in a ref so the response useEffect doesn't re-run when config object reference changes
63
+ const configRef = useRef(config);
64
+ configRef.current = config;
65
+
66
+ // Call the Hook directly (only valid in React component)
67
+ // If expo-auth-session is not available, these will be null
68
+ // Empty strings are passed when config is missing to preserve hook call order (Rules of Hooks)
69
+ const [request, response, promptAsync] = isExpoAuthAvailable && ExpoAuthSession
70
+ ? ExpoAuthSession.useAuthRequest({
71
+ iosClientId: config?.iosClientId ?? "",
72
+ webClientId: config?.webClientId ?? "",
73
+ androidClientId: config?.androidClientId ?? "",
74
+ })
75
+ : [null, null, null];
76
+
77
+ // Handle OAuth response
78
+ useEffect(() => {
79
+ if (!googleAvailable || !response) return;
80
+
81
+ const handleResponse = async () => {
82
+ if (response.type === "success" && response.authentication?.idToken) {
83
+ setIsLoading(true);
84
+ setGoogleError(null);
85
+
86
+ try {
87
+ const auth = getFirebaseAuth();
88
+ if (!auth) {
89
+ setGoogleError("Firebase Auth not initialized");
90
+ setIsLoading(false);
91
+ return;
92
+ }
93
+
94
+ await googleOAuthService.signInWithOAuth(
95
+ auth,
96
+ configRef.current,
97
+ async () => response
98
+ );
99
+ } catch (error) {
100
+ setGoogleError(
101
+ error instanceof Error ? error.message : "Firebase sign-in failed"
102
+ );
103
+ } finally {
104
+ setIsLoading(false);
105
+ }
106
+ } else if (response.type === "error") {
107
+ setGoogleError("Google authentication failed");
108
+ }
109
+ };
110
+
111
+ // Call async function and catch errors separately
112
+ handleResponse().catch((err) => {
113
+ // Errors are already handled in handleResponse
114
+ if (__DEV__) {
115
+ console.error('[useGoogleOAuth] Unexpected error in handleResponse:', err);
116
+ }
117
+ });
118
+ }, [response, googleAvailable]); // config read via ref to prevent re-running on reference changes
38
119
 
39
120
  const signInWithGoogle = useCallback(async (): Promise<SocialAuthResult> => {
121
+ if (!googleAvailable) {
122
+ const error = "expo-auth-session is not available. Please install expo-auth-session and expo-web-browser.";
123
+ setGoogleError(error);
124
+ return { success: false, error };
125
+ }
126
+
127
+ if (!googleConfigured) {
128
+ const error = "Google Sign-In is not configured. Please provide valid client IDs.";
129
+ setGoogleError(error);
130
+ return { success: false, error };
131
+ }
132
+
133
+ if (!request || !promptAsync) {
134
+ const error = "Google Sign-In not ready";
135
+ setGoogleError(error);
136
+ return { success: false, error };
137
+ }
138
+
40
139
  setIsLoading(true);
41
140
  setGoogleError(null);
42
141
 
43
142
  try {
44
143
  const auth = getFirebaseAuth();
45
- const result = await service.signInWithOAuth(auth, config);
46
-
47
- if (!result.success) {
48
- setGoogleError(result.error || 'Google sign-in failed');
49
- return { success: false, error: result.error };
144
+ if (!auth) {
145
+ const error = "Firebase Auth not initialized";
146
+ setGoogleError(error);
147
+ return { success: false, error };
50
148
  }
51
149
 
52
- return {
53
- success: true,
54
- isNewUser: result.isNewUser,
55
- };
150
+ return await googleOAuthService.signInWithOAuth(auth, config, promptAsync);
56
151
  } catch (error) {
57
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
152
+ const errorMessage = error instanceof Error ? error.message : "Google sign-in failed";
58
153
  setGoogleError(errorMessage);
59
154
  return { success: false, error: errorMessage };
60
155
  } finally {
61
156
  setIsLoading(false);
62
157
  }
63
- }, [service, config]);
158
+ }, [googleAvailable, googleConfigured, request, promptAsync]); // config read via ref to prevent re-creation on reference changes
64
159
 
65
160
  return {
66
161
  signInWithGoogle,
@@ -0,0 +1,101 @@
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
+ }
@@ -0,0 +1,26 @@
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
+ }
@@ -0,0 +1,28 @@
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
+ }
@@ -0,0 +1,71 @@
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
+