@umituz/react-native-firebase 3.0.3 → 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.
Files changed (80) 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 +235 -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/infrastructure/stores/auth.store.ts +4 -1
  10. package/src/domains/auth/presentation/hooks/useAnonymousAuth.ts +3 -1
  11. package/src/domains/auth/presentation/hooks/useGoogleOAuth.ts +115 -20
  12. package/src/domains/auth/presentation/hooks/utils/auth-state-change.handler.ts +5 -11
  13. package/src/domains/firestore/domain/constants/QuotaLimits.ts +101 -0
  14. package/src/domains/firestore/domain/entities/QuotaMetrics.ts +26 -0
  15. package/src/domains/firestore/domain/entities/RequestLog.ts +28 -0
  16. package/src/domains/firestore/domain/services/QuotaCalculator.ts +71 -0
  17. package/src/domains/firestore/index.ts +85 -31
  18. package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +82 -45
  19. package/src/domains/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +249 -4
  20. package/src/domains/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +306 -0
  21. package/src/domains/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +92 -0
  22. package/src/domains/firestore/infrastructure/repositories/BasePaginatedRepository.ts +9 -1
  23. package/src/domains/firestore/infrastructure/repositories/BaseQueryRepository.ts +34 -8
  24. package/src/domains/firestore/infrastructure/repositories/BaseRepository.ts +48 -9
  25. package/src/domains/firestore/infrastructure/services/RequestLoggerService.ts +168 -0
  26. package/src/domains/firestore/presentation/hooks/index.ts +10 -0
  27. package/src/domains/firestore/presentation/hooks/useFirestoreMutation.ts +1 -1
  28. package/src/domains/firestore/presentation/hooks/useFirestoreQuery.ts +1 -1
  29. package/src/domains/firestore/presentation/hooks/useFirestoreSnapshot.ts +2 -1
  30. package/src/domains/firestore/presentation/hooks/useSmartFirestoreSnapshot.ts +362 -0
  31. package/src/domains/firestore/presentation/query-keys/createFirestoreKeys.ts +32 -0
  32. package/src/domains/firestore/presentation/query-keys/index.ts +1 -0
  33. package/src/domains/firestore/utils/deduplication/pending-query-manager.util.ts +126 -0
  34. package/src/domains/firestore/utils/deduplication/query-key-generator.util.ts +41 -0
  35. package/src/domains/firestore/utils/deduplication/timer-manager.util.ts +83 -0
  36. package/src/domains/firestore/utils/pagination.helper.ts +5 -2
  37. package/src/domains/firestore/utils/transaction/transaction.util.ts +8 -2
  38. package/src/index.ts +324 -32
  39. package/src/shared/domain/utils/calculation.util.ts +305 -17
  40. package/src/shared/domain/utils/error-handlers/error-messages.ts +0 -15
  41. package/src/shared/domain/utils/index.ts +5 -0
  42. package/src/shared/infrastructure/config/base/ClientStateManager.ts +82 -0
  43. package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts +136 -20
  44. package/src/shared/infrastructure/config/clients/FirebaseClientSingleton.ts +1 -1
  45. package/src/shared/infrastructure/config/initializers/FirebaseAppInitializer.ts +9 -0
  46. package/src/shared/infrastructure/config/services/FirebaseInitializationService.ts +1 -1
  47. package/src/shared/infrastructure/config/state/FirebaseClientState.ts +14 -36
  48. package/src/application/auth/index.ts +0 -10
  49. package/src/application/auth/use-cases/index.ts +0 -6
  50. package/src/domains/account-deletion/domain/index.ts +0 -8
  51. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts +0 -79
  52. package/src/domains/account-deletion/infrastructure/services/AccountDeletionTypes.ts +0 -32
  53. package/src/domains/auth/domain.ts +0 -16
  54. package/src/domains/auth/infrastructure/config/index.ts +0 -2
  55. package/src/domains/auth/infrastructure/config/initializers/index.ts +0 -1
  56. package/src/domains/auth/infrastructure/services/index.ts +0 -16
  57. package/src/domains/auth/infrastructure/services/utils/index.ts +0 -1
  58. package/src/domains/auth/infrastructure/stores/index.ts +0 -1
  59. package/src/domains/auth/infrastructure/utils/index.ts +0 -1
  60. package/src/domains/auth/infrastructure.ts +0 -11
  61. package/src/domains/auth/presentation/hooks/useAppleAuth.ts +0 -82
  62. package/src/domains/auth/presentation.ts +0 -31
  63. package/src/domains/firestore/domain/entities/Collection.ts +0 -122
  64. package/src/domains/firestore/domain/entities/CollectionFactory.ts +0 -55
  65. package/src/domains/firestore/domain/entities/CollectionHelpers.ts +0 -143
  66. package/src/domains/firestore/domain/entities/CollectionUtils.ts +0 -72
  67. package/src/domains/firestore/domain/entities/CollectionValidation.ts +0 -138
  68. package/src/domains/firestore/domain/index.ts +0 -61
  69. package/src/domains/firestore/domain/value-objects/QueryOptions.ts +0 -143
  70. package/src/domains/firestore/domain/value-objects/QueryOptionsFactory.ts +0 -95
  71. package/src/domains/firestore/domain/value-objects/QueryOptionsHelpers.ts +0 -110
  72. package/src/domains/firestore/domain/value-objects/WhereClause.ts +0 -114
  73. package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts +0 -101
  74. package/src/domains/firestore/domain/value-objects/WhereClauseHelpers.ts +0 -123
  75. package/src/domains/firestore/domain/value-objects/WhereClauseValidation.ts +0 -83
  76. package/src/shared/infrastructure/base/ErrorHandler.ts +0 -81
  77. package/src/shared/infrastructure/base/ServiceBase.ts +0 -62
  78. package/src/shared/infrastructure/base/TypedGuard.ts +0 -131
  79. package/src/shared/infrastructure/base/index.ts +0 -34
  80. package/src/shared/types/firebase.types.ts +0 -274
@@ -1,7 +1,24 @@
1
1
  /**
2
2
  * Calculation Utilities
3
+ * Common mathematical operations used across the codebase
4
+ * Optimized for performance with minimal allocations
3
5
  */
4
6
 
7
+ /**
8
+ * Safely calculates percentage (0-1 range)
9
+ * Optimized: Guards against division by zero
10
+ *
11
+ * @param current - Current value
12
+ * @param limit - Maximum value
13
+ * @returns Percentage between 0 and 1
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const percentage = calculatePercentage(750, 1000); // 0.75
18
+ * const percentage = calculatePercentage(1200, 1000); // 1.0 (capped)
19
+ * const percentage = calculatePercentage(0, 1000); // 0.0
20
+ * ```
21
+ */
5
22
  export function calculatePercentage(current: number, limit: number): number {
6
23
  if (limit <= 0) return 0;
7
24
  if (current <= 0) return 0;
@@ -9,56 +26,327 @@ export function calculatePercentage(current: number, limit: number): number {
9
26
  return current / limit;
10
27
  }
11
28
 
29
+ /**
30
+ * Calculates remaining quota
31
+ * Optimized: Single Math.max call
32
+ *
33
+ * @param current - Current usage
34
+ * @param limit - Maximum limit
35
+ * @returns Remaining amount (minimum 0)
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const remaining = calculateRemaining(250, 1000); // 750
40
+ * const remaining = calculateRemaining(1200, 1000); // 0 (capped)
41
+ * ```
42
+ */
12
43
  export function calculateRemaining(current: number, limit: number): number {
13
44
  return Math.max(0, limit - current);
14
45
  }
15
46
 
47
+ /**
48
+ * Safe floor with minimum value
49
+ * Optimized: Single comparison
50
+ *
51
+ * @param value - Value to floor
52
+ * @param min - Minimum allowed value
53
+ * @returns Floored value, at least min
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const result = safeFloor(5.7, 1); // 5
58
+ * const result = safeFloor(0.3, 1); // 1 (min enforced)
59
+ * const result = safeFloor(-2.5, 0); // 0 (min enforced)
60
+ * ```
61
+ */
16
62
  export function safeFloor(value: number, min: number): number {
17
63
  const floored = Math.floor(value);
18
64
  return floored < min ? min : floored;
19
65
  }
20
66
 
67
+ /**
68
+ * Safe ceil with maximum value
69
+ * Optimized: Single comparison
70
+ *
71
+ * @param value - Value to ceil
72
+ * @param max - Maximum allowed value
73
+ * @returns Ceiled value, at most max
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const result = safeCeil(5.2, 10); // 6
78
+ * const result = safeCeil(9.8, 10); // 10 (max enforced)
79
+ * const result = safeCeil(12.1, 10); // 10 (max enforced)
80
+ * ```
81
+ */
82
+ export function safeCeil(value: number, max: number): number {
83
+ const ceiled = Math.ceil(value);
84
+ return ceiled > max ? max : ceiled;
85
+ }
86
+
87
+ /**
88
+ * Clamp value between min and max
89
+ * Optimized: Efficient without branching
90
+ *
91
+ * @param value - Value to clamp
92
+ * @param min - Minimum value
93
+ * @param max - Maximum value
94
+ * @returns Clamped value
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const result = clamp(5, 0, 10); // 5
99
+ * const result = clamp(-5, 0, 10); // 0
100
+ * const result = clamp(15, 0, 10); // 10
101
+ * ```
102
+ */
103
+ export function clamp(value: number, min: number, max: number): number {
104
+ return Math.max(min, Math.min(value, max));
105
+ }
106
+
107
+ /**
108
+ * Calculate milliseconds between two dates
109
+ * Optimized: Direct subtraction without Date object creation
110
+ *
111
+ * @param date1 - First date (timestamp or Date)
112
+ * @param date2 - Second date (timestamp or Date)
113
+ * @returns Difference in milliseconds (date1 - date2)
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * const diff = diffMs(Date.now(), Date.now() - 3600000); // 3600000
118
+ * ```
119
+ */
21
120
  export function diffMs(date1: number | Date, date2: number | Date): number {
22
- const d1 = typeof date1 === 'number' ? date1 : date1.getTime();
23
- const d2 = typeof date2 === 'number' ? date2 : date2.getTime();
24
- return Math.abs(d1 - d2);
121
+ const ms1 = typeof date1 === 'number' ? date1 : date1.getTime();
122
+ const ms2 = typeof date2 === 'number' ? date2 : date2.getTime();
123
+ return ms1 - ms2;
25
124
  }
26
125
 
126
+ /**
127
+ * Calculate minutes difference between two dates
128
+ * Optimized: Single Math.floor call
129
+ *
130
+ * @param date1 - First date
131
+ * @param date2 - Second date
132
+ * @returns Difference in minutes (floored)
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * const diff = diffMinutes(Date.now(), Date.now() - 180000); // 3
137
+ * ```
138
+ */
27
139
  export function diffMinutes(date1: number | Date, date2: number | Date): number {
28
- return Math.floor(diffMs(date1, date2) / 60000);
140
+ const msDiff = diffMs(date1, date2);
141
+ return Math.floor(msDiff / 60_000);
29
142
  }
30
143
 
144
+ /**
145
+ * Calculate hours difference between two dates
146
+ * Optimized: Single Math.floor call
147
+ *
148
+ * @param date1 - First date
149
+ * @param date2 - Second date
150
+ * @returns Difference in hours (floored)
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * const diff = diffHours(Date.now(), Date.now() - 7200000); // 2
155
+ * ```
156
+ */
31
157
  export function diffHours(date1: number | Date, date2: number | Date): number {
32
- return Math.floor(diffMs(date1, date2) / 3600000);
158
+ const minsDiff = diffMinutes(date1, date2);
159
+ return Math.floor(minsDiff / 60);
33
160
  }
34
161
 
162
+ /**
163
+ * Calculate days difference between two dates
164
+ * Optimized: Single Math.floor call
165
+ *
166
+ * @param date1 - First date
167
+ * @param date2 - Second date
168
+ * @returns Difference in days (floored)
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * const diff = diffDays(Date.now(), Date.now() - 172800000); // 2
173
+ * ```
174
+ */
35
175
  export function diffDays(date1: number | Date, date2: number | Date): number {
36
- return Math.floor(diffMs(date1, date2) / 86400000);
176
+ const hoursDiff = diffHours(date1, date2);
177
+ return Math.floor(hoursDiff / 24);
37
178
  }
38
179
 
39
- export function chunkArray<T>(array: readonly T[], chunkSize: number): T[][] {
40
- if (chunkSize <= 0) return [];
41
- const result: T[][] = [];
42
- for (let i = 0; i < array.length; i += chunkSize) {
43
- result.push(array.slice(i, i + chunkSize) as T[]);
180
+ /**
181
+ * Safe array slice with bounds checking
182
+ * Optimized: Prevents negative indices and out-of-bounds
183
+ *
184
+ * @param array - Array to slice
185
+ * @param start - Start index (inclusive)
186
+ * @param end - End index (exclusive)
187
+ * @returns Sliced array
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const items = [1, 2, 3, 4, 5];
192
+ * const sliced = safeSlice(items, 1, 3); // [2, 3]
193
+ * const sliced = safeSlice(items, -5, 10); // [1, 2, 3, 4, 5] (bounds checked)
194
+ * ```
195
+ */
196
+ export function safeSlice<T>(array: T[], start: number, end?: number): T[] {
197
+ const len = array.length;
198
+
199
+ // Clamp start index
200
+ const safeStart = start < 0 ? 0 : (start >= len ? len : start);
201
+
202
+ // Clamp end index
203
+ const safeEnd = end === undefined
204
+ ? len
205
+ : (end < 0 ? 0 : (end >= len ? len : end));
206
+
207
+ // Only slice if valid range
208
+ if (safeStart >= safeEnd) {
209
+ return [];
44
210
  }
45
- return result;
46
- }
47
211
 
48
- export function safeSlice<T>(array: T[], start: number, end?: number): T[] {
49
- if (start < 0) return array.slice(0, end);
50
- if (start >= array.length) return [];
51
- return array.slice(start, end);
212
+ return array.slice(safeStart, safeEnd);
52
213
  }
53
214
 
215
+ /**
216
+ * Calculate fetch limit for pagination (pageLimit + 1)
217
+ * Optimized: Simple addition
218
+ *
219
+ * @param pageLimit - Requested page size
220
+ * @returns Fetch limit (pageLimit + 1 for hasMore detection)
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * const fetchLimit = getFetchLimit(10); // 11
225
+ * ```
226
+ */
54
227
  export function getFetchLimit(pageLimit: number): number {
55
228
  return pageLimit + 1;
56
229
  }
57
230
 
231
+ /**
232
+ * Calculate if hasMore based on items length and page limit
233
+ * Optimized: Direct comparison
234
+ *
235
+ * @param itemsLength - Total items fetched
236
+ * @param pageLimit - Requested page size
237
+ * @returns true if there are more items
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * const hasMore = hasMore(11, 10); // true
242
+ * const hasMore = hasMore(10, 10); // false
243
+ * ```
244
+ */
58
245
  export function hasMore(itemsLength: number, pageLimit: number): boolean {
59
246
  return itemsLength > pageLimit;
60
247
  }
61
248
 
249
+ /**
250
+ * Calculate result items count (min of itemsLength and pageLimit)
251
+ * Optimized: Single Math.min call
252
+ *
253
+ * @param itemsLength - Total items fetched
254
+ * @param pageLimit - Requested page size
255
+ * @returns Number of items to return
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * const count = getResultCount(11, 10); // 10
260
+ * const count = getResultCount(8, 10); // 8
261
+ * ```
262
+ */
62
263
  export function getResultCount(itemsLength: number, pageLimit: number): number {
63
264
  return Math.min(itemsLength, pageLimit);
64
265
  }
266
+
267
+ /**
268
+ * Chunk array into smaller arrays
269
+ * Optimized: Pre-allocated chunks when size is known
270
+ *
271
+ * @param array - Array to chunk
272
+ * @param chunkSize - Size of each chunk
273
+ * @returns Array of chunks
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const chunks = chunkArray([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
278
+ * ```
279
+ */
280
+ export function chunkArray<T>(array: readonly T[], chunkSize: number): T[][] {
281
+ if (chunkSize <= 0) {
282
+ throw new Error('chunkSize must be greater than 0');
283
+ }
284
+
285
+ const chunks: T[][] = [];
286
+ const len = array.length;
287
+
288
+ for (let i = 0; i < len; i += chunkSize) {
289
+ const end = Math.min(i + chunkSize, len);
290
+ chunks.push(array.slice(i, end) as T[]);
291
+ }
292
+
293
+ return chunks;
294
+ }
295
+
296
+ /**
297
+ * Sum array of numbers
298
+ * Optimized: Direct for-loop (faster than reduce)
299
+ *
300
+ * @param numbers - Array of numbers to sum
301
+ * @returns Sum of all numbers
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * const sum = sumArray([1, 2, 3, 4, 5]); // 15
306
+ * ```
307
+ */
308
+ export function sumArray(numbers: number[]): number {
309
+ let sum = 0;
310
+ for (let i = 0; i < numbers.length; i++) {
311
+ const num = numbers[i];
312
+ if (num !== undefined && num !== null) {
313
+ sum += num;
314
+ }
315
+ }
316
+ return sum;
317
+ }
318
+
319
+ /**
320
+ * Average of array of numbers
321
+ * Optimized: Single-pass calculation
322
+ *
323
+ * @param numbers - Array of numbers
324
+ * @returns Average value
325
+ *
326
+ * @example
327
+ * ```typescript
328
+ * const avg = averageArray([1, 2, 3, 4, 5]); // 3
329
+ * ```
330
+ */
331
+ export function averageArray(numbers: number[]): number {
332
+ if (numbers.length === 0) return 0;
333
+ return sumArray(numbers) / numbers.length;
334
+ }
335
+
336
+ /**
337
+ * Round to decimal places
338
+ * Optimized: Efficient rounding without string conversion
339
+ *
340
+ * @param value - Value to round
341
+ * @param decimals - Number of decimal places
342
+ * @returns Rounded value
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * const rounded = roundToDecimals(3.14159, 2); // 3.14
347
+ * ```
348
+ */
349
+ export function roundToDecimals(value: number, decimals: number): number {
350
+ const multiplier = Math.pow(10, decimals);
351
+ return Math.round(value * multiplier) / multiplier;
352
+ }
@@ -13,11 +13,6 @@ export const ERROR_MESSAGES = {
13
13
  INVALID_CREDENTIALS: 'Invalid credentials provided',
14
14
  USER_CHANGED: 'User changed during operation',
15
15
  OPERATION_IN_PROGRESS: 'Operation already in progress',
16
- USER_NOT_FOUND: 'User not found',
17
- EMAIL_ALREADY_IN_USE: 'Email is already in use',
18
- TOO_MANY_REQUESTS: 'Too many requests. Please try again later',
19
- USER_DISABLED: 'This account has been disabled',
20
- GENERIC_ERROR: 'Authentication error occurred',
21
16
  },
22
17
  FIRESTORE: {
23
18
  NOT_INITIALIZED: 'Firestore is not initialized',
@@ -30,12 +25,6 @@ export const ERROR_MESSAGES = {
30
25
  PERMISSION_DENIED: 'Permission denied',
31
26
  TRANSACTION_FAILED: 'Transaction failed',
32
27
  NETWORK_ERROR: 'Network error occurred',
33
- NOT_FOUND: 'Document not found',
34
- UNAVAILABLE: 'Firestore service is unavailable',
35
- GENERIC_ERROR: 'Firestore error occurred',
36
- },
37
- STORAGE: {
38
- GENERIC_ERROR: 'Storage error occurred',
39
28
  },
40
29
  REPOSITORY: {
41
30
  DESTROYED: 'Repository has been destroyed',
@@ -54,7 +43,3 @@ export const ERROR_MESSAGES = {
54
43
  },
55
44
  } as const;
56
45
 
57
- export function getQuotaErrorMessage(): string {
58
- return ERROR_MESSAGES.FIRESTORE.QUOTA_EXCEEDED;
59
- }
60
-
@@ -37,6 +37,8 @@ export {
37
37
  calculatePercentage,
38
38
  calculateRemaining,
39
39
  safeFloor,
40
+ safeCeil,
41
+ clamp,
40
42
  diffMs,
41
43
  diffMinutes,
42
44
  diffHours,
@@ -46,4 +48,7 @@ export {
46
48
  hasMore,
47
49
  getResultCount,
48
50
  chunkArray,
51
+ sumArray,
52
+ averageArray,
53
+ roundToDecimals,
49
54
  } from './calculation.util';
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Client State Manager
3
+ *
4
+ * Generic state management for Firebase service clients.
5
+ * Provides centralized state tracking for initialization status, errors, and instances.
6
+ *
7
+ * @template TInstance - The service instance type (e.g., FirebaseApp, Firestore, Auth)
8
+ */
9
+
10
+ interface ClientState<TInstance> {
11
+ instance: TInstance | null;
12
+ initializationError: string | null;
13
+ isInitialized: boolean;
14
+ }
15
+
16
+ /**
17
+ * Generic client state manager
18
+ * Handles initialization state, error tracking, and instance management
19
+ */
20
+ export class ClientStateManager<TInstance> {
21
+ private state: ClientState<TInstance>;
22
+
23
+ constructor() {
24
+ this.state = {
25
+ instance: null,
26
+ initializationError: null,
27
+ isInitialized: false,
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Get the current instance
33
+ */
34
+ getInstance(): TInstance | null {
35
+ return this.state.instance;
36
+ }
37
+
38
+ /**
39
+ * Set the instance
40
+ */
41
+ setInstance(instance: TInstance | null): void {
42
+ this.state.instance = instance;
43
+ this.state.isInitialized = instance !== null;
44
+ }
45
+
46
+ /**
47
+ * Check if the service is initialized
48
+ */
49
+ isInitialized(): boolean {
50
+ return this.state.isInitialized;
51
+ }
52
+
53
+ /**
54
+ * Get the initialization error if any
55
+ */
56
+ getInitializationError(): string | null {
57
+ return this.state.initializationError;
58
+ }
59
+
60
+ /**
61
+ * Set the initialization error
62
+ */
63
+ setInitializationError(error: string | null): void {
64
+ this.state.initializationError = error;
65
+ }
66
+
67
+ /**
68
+ * Reset the state
69
+ */
70
+ reset(): void {
71
+ this.state.instance = null;
72
+ this.state.initializationError = null;
73
+ this.state.isInitialized = false;
74
+ }
75
+
76
+ /**
77
+ * Get the current state (read-only)
78
+ */
79
+ getState(): Readonly<ClientState<TInstance>> {
80
+ return this.state;
81
+ }
82
+ }
@@ -1,39 +1,155 @@
1
1
  /**
2
- * Service Client Singleton Base
3
- * Base singleton pattern for service clients
2
+ * Service Client Singleton Base Class
4
3
  *
5
- * Max lines: 150 (enforced for maintainability)
4
+ * Provides a generic singleton pattern for Firebase service clients.
5
+ * Eliminates code duplication across FirebaseClient, FirestoreClient, FirebaseAuthClient.
6
+ *
7
+ * Features:
8
+ * - Generic singleton pattern
9
+ * - Initialization state management
10
+ * - Error handling and tracking
11
+ * - Automatic cleanup
12
+ *
13
+ * @template TInstance - The service instance type (e.g., Firestore, Auth)
14
+ * @template TConfig - The configuration type (optional)
15
+ */
16
+
17
+ interface ServiceClientState<TInstance> {
18
+ instance: TInstance | null;
19
+ initializationError: string | null;
20
+ isInitialized: boolean;
21
+ }
22
+
23
+ interface ServiceClientOptions<TInstance, TConfig = unknown> {
24
+ serviceName: string;
25
+ initializer?: (config?: TConfig) => TInstance | null;
26
+ autoInitializer?: () => TInstance | null;
27
+ }
28
+
29
+ /**
30
+ * Generic service client singleton base class
31
+ * Provides common initialization, state management, and error handling
6
32
  */
33
+ export class ServiceClientSingleton<TInstance, TConfig = unknown> {
34
+ protected state: ServiceClientState<TInstance>;
35
+ private readonly options: ServiceClientOptions<TInstance, TConfig>;
36
+ private initInProgress = false;
37
+
38
+ constructor(options: ServiceClientOptions<TInstance, TConfig>) {
39
+ this.options = options;
40
+ this.state = {
41
+ instance: null,
42
+ initializationError: null,
43
+ isInitialized: false,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Initialize the service with optional configuration
49
+ */
50
+ initialize(config?: TConfig): TInstance | null {
51
+ if (this.state.isInitialized && this.state.instance) {
52
+ return this.state.instance;
53
+ }
54
+
55
+ if (this.state.initializationError) {
56
+ return null;
57
+ }
58
+
59
+ // Prevent concurrent initialization attempts
60
+ if (this.initInProgress) {
61
+ return null;
62
+ }
7
63
 
8
- export abstract class ServiceClientSingleton<TInstance, TConfig = unknown> {
9
- protected instance: TInstance | null = null;
10
- protected initializationError: Error | null = null;
64
+ this.initInProgress = true;
65
+ try {
66
+ const instance = this.options.initializer ? this.options.initializer(config) : null;
67
+ if (instance) {
68
+ this.state.instance = instance;
69
+ this.state.isInitialized = true;
70
+ }
71
+ return instance;
72
+ } catch (error) {
73
+ const errorMessage = error instanceof Error ? error.message : `Failed to initialize ${this.options.serviceName}`;
74
+ this.state.initializationError = errorMessage;
75
+ return null;
76
+ } finally {
77
+ this.initInProgress = false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get the service instance, auto-initializing if needed
83
+ */
84
+ getInstance(autoInit: boolean = false): TInstance | null {
85
+ if (this.state.instance) {
86
+ return this.state.instance;
87
+ }
11
88
 
12
- protected constructor() {}
89
+ if (this.state.initializationError) {
90
+ return null;
91
+ }
13
92
 
14
- abstract initialize(config?: TConfig): Promise<TInstance> | TInstance;
93
+ // Prevent concurrent auto-initialization attempts
94
+ if (this.initInProgress) {
95
+ return null;
96
+ }
15
97
 
16
- getInstance(): TInstance {
17
- if (!this.instance) {
18
- throw new Error('Service not initialized. Call initialize() first.');
98
+ if (autoInit && this.options.autoInitializer) {
99
+ this.initInProgress = true;
100
+ try {
101
+ const instance = this.options.autoInitializer();
102
+ if (instance) {
103
+ this.state.instance = instance;
104
+ this.state.isInitialized = true;
105
+ }
106
+ return instance;
107
+ } catch (error) {
108
+ const errorMessage = error instanceof Error ? error.message : `Failed to initialize ${this.options.serviceName}`;
109
+ this.state.initializationError = errorMessage;
110
+ } finally {
111
+ this.initInProgress = false;
112
+ }
19
113
  }
20
- return this.instance;
114
+
115
+ return null;
21
116
  }
22
117
 
118
+ /**
119
+ * Check if the service is initialized
120
+ */
23
121
  isInitialized(): boolean {
24
- return this.instance !== null;
122
+ return this.state.isInitialized;
25
123
  }
26
124
 
27
- getInitializationError(): Error | null {
28
- return this.initializationError;
125
+ /**
126
+ * Get the initialization error if any
127
+ */
128
+ getInitializationError(): string | null {
129
+ return this.state.initializationError;
29
130
  }
30
131
 
31
- setError(message: string): void {
32
- this.initializationError = new Error(message);
132
+ /**
133
+ * Reset the service state
134
+ */
135
+ reset(): void {
136
+ this.state.instance = null;
137
+ this.state.initializationError = null;
138
+ this.state.isInitialized = false;
139
+ this.initInProgress = false;
33
140
  }
34
141
 
35
- reset(): void {
36
- this.instance = null;
37
- this.initializationError = null;
142
+ /**
143
+ * Get the current instance without initialization
144
+ */
145
+ protected getCurrentInstance(): TInstance | null {
146
+ return this.state.instance;
147
+ }
148
+
149
+ /**
150
+ * Set initialization error
151
+ */
152
+ protected setError(error: string): void {
153
+ this.state.initializationError = error;
38
154
  }
39
155
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type { FirebaseConfig } from '../../../domain/value-objects/FirebaseConfig';
7
7
  import type { IFirebaseClient } from '../../../../application/ports/IFirebaseClient';
8
- import type { FirebaseApp } from 'firebase/app';
8
+ import type { FirebaseApp } from '../initializers/FirebaseAppInitializer';
9
9
  import { FirebaseClientState } from '../state/FirebaseClientState';
10
10
  import { FirebaseInitializationOrchestrator } from '../orchestrators/FirebaseInitializationOrchestrator';
11
11
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Firebase App Initializer
3
+ *
4
+ * Single Responsibility: Expose Firebase App type
5
+ */
6
+
7
+ import type { FirebaseApp as FirebaseAppType } from 'firebase/app';
8
+
9
+ export type FirebaseApp = FirebaseAppType;
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { FirebaseConfig } from '../../../domain/value-objects/FirebaseConfig';
7
- import type { FirebaseApp } from 'firebase/app';
7
+ import type { FirebaseApp } from '../initializers/FirebaseAppInitializer';
8
8
  import { FirebaseClientSingleton } from '../clients/FirebaseClientSingleton';
9
9
  import { loadFirebaseConfig } from '../FirebaseConfigLoader';
10
10