@umituz/react-native-firebase 1.13.2 → 1.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -2
- package/src/auth/domain/entities/AnonymousUser.ts +44 -0
- package/src/auth/domain/errors/FirebaseAuthError.ts +18 -0
- package/src/auth/domain/value-objects/FirebaseAuthConfig.ts +45 -0
- package/src/auth/index.ts +146 -0
- package/src/auth/infrastructure/config/FirebaseAuthClient.ts +210 -0
- package/src/auth/infrastructure/config/initializers/FirebaseAuthInitializer.ts +148 -0
- package/src/auth/infrastructure/services/account-deletion.service.ts +250 -0
- package/src/auth/infrastructure/services/anonymous-auth.service.ts +135 -0
- package/src/auth/infrastructure/services/apple-auth.service.ts +146 -0
- package/src/auth/infrastructure/services/auth-guard.service.ts +97 -0
- package/src/auth/infrastructure/services/auth-utils.service.ts +168 -0
- package/src/auth/infrastructure/services/firestore-utils.service.ts +155 -0
- package/src/auth/infrastructure/services/google-auth.service.ts +100 -0
- package/src/auth/infrastructure/services/reauthentication.service.ts +216 -0
- package/src/auth/presentation/hooks/useAnonymousAuth.ts +201 -0
- package/src/auth/presentation/hooks/useFirebaseAuth.ts +84 -0
- package/src/auth/presentation/hooks/useSocialAuth.ts +162 -0
- package/src/firestore/__tests__/BaseRepository.test.ts +133 -0
- package/src/firestore/__tests__/QueryDeduplicationMiddleware.test.ts +147 -0
- package/src/firestore/__tests__/mocks/react-native-firebase.ts +23 -0
- package/src/firestore/__tests__/setup.ts +36 -0
- package/src/firestore/domain/constants/QuotaLimits.ts +97 -0
- package/src/firestore/domain/entities/QuotaMetrics.ts +28 -0
- package/src/firestore/domain/entities/RequestLog.ts +30 -0
- package/src/firestore/domain/errors/FirebaseFirestoreError.ts +52 -0
- package/src/firestore/domain/services/QuotaCalculator.ts +70 -0
- package/src/firestore/index.ts +174 -0
- package/src/firestore/infrastructure/config/FirestoreClient.ts +181 -0
- package/src/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +46 -0
- package/src/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +153 -0
- package/src/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +165 -0
- package/src/firestore/infrastructure/repositories/BasePaginatedRepository.ts +90 -0
- package/src/firestore/infrastructure/repositories/BaseQueryRepository.ts +80 -0
- package/src/firestore/infrastructure/repositories/BaseRepository.ts +147 -0
- package/src/firestore/infrastructure/services/QuotaMonitorService.ts +108 -0
- package/src/firestore/infrastructure/services/RequestLoggerService.ts +139 -0
- package/src/firestore/types/pagination.types.ts +60 -0
- package/src/firestore/utils/dateUtils.ts +31 -0
- package/src/firestore/utils/document-mapper.helper.ts +145 -0
- package/src/firestore/utils/pagination.helper.ts +93 -0
- package/src/firestore/utils/query-builder.ts +188 -0
- package/src/firestore/utils/quota-error-detector.util.ts +100 -0
- package/src/index.ts +16 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for QueryDeduplicationMiddleware
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
|
|
6
|
+
import { QueryDeduplicationMiddleware } from '../infrastructure/middleware/QueryDeduplicationMiddleware';
|
|
7
|
+
|
|
8
|
+
describe('QueryDeduplicationMiddleware', () => {
|
|
9
|
+
let middleware: QueryDeduplicationMiddleware;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
middleware = new QueryDeduplicationMiddleware();
|
|
13
|
+
jest.useFakeTimers();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
middleware.destroy();
|
|
18
|
+
jest.useRealTimers();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('deduplicate', () => {
|
|
22
|
+
it('should execute query immediately if not pending', async () => {
|
|
23
|
+
const queryFn = jest.fn().mockResolvedValue('result');
|
|
24
|
+
const queryKey = {
|
|
25
|
+
collection: 'test',
|
|
26
|
+
filters: 'field == value',
|
|
27
|
+
limit: 10,
|
|
28
|
+
orderBy: 'createdAt',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const result = await middleware.deduplicate(queryKey, queryFn);
|
|
32
|
+
expect(result).toBe('result');
|
|
33
|
+
expect(queryFn).toHaveBeenCalledTimes(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should deduplicate identical queries within window', async () => {
|
|
37
|
+
const queryFn = jest.fn().mockResolvedValue('result');
|
|
38
|
+
const queryKey = {
|
|
39
|
+
collection: 'test',
|
|
40
|
+
filters: 'field == value',
|
|
41
|
+
limit: 10,
|
|
42
|
+
orderBy: 'createdAt',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const promise1 = middleware.deduplicate(queryKey, queryFn);
|
|
46
|
+
const promise2 = middleware.deduplicate(queryKey, queryFn);
|
|
47
|
+
|
|
48
|
+
const [result1, result2] = await Promise.all([promise1, promise2]);
|
|
49
|
+
expect(result1).toBe('result');
|
|
50
|
+
expect(result2).toBe('result');
|
|
51
|
+
expect(queryFn).toHaveBeenCalledTimes(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should execute new query after deduplication window', async () => {
|
|
55
|
+
const queryFn = jest.fn().mockResolvedValue('result');
|
|
56
|
+
const queryKey = {
|
|
57
|
+
collection: 'test',
|
|
58
|
+
filters: 'field == value',
|
|
59
|
+
limit: 10,
|
|
60
|
+
orderBy: 'createdAt',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
await middleware.deduplicate(queryKey, queryFn);
|
|
64
|
+
expect(queryFn).toHaveBeenCalledTimes(1);
|
|
65
|
+
|
|
66
|
+
// Advance time beyond deduplication window
|
|
67
|
+
jest.advanceTimersByTime(1100);
|
|
68
|
+
|
|
69
|
+
await middleware.deduplicate(queryKey, queryFn);
|
|
70
|
+
expect(queryFn).toHaveBeenCalledTimes(2);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle different query keys separately', async () => {
|
|
74
|
+
const queryFn = jest.fn().mockResolvedValue('result');
|
|
75
|
+
const queryKey1 = {
|
|
76
|
+
collection: 'test1',
|
|
77
|
+
filters: 'field == value',
|
|
78
|
+
limit: 10,
|
|
79
|
+
orderBy: 'createdAt',
|
|
80
|
+
};
|
|
81
|
+
const queryKey2 = {
|
|
82
|
+
collection: 'test2',
|
|
83
|
+
filters: 'field == value',
|
|
84
|
+
limit: 10,
|
|
85
|
+
orderBy: 'createdAt',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
await middleware.deduplicate(queryKey1, queryFn);
|
|
89
|
+
await middleware.deduplicate(queryKey2, queryFn);
|
|
90
|
+
|
|
91
|
+
expect(queryFn).toHaveBeenCalledTimes(2);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('cleanup', () => {
|
|
96
|
+
it('should clean up expired queries automatically', async () => {
|
|
97
|
+
const queryFn = jest.fn().mockResolvedValue('result');
|
|
98
|
+
const queryKey = {
|
|
99
|
+
collection: 'test',
|
|
100
|
+
filters: 'field == value',
|
|
101
|
+
limit: 10,
|
|
102
|
+
orderBy: 'createdAt',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
await middleware.deduplicate(queryKey, queryFn);
|
|
106
|
+
expect(middleware.getPendingCount()).toBe(0);
|
|
107
|
+
|
|
108
|
+
// Advance time to trigger cleanup
|
|
109
|
+
jest.advanceTimersByTime(6000);
|
|
110
|
+
expect(middleware.getPendingCount()).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('clear', () => {
|
|
115
|
+
it('should clear all pending queries', async () => {
|
|
116
|
+
const queryFn = jest.fn(() => new Promise(() => {})); // Never resolves
|
|
117
|
+
const queryKey = {
|
|
118
|
+
collection: 'test',
|
|
119
|
+
filters: 'field == value',
|
|
120
|
+
limit: 10,
|
|
121
|
+
orderBy: 'createdAt',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
middleware.deduplicate(queryKey, queryFn);
|
|
125
|
+
expect(middleware.getPendingCount()).toBe(1);
|
|
126
|
+
|
|
127
|
+
middleware.clear();
|
|
128
|
+
expect(middleware.getPendingCount()).toBe(0);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('destroy', () => {
|
|
133
|
+
it('should cleanup resources and clear queries', () => {
|
|
134
|
+
const queryKey = {
|
|
135
|
+
collection: 'test',
|
|
136
|
+
filters: 'field == value',
|
|
137
|
+
limit: 10,
|
|
138
|
+
orderBy: 'createdAt',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
middleware.deduplicate(queryKey, () => Promise.resolve('result'));
|
|
142
|
+
middleware.destroy();
|
|
143
|
+
|
|
144
|
+
expect(middleware.getPendingCount()).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for @umituz/react-native-firebase
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class FirebaseError extends Error {
|
|
6
|
+
code: string;
|
|
7
|
+
originalError?: unknown;
|
|
8
|
+
|
|
9
|
+
constructor(message: string, code?: string, originalError?: unknown) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'FirebaseError';
|
|
12
|
+
this.code = code || 'UNKNOWN';
|
|
13
|
+
this.originalError = originalError;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getFirebaseApp(): any {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function initializeFirebase(): any {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest setup file
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Mock __DEV__ for tests
|
|
6
|
+
export {};
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
var __DEV__: boolean | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof (global as any).__DEV__ === 'undefined') {
|
|
13
|
+
(global as any).__DEV__ = true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Mock console methods to avoid noise in tests
|
|
17
|
+
const originalConsole = { ...console };
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Restore console methods before each test
|
|
21
|
+
Object.assign(console, originalConsole);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Set up global test utilities
|
|
25
|
+
global.mockFirestore = () => ({
|
|
26
|
+
collection: jest.fn(),
|
|
27
|
+
doc: jest.fn(),
|
|
28
|
+
runTransaction: jest.fn(),
|
|
29
|
+
batch: jest.fn(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
global.mockFirebaseError = (code: string, message: string) => {
|
|
33
|
+
const error = new Error(message) as any;
|
|
34
|
+
error.code = code;
|
|
35
|
+
return error;
|
|
36
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* Firestore free tier daily limits
|
|
11
|
+
* https://firebase.google.com/docs/firestore/quotas
|
|
12
|
+
*/
|
|
13
|
+
export const FREE_TIER_LIMITS = {
|
|
14
|
+
/**
|
|
15
|
+
* Daily read operations (documents)
|
|
16
|
+
* Free tier: 50,000 reads/day
|
|
17
|
+
*/
|
|
18
|
+
DAILY_READS: 50_000,
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Daily write operations (documents)
|
|
22
|
+
* Free tier: 20,000 writes/day
|
|
23
|
+
*/
|
|
24
|
+
DAILY_WRITES: 20_000,
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Daily delete operations (documents)
|
|
28
|
+
* Free tier: 20,000 deletes/day
|
|
29
|
+
*/
|
|
30
|
+
DAILY_DELETES: 20_000,
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Stored data (GB)
|
|
34
|
+
* Free tier: 1 GB
|
|
35
|
+
*/
|
|
36
|
+
STORAGE_GB: 1,
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Quota warning thresholds (percentage of limit)
|
|
41
|
+
* Apps can use these to show warnings before hitting limits
|
|
42
|
+
*/
|
|
43
|
+
export const QUOTA_THRESHOLDS = {
|
|
44
|
+
/**
|
|
45
|
+
* Warning threshold (80% of limit)
|
|
46
|
+
* Show warning to user
|
|
47
|
+
*/
|
|
48
|
+
WARNING: 0.8,
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Critical threshold (95% of limit)
|
|
52
|
+
* Show critical alert, consider disabling features
|
|
53
|
+
*/
|
|
54
|
+
CRITICAL: 0.95,
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Emergency threshold (98% of limit)
|
|
58
|
+
* Disable non-essential features
|
|
59
|
+
*/
|
|
60
|
+
EMERGENCY: 0.98,
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Calculate quota usage percentage
|
|
65
|
+
* @param current - Current usage count
|
|
66
|
+
* @param limit - Total limit
|
|
67
|
+
* @returns Percentage (0-1)
|
|
68
|
+
*/
|
|
69
|
+
export function calculateQuotaUsage(current: number, limit: number): number {
|
|
70
|
+
return Math.min(1, current / limit);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if quota threshold is reached
|
|
75
|
+
* @param current - Current usage count
|
|
76
|
+
* @param limit - Total limit
|
|
77
|
+
* @param threshold - Threshold to check (0-1)
|
|
78
|
+
* @returns true if threshold is reached
|
|
79
|
+
*/
|
|
80
|
+
export function isQuotaThresholdReached(
|
|
81
|
+
current: number,
|
|
82
|
+
limit: number,
|
|
83
|
+
threshold: number,
|
|
84
|
+
): boolean {
|
|
85
|
+
const usage = calculateQuotaUsage(current, limit);
|
|
86
|
+
return usage >= threshold;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get remaining quota
|
|
91
|
+
* @param current - Current usage count
|
|
92
|
+
* @param limit - Total limit
|
|
93
|
+
* @returns Remaining quota count
|
|
94
|
+
*/
|
|
95
|
+
export function getRemainingQuota(current: number, limit: number): number {
|
|
96
|
+
return Math.max(0, limit - current);
|
|
97
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Metrics Entity
|
|
3
|
+
* Domain entity for tracking Firestore quota usage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface QuotaMetrics {
|
|
7
|
+
readCount: number;
|
|
8
|
+
writeCount: number;
|
|
9
|
+
deleteCount: number;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface QuotaLimits {
|
|
14
|
+
dailyReadLimit: number;
|
|
15
|
+
dailyWriteLimit: number;
|
|
16
|
+
dailyDeleteLimit: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface QuotaStatus {
|
|
20
|
+
metrics: QuotaMetrics;
|
|
21
|
+
limits: QuotaLimits;
|
|
22
|
+
readPercentage: number;
|
|
23
|
+
writePercentage: number;
|
|
24
|
+
deletePercentage: number;
|
|
25
|
+
isNearLimit: boolean;
|
|
26
|
+
isOverLimit: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Log Entity
|
|
3
|
+
* Domain entity for tracking Firestore requests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type RequestType = 'read' | 'write' | 'delete' | 'listener';
|
|
7
|
+
|
|
8
|
+
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;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
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;
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Firestore Domain Errors
|
|
3
|
+
*
|
|
4
|
+
* Domain-Driven Design: Error types for Firestore operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FirebaseError } from '../../../domain/errors/FirebaseError';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Firestore Error
|
|
11
|
+
* Thrown when Firestore operations fail
|
|
12
|
+
*/
|
|
13
|
+
export class FirebaseFirestoreError extends FirebaseError {
|
|
14
|
+
constructor(message: string, originalError?: unknown) {
|
|
15
|
+
super(message, 'FIRESTORE_ERROR', originalError);
|
|
16
|
+
this.name = 'FirebaseFirestoreError';
|
|
17
|
+
Object.setPrototypeOf(this, FirebaseFirestoreError.prototype);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Firestore Initialization Error
|
|
23
|
+
* Thrown when Firestore fails to initialize
|
|
24
|
+
*/
|
|
25
|
+
export class FirebaseFirestoreInitializationError extends FirebaseFirestoreError {
|
|
26
|
+
constructor(message: string, originalError?: unknown) {
|
|
27
|
+
super(message, originalError);
|
|
28
|
+
this.name = 'FirebaseFirestoreInitializationError';
|
|
29
|
+
Object.setPrototypeOf(this, FirebaseFirestoreInitializationError.prototype);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Firestore Quota Error
|
|
35
|
+
* Thrown when Firebase quota limits are exceeded
|
|
36
|
+
*
|
|
37
|
+
* Firebase quota limits:
|
|
38
|
+
* - Free tier: 50K reads/day, 20K writes/day, 20K deletes/day
|
|
39
|
+
* - Blaze plan: Pay as you go, higher limits
|
|
40
|
+
*
|
|
41
|
+
* This error is NOT retryable - quota won't increase by retrying
|
|
42
|
+
*/
|
|
43
|
+
export class FirebaseFirestoreQuotaError extends FirebaseFirestoreError {
|
|
44
|
+
constructor(message: string, originalError?: unknown) {
|
|
45
|
+
super(message, originalError);
|
|
46
|
+
this.name = 'FirebaseFirestoreQuotaError';
|
|
47
|
+
(this as any).isQuotaError = true;
|
|
48
|
+
(this as any).code = 'resource-exhausted';
|
|
49
|
+
Object.setPrototypeOf(this, FirebaseFirestoreQuotaError.prototype);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
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 } 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 = (metrics.readCount / limits.dailyReadLimit) * 100;
|
|
28
|
+
const writePercentage = (metrics.writeCount / limits.dailyWriteLimit) * 100;
|
|
29
|
+
const deletePercentage = (metrics.deleteCount / limits.dailyDeleteLimit) * 100;
|
|
30
|
+
|
|
31
|
+
const isNearLimit =
|
|
32
|
+
readPercentage >= 80 ||
|
|
33
|
+
writePercentage >= 80 ||
|
|
34
|
+
deletePercentage >= 80;
|
|
35
|
+
|
|
36
|
+
const isOverLimit =
|
|
37
|
+
readPercentage >= 100 ||
|
|
38
|
+
writePercentage >= 100 ||
|
|
39
|
+
deletePercentage >= 100;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
metrics,
|
|
43
|
+
limits,
|
|
44
|
+
readPercentage,
|
|
45
|
+
writePercentage,
|
|
46
|
+
deletePercentage,
|
|
47
|
+
isNearLimit,
|
|
48
|
+
isOverLimit,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get default quota limits
|
|
54
|
+
*/
|
|
55
|
+
static getDefaultLimits(): QuotaLimits {
|
|
56
|
+
return { ...DEFAULT_QUOTA_LIMITS };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if metrics are within limits
|
|
61
|
+
*/
|
|
62
|
+
static isWithinLimits(
|
|
63
|
+
metrics: QuotaMetrics,
|
|
64
|
+
limits: QuotaLimits = DEFAULT_QUOTA_LIMITS,
|
|
65
|
+
): boolean {
|
|
66
|
+
const status = this.calculateStatus(metrics, limits);
|
|
67
|
+
return !status.isOverLimit;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native Firestore Module
|
|
3
|
+
* Domain-Driven Design (DDD) Architecture
|
|
4
|
+
*
|
|
5
|
+
* This is the SINGLE SOURCE OF TRUTH for all Firestore operations.
|
|
6
|
+
* ALL imports from the Firestore module MUST go through this file.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - domain: Errors, Constants, Entities, Services
|
|
10
|
+
* - infrastructure: Firestore client, BaseRepository, utilities
|
|
11
|
+
* - utils: Date utilities, timestamp conversion, query builders
|
|
12
|
+
*
|
|
13
|
+
* This module is designed to be used across hundreds of apps.
|
|
14
|
+
* It provides a consistent interface for Firestore operations.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// DOMAIN LAYER - Errors
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
FirebaseFirestoreError,
|
|
23
|
+
FirebaseFirestoreInitializationError,
|
|
24
|
+
FirebaseFirestoreQuotaError,
|
|
25
|
+
} from './domain/errors/FirebaseFirestoreError';
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// INFRASTRUCTURE LAYER - Firestore Client
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
initializeFirestore,
|
|
33
|
+
getFirestore,
|
|
34
|
+
isFirestoreInitialized,
|
|
35
|
+
getFirestoreInitializationError,
|
|
36
|
+
resetFirestoreClient,
|
|
37
|
+
firestoreClient,
|
|
38
|
+
} from './infrastructure/config/FirestoreClient';
|
|
39
|
+
|
|
40
|
+
export type { Firestore } from './infrastructure/config/FirestoreClient';
|
|
41
|
+
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// INFRASTRUCTURE LAYER - BaseRepository
|
|
44
|
+
// =============================================================================
|
|
45
|
+
|
|
46
|
+
export { BaseRepository } from './infrastructure/repositories/BaseRepository';
|
|
47
|
+
export { BaseQueryRepository } from './infrastructure/repositories/BaseQueryRepository';
|
|
48
|
+
export { BasePaginatedRepository } from './infrastructure/repositories/BasePaginatedRepository';
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// UTILS - Date Utilities
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
isoToTimestamp,
|
|
56
|
+
timestampToISO,
|
|
57
|
+
timestampToDate,
|
|
58
|
+
getCurrentISOString,
|
|
59
|
+
} from './utils/dateUtils';
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// UTILS - Query Builder
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
buildQuery,
|
|
67
|
+
createInFilter,
|
|
68
|
+
createEqualFilter,
|
|
69
|
+
} from './utils/query-builder';
|
|
70
|
+
|
|
71
|
+
export type {
|
|
72
|
+
QueryBuilderOptions,
|
|
73
|
+
FieldFilter,
|
|
74
|
+
} from './utils/query-builder';
|
|
75
|
+
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// UTILS - Pagination
|
|
78
|
+
// =============================================================================
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
PaginationHelper,
|
|
82
|
+
createPaginationHelper,
|
|
83
|
+
} from './utils/pagination.helper';
|
|
84
|
+
|
|
85
|
+
export type {
|
|
86
|
+
PaginatedResult,
|
|
87
|
+
PaginationParams,
|
|
88
|
+
} from './types/pagination.types';
|
|
89
|
+
|
|
90
|
+
export { EMPTY_PAGINATED_RESULT } from './types/pagination.types';
|
|
91
|
+
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// UTILS - Document Mapper
|
|
94
|
+
// =============================================================================
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
DocumentMapperHelper,
|
|
98
|
+
createDocumentMapper,
|
|
99
|
+
} from './utils/document-mapper.helper';
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// UTILS - Quota Error Detection
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
isQuotaError,
|
|
107
|
+
isRetryableError,
|
|
108
|
+
getQuotaErrorMessage,
|
|
109
|
+
} from './utils/quota-error-detector.util';
|
|
110
|
+
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// DOMAIN LAYER - Constants
|
|
113
|
+
// =============================================================================
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
FREE_TIER_LIMITS,
|
|
117
|
+
QUOTA_THRESHOLDS,
|
|
118
|
+
calculateQuotaUsage,
|
|
119
|
+
isQuotaThresholdReached,
|
|
120
|
+
getRemainingQuota,
|
|
121
|
+
} from './domain/constants/QuotaLimits';
|
|
122
|
+
|
|
123
|
+
// =============================================================================
|
|
124
|
+
// DOMAIN LAYER - Entities
|
|
125
|
+
// =============================================================================
|
|
126
|
+
|
|
127
|
+
export type {
|
|
128
|
+
QuotaMetrics,
|
|
129
|
+
QuotaLimits,
|
|
130
|
+
QuotaStatus,
|
|
131
|
+
} from './domain/entities/QuotaMetrics';
|
|
132
|
+
|
|
133
|
+
export type {
|
|
134
|
+
RequestLog,
|
|
135
|
+
RequestStats,
|
|
136
|
+
RequestType,
|
|
137
|
+
} from './domain/entities/RequestLog';
|
|
138
|
+
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// DOMAIN LAYER - Services
|
|
141
|
+
// =============================================================================
|
|
142
|
+
|
|
143
|
+
export { QuotaCalculator } from './domain/services/QuotaCalculator';
|
|
144
|
+
|
|
145
|
+
// =============================================================================
|
|
146
|
+
// INFRASTRUCTURE LAYER - Middleware
|
|
147
|
+
// =============================================================================
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
QueryDeduplicationMiddleware,
|
|
151
|
+
queryDeduplicationMiddleware,
|
|
152
|
+
} from './infrastructure/middleware/QueryDeduplicationMiddleware';
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
QuotaTrackingMiddleware,
|
|
156
|
+
quotaTrackingMiddleware,
|
|
157
|
+
} from './infrastructure/middleware/QuotaTrackingMiddleware';
|
|
158
|
+
|
|
159
|
+
// =============================================================================
|
|
160
|
+
// INFRASTRUCTURE LAYER - Services
|
|
161
|
+
// =============================================================================
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
QuotaMonitorService,
|
|
165
|
+
quotaMonitorService,
|
|
166
|
+
} from './infrastructure/services/QuotaMonitorService';
|
|
167
|
+
|
|
168
|
+
export {
|
|
169
|
+
RequestLoggerService,
|
|
170
|
+
requestLoggerService,
|
|
171
|
+
} from './infrastructure/services/RequestLoggerService';
|
|
172
|
+
|
|
173
|
+
// Re-export Firestore types for convenience
|
|
174
|
+
export type { Timestamp } from 'firebase/firestore';
|