@umituz/react-native-firebase 1.13.20 → 1.13.23

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": "1.13.20",
3
+ "version": "1.13.23",
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",
@@ -52,4 +52,4 @@
52
52
  "README.md",
53
53
  "LICENSE"
54
54
  ]
55
- }
55
+ }
@@ -1,28 +1,26 @@
1
1
  /**
2
- * Quota Metrics Entity
3
- * Domain entity for tracking Firestore quota usage
2
+ * Quota Metrics Types
4
3
  */
5
4
 
6
5
  export interface QuotaMetrics {
7
- readCount: number;
8
- writeCount: number;
9
- deleteCount: number;
10
- timestamp: number;
6
+ readCount: number;
7
+ writeCount: number;
8
+ deleteCount: number;
9
+ lastResetDate: string;
11
10
  }
12
11
 
13
12
  export interface QuotaLimits {
14
- dailyReadLimit: number;
15
- dailyWriteLimit: number;
16
- dailyDeleteLimit: number;
13
+ dailyReadLimit: number;
14
+ dailyWriteLimit: number;
15
+ dailyDeleteLimit: number;
17
16
  }
18
17
 
19
18
  export interface QuotaStatus {
20
- metrics: QuotaMetrics;
21
- limits: QuotaLimits;
22
- readPercentage: number;
23
- writePercentage: number;
24
- deletePercentage: number;
25
- isNearLimit: boolean;
26
- isOverLimit: boolean;
19
+ metrics: QuotaMetrics;
20
+ limits: QuotaLimits;
21
+ readPercentage: number;
22
+ writePercentage: number;
23
+ deletePercentage: number;
24
+ isNearLimit: boolean;
25
+ isOverLimit: boolean;
27
26
  }
28
-
@@ -1,30 +1,28 @@
1
1
  /**
2
- * Request Log Entity
3
- * Domain entity for tracking Firestore requests
2
+ * Request Log Types
4
3
  */
5
4
 
6
5
  export type RequestType = 'read' | 'write' | 'delete' | 'listener';
7
6
 
8
7
  export interface RequestLog {
9
- id: string;
10
- type: RequestType;
11
- collection: string;
12
- documentId?: string;
13
- timestamp: number;
14
- duration?: number;
15
- success: boolean;
16
- error?: string;
17
- cached: boolean;
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;
18
17
  }
19
18
 
20
19
  export interface RequestStats {
21
- totalRequests: number;
22
- readRequests: number;
23
- writeRequests: number;
24
- deleteRequests: number;
25
- listenerRequests: number;
26
- cachedRequests: number;
27
- failedRequests: number;
28
- averageDuration: number;
20
+ totalRequests: number;
21
+ readRequests: number;
22
+ writeRequests: number;
23
+ deleteRequests: number;
24
+ listenerRequests: number;
25
+ cachedRequests: number;
26
+ failedRequests: number;
27
+ averageDuration: number;
29
28
  }
30
-
@@ -1,17 +1,6 @@
1
1
  /**
2
2
  * React Native Firestore Module
3
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
4
  */
16
5
 
17
6
  // =============================================================================
@@ -98,16 +87,6 @@ export {
98
87
  createDocumentMapper,
99
88
  } from './utils/document-mapper.helper';
100
89
 
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
90
  // =============================================================================
112
91
  // UTILS - Path Resolver
113
92
  // =============================================================================
@@ -148,6 +127,16 @@ export type {
148
127
 
149
128
  export { QuotaCalculator } from './domain/services/QuotaCalculator';
150
129
 
130
+ // =============================================================================
131
+ // UTILS - Quota Error Detection
132
+ // =============================================================================
133
+
134
+ export {
135
+ isQuotaError,
136
+ isRetryableError,
137
+ getQuotaErrorMessage,
138
+ } from './utils/quota-error-detector.util';
139
+
151
140
  // =============================================================================
152
141
  // INFRASTRUCTURE LAYER - Middleware
153
142
  // =============================================================================
@@ -166,11 +155,6 @@ export {
166
155
  // INFRASTRUCTURE LAYER - Services
167
156
  // =============================================================================
168
157
 
169
- export {
170
- QuotaMonitorService,
171
- quotaMonitorService,
172
- } from './infrastructure/services/QuotaMonitorService';
173
-
174
158
  export {
175
159
  RequestLoggerService,
176
160
  requestLoggerService,
@@ -3,163 +3,101 @@
3
3
  * Tracks Firestore operations for quota monitoring
4
4
  */
5
5
 
6
- import { quotaMonitorService } from '../services/QuotaMonitorService';
7
- import { requestLoggerService } from '../services/RequestLoggerService';
8
- import type { RequestType } from '../../domain/entities/RequestLog';
6
+ declare const __DEV__: boolean;
9
7
 
10
- interface TrackedOperation {
11
- type: RequestType;
12
- collection: string;
13
- documentId?: string;
14
- count: number;
15
- cached?: boolean;
8
+ interface OperationInfo {
9
+ type: 'read' | 'write' | 'delete';
10
+ collection: string;
11
+ count: number;
12
+ cached: boolean;
16
13
  }
17
14
 
18
15
  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
- }
16
+ private readCount = 0;
17
+ private writeCount = 0;
18
+ private deleteCount = 0;
31
19
 
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
- }
20
+ /**
21
+ * Track a Firestore operation
22
+ */
23
+ async trackOperation<T>(
24
+ info: OperationInfo,
25
+ operation: () => Promise<T>
26
+ ): Promise<T> {
27
+ const result = await operation();
49
28
 
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
- }
29
+ switch (info.type) {
30
+ case 'read':
31
+ if (!info.cached) {
32
+ this.readCount += info.count;
33
+ }
34
+ break;
35
+ case 'write':
36
+ this.writeCount += info.count;
37
+ break;
38
+ case 'delete':
39
+ this.deleteCount += info.count;
40
+ break;
41
+ }
67
42
 
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
- }
43
+ if (__DEV__) {
44
+ console.log(`[QuotaTracking] ${info.type}: ${info.collection} (${info.count})`);
45
+ }
80
46
 
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
- }
47
+ return result;
48
+ }
99
49
 
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();
50
+ /**
51
+ * Track read operation
52
+ */
53
+ trackRead(collection: string, count: number = 1, cached: boolean = false): void {
54
+ if (!cached) {
55
+ this.readCount += count;
56
+ }
57
+ if (__DEV__) {
58
+ console.log(`[QuotaTracking] read: ${collection} (${count})`);
59
+ }
60
+ }
108
61
 
109
- try {
110
- const result = await operationFn();
111
- const duration = Date.now() - startTime;
62
+ /**
63
+ * Track write operation
64
+ */
65
+ trackWrite(collection: string, _documentId?: string, count: number = 1): void {
66
+ this.writeCount += count;
67
+ if (__DEV__) {
68
+ console.log(`[QuotaTracking] write: ${collection} (${count})`);
69
+ }
70
+ }
112
71
 
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
- }
72
+ /**
73
+ * Track delete operation
74
+ */
75
+ trackDelete(collection: string, _documentId?: string, count: number = 1): void {
76
+ this.deleteCount += count;
77
+ if (__DEV__) {
78
+ console.log(`[QuotaTracking] delete: ${collection} (${count})`);
79
+ }
80
+ }
144
81
 
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
- });
82
+ /**
83
+ * Get current counts
84
+ */
85
+ getCounts(): { reads: number; writes: number; deletes: number } {
86
+ return {
87
+ reads: this.readCount,
88
+ writes: this.writeCount,
89
+ deletes: this.deleteCount,
90
+ };
91
+ }
158
92
 
159
- throw error;
93
+ /**
94
+ * Reset counts
95
+ */
96
+ reset(): void {
97
+ this.readCount = 0;
98
+ this.writeCount = 0;
99
+ this.deleteCount = 0;
160
100
  }
161
- }
162
101
  }
163
102
 
164
103
  export const quotaTrackingMiddleware = new QuotaTrackingMiddleware();
165
-
@@ -1,100 +1,59 @@
1
1
  /**
2
- * Quota Error Detector Utility
3
- * Single Responsibility: Detect Firebase quota errors
4
- *
5
- * Firebase quota limits:
6
- * - Free tier: 50K reads/day, 20K writes/day, 20K deletes/day
7
- * - Blaze plan: Pay as you go, higher limits
8
- *
9
- * Quota errors are NOT retryable - quota won't increase by retrying
2
+ * Quota Error Detection Utilities
10
3
  */
11
4
 
5
+ const QUOTA_ERROR_CODES = [
6
+ 'resource-exhausted',
7
+ 'quota-exceeded',
8
+ 'RESOURCE_EXHAUSTED',
9
+ ];
10
+
11
+ const QUOTA_ERROR_MESSAGES = [
12
+ 'quota',
13
+ 'exceeded',
14
+ 'limit',
15
+ 'too many requests',
16
+ ];
17
+
12
18
  /**
13
- * Check if error is a Firebase quota error
14
- * Quota errors indicate daily read/write/delete limits are exceeded
15
- *
16
- * @param error - Error object to check
17
- * @returns true if error is a quota error
19
+ * Check if error is a Firestore quota error
18
20
  */
19
21
  export function isQuotaError(error: unknown): boolean {
20
- if (!error || typeof error !== "object") {
21
- return false;
22
- }
22
+ if (!error) return false;
23
23
 
24
- const firebaseError = error as { code?: string; message?: string; name?: string };
24
+ const errorObj = error as Record<string, unknown>;
25
+ const code = errorObj.code as string | undefined;
26
+ const message = errorObj.message as string | undefined;
25
27
 
26
- // Check error code
27
- if (firebaseError.code === "resource-exhausted") {
28
- return true;
29
- }
28
+ if (code && QUOTA_ERROR_CODES.some((c) => code.includes(c))) {
29
+ return true;
30
+ }
30
31
 
31
- // Check error message
32
- const errorMessage = firebaseError.message?.toLowerCase() || "";
33
- if (
34
- errorMessage.includes("quota") ||
35
- errorMessage.includes("quota exceeded") ||
36
- errorMessage.includes("resource-exhausted") ||
37
- errorMessage.includes("daily limit")
38
- ) {
39
- return true;
40
- }
32
+ if (message) {
33
+ const lowerMessage = message.toLowerCase();
34
+ return QUOTA_ERROR_MESSAGES.some((m) => lowerMessage.includes(m));
35
+ }
41
36
 
42
- // Check error name
43
- const errorName = firebaseError.name?.toLowerCase() || "";
44
- if (errorName.includes("quota") || errorName.includes("resource-exhausted")) {
45
- return true;
46
- }
47
-
48
- return false;
37
+ return false;
49
38
  }
50
39
 
51
40
  /**
52
41
  * Check if error is retryable
53
- * Quota errors are NOT retryable
54
- *
55
- * @param error - Error object to check
56
- * @returns true if error is retryable
57
42
  */
58
43
  export function isRetryableError(error: unknown): boolean {
59
- // Quota errors are NOT retryable
60
- if (isQuotaError(error)) {
61
- return false;
62
- }
44
+ if (!error) return false;
63
45
 
64
- if (!error || typeof error !== "object") {
65
- return false;
66
- }
67
-
68
- const firebaseError = error as { code?: string; message?: string };
69
-
70
- // Firestore transaction conflicts are retryable
71
- if (firebaseError.code === "failed-precondition") {
72
- return true;
73
- }
46
+ const errorObj = error as Record<string, unknown>;
47
+ const code = errorObj.code as string | undefined;
74
48
 
75
- // Network errors are retryable
76
- if (
77
- firebaseError.code === "unavailable" ||
78
- firebaseError.code === "deadline-exceeded"
79
- ) {
80
- return true;
81
- }
49
+ const retryableCodes = ['unavailable', 'deadline-exceeded', 'aborted'];
82
50
 
83
- // Timeout errors are retryable
84
- const errorMessage = firebaseError.message?.toLowerCase() || "";
85
- if (errorMessage.includes("timeout")) {
86
- return true;
87
- }
88
-
89
- return false;
51
+ return code ? retryableCodes.some((c) => code.includes(c)) : false;
90
52
  }
91
53
 
92
54
  /**
93
55
  * Get user-friendly quota error message
94
- *
95
- * @returns User-friendly error message
96
56
  */
97
57
  export function getQuotaErrorMessage(): string {
98
- return "Firebase quota exceeded. Please try again later or upgrade your Firebase plan.";
58
+ return 'Daily quota exceeded. Please try again tomorrow or upgrade your plan.';
99
59
  }
100
-
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Firebase Storage Deleter
3
+ * Handles image deletion from Firebase Storage
4
+ */
5
+
6
+ import { getStorage, ref, deleteObject } from "firebase/storage";
7
+ import { getFirebaseApp } from "../infrastructure/config/FirebaseClient";
8
+ import type { DeleteResult } from "./types";
9
+
10
+ declare const __DEV__: boolean;
11
+
12
+ /**
13
+ * Extract storage path from Firebase download URL
14
+ */
15
+ function extractStoragePath(downloadUrl: string): string | null {
16
+ if (!downloadUrl.startsWith("https://")) {
17
+ return downloadUrl;
18
+ }
19
+
20
+ try {
21
+ const urlObj = new URL(downloadUrl);
22
+ const pathMatch = urlObj.pathname.match(/\/o\/(.+)/);
23
+
24
+ if (!pathMatch) {
25
+ return null;
26
+ }
27
+
28
+ return decodeURIComponent(pathMatch[1]);
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ function getStorageInstance() {
35
+ const app = getFirebaseApp();
36
+ return app ? getStorage(app) : null;
37
+ }
38
+
39
+ /**
40
+ * Delete image from Firebase Storage
41
+ * Accepts either a download URL or storage path
42
+ */
43
+ export async function deleteStorageFile(
44
+ downloadUrlOrPath: string
45
+ ): Promise<DeleteResult> {
46
+ if (__DEV__) {
47
+ console.log("[StorageDeleter] Deleting", { url: downloadUrlOrPath });
48
+ }
49
+
50
+ const storagePath = extractStoragePath(downloadUrlOrPath);
51
+
52
+ if (!storagePath) {
53
+ if (__DEV__) {
54
+ console.error("[StorageDeleter] Invalid URL", {
55
+ url: downloadUrlOrPath,
56
+ });
57
+ }
58
+ return { success: false, storagePath: downloadUrlOrPath };
59
+ }
60
+
61
+ try {
62
+ const storage = getStorageInstance();
63
+ if (!storage) {
64
+ throw new Error("Firebase Storage not initialized");
65
+ }
66
+
67
+ const storageRef = ref(storage, storagePath);
68
+ await deleteObject(storageRef);
69
+
70
+ if (__DEV__) {
71
+ console.log("[StorageDeleter] Deleted successfully", {
72
+ storagePath,
73
+ });
74
+ }
75
+
76
+ return { success: true, storagePath };
77
+ } catch {
78
+ return { success: false, storagePath };
79
+ }
80
+ }
@@ -1 +1,7 @@
1
- export * from "./uploader";
1
+ /**
2
+ * Firebase Storage Module
3
+ */
4
+
5
+ export type { UploadResult, UploadOptions, DeleteResult } from "./types";
6
+ export { uploadBase64Image, uploadFile, getMimeType } from "./uploader";
7
+ export { deleteStorageFile } from "./deleter";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Firebase Storage Types
3
+ * Shared types for storage operations
4
+ */
5
+
6
+ export interface UploadResult {
7
+ downloadUrl: string;
8
+ storagePath: string;
9
+ }
10
+
11
+ export interface UploadOptions {
12
+ mimeType?: string;
13
+ customMetadata?: Record<string, string>;
14
+ }
15
+
16
+ export interface DeleteResult {
17
+ success: boolean;
18
+ storagePath: string;
19
+ }
@@ -11,20 +11,10 @@ import {
11
11
  type UploadMetadata,
12
12
  } from "firebase/storage";
13
13
  import { getFirebaseApp } from "../infrastructure/config/FirebaseClient";
14
+ import type { UploadResult, UploadOptions } from "./types";
14
15
 
15
16
  declare const __DEV__: boolean;
16
17
 
17
- export interface UploadResult {
18
- downloadUrl: string;
19
- storagePath: string;
20
- metadata?: any;
21
- }
22
-
23
- export interface UploadOptions {
24
- mimeType?: string;
25
- metadata?: any;
26
- }
27
-
28
18
  /**
29
19
  * Extract MIME type from base64 data URL or return default
30
20
  */
@@ -79,16 +69,22 @@ export async function uploadBase64Image(
79
69
 
80
70
  const metadata: UploadMetadata = {
81
71
  contentType: mimeType,
82
- customMetadata: options?.metadata,
72
+ customMetadata: options?.customMetadata,
83
73
  };
84
74
 
85
- const snapshot = await uploadBytes(storageRef, blob, metadata);
75
+ await uploadBytes(storageRef, blob, metadata);
86
76
  const downloadUrl = await getDownloadURL(storageRef);
87
77
 
78
+ if (__DEV__) {
79
+ console.log("[StorageUploader] Upload complete", {
80
+ storagePath,
81
+ downloadUrl,
82
+ });
83
+ }
84
+
88
85
  return {
89
86
  downloadUrl,
90
87
  storagePath,
91
- metadata: snapshot.metadata,
92
88
  };
93
89
  }
94
90
 
@@ -118,15 +114,21 @@ export async function uploadFile(
118
114
 
119
115
  const metadata: UploadMetadata = {
120
116
  contentType: options?.mimeType ?? "image/jpeg",
121
- customMetadata: options?.metadata,
117
+ customMetadata: options?.customMetadata,
122
118
  };
123
119
 
124
- const snapshot = await uploadBytes(storageRef, blob, metadata);
120
+ await uploadBytes(storageRef, blob, metadata);
125
121
  const downloadUrl = await getDownloadURL(storageRef);
126
122
 
123
+ if (__DEV__) {
124
+ console.log("[StorageUploader] Upload complete", {
125
+ storagePath,
126
+ downloadUrl,
127
+ });
128
+ }
129
+
127
130
  return {
128
131
  downloadUrl,
129
132
  storagePath,
130
- metadata: snapshot.metadata,
131
133
  };
132
134
  }
@@ -1,108 +0,0 @@
1
- /**
2
- * Quota Monitor Service
3
- * Infrastructure service for monitoring Firestore quota usage
4
- */
5
-
6
- import type { QuotaMetrics, QuotaLimits, QuotaStatus } from '../../domain/entities/QuotaMetrics';
7
- import { QuotaCalculator } from '../../domain/services/QuotaCalculator';
8
-
9
- export class QuotaMonitorService {
10
- private metrics: QuotaMetrics = {
11
- readCount: 0,
12
- writeCount: 0,
13
- deleteCount: 0,
14
- timestamp: Date.now(),
15
- };
16
-
17
- private limits: QuotaLimits = QuotaCalculator.getDefaultLimits();
18
- private listeners: Set<(status: QuotaStatus) => void> = new Set();
19
-
20
- /**
21
- * Set quota limits
22
- */
23
- setLimits(limits: Partial<QuotaLimits>): void {
24
- this.limits = { ...this.limits, ...limits };
25
- }
26
-
27
- /**
28
- * Increment read count
29
- */
30
- incrementRead(count: number = 1): void {
31
- this.metrics.readCount += count;
32
- this.notifyListeners();
33
- }
34
-
35
- /**
36
- * Increment write count
37
- */
38
- incrementWrite(count: number = 1): void {
39
- this.metrics.writeCount += count;
40
- this.notifyListeners();
41
- }
42
-
43
- /**
44
- * Increment delete count
45
- */
46
- incrementDelete(count: number = 1): void {
47
- this.metrics.deleteCount += count;
48
- this.notifyListeners();
49
- }
50
-
51
- /**
52
- * Get current metrics
53
- */
54
- getMetrics(): QuotaMetrics {
55
- return { ...this.metrics };
56
- }
57
-
58
- /**
59
- * Get current status
60
- */
61
- getStatus(): QuotaStatus {
62
- return QuotaCalculator.calculateStatus(this.metrics, this.limits);
63
- }
64
-
65
- /**
66
- * Reset metrics
67
- */
68
- resetMetrics(): void {
69
- this.metrics = {
70
- readCount: 0,
71
- writeCount: 0,
72
- deleteCount: 0,
73
- timestamp: Date.now(),
74
- };
75
- this.notifyListeners();
76
- }
77
-
78
- /**
79
- * Add status change listener
80
- */
81
- addListener(listener: (status: QuotaStatus) => void): () => void {
82
- this.listeners.add(listener);
83
- return () => {
84
- this.listeners.delete(listener);
85
- };
86
- }
87
-
88
- /**
89
- * Notify all listeners
90
- */
91
- private notifyListeners(): void {
92
- const status = this.getStatus();
93
- this.listeners.forEach((listener) => {
94
- try {
95
- listener(status);
96
- } catch (error) {
97
- /* eslint-disable-next-line no-console */
98
- if (__DEV__) {
99
- /* eslint-disable-next-line no-console */
100
- console.error('[QuotaMonitor] Listener error:', error);
101
- }
102
- }
103
- });
104
- }
105
- }
106
-
107
- export const quotaMonitorService = new QuotaMonitorService();
108
-