@umituz/react-native-firebase 2.4.100 → 2.6.0
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 +1 -1
- package/src/domains/firestore/index.ts +11 -58
- package/src/domains/firestore/infrastructure/repositories/BasePaginatedRepository.ts +1 -7
- package/src/domains/firestore/infrastructure/repositories/BaseQueryRepository.ts +8 -34
- package/src/domains/firestore/infrastructure/repositories/BaseRepository.ts +9 -48
- package/src/domains/firestore/presentation/hooks/index.ts +0 -10
- package/src/shared/domain/utils/calculation.util.ts +17 -305
- package/src/shared/domain/utils/index.ts +0 -5
- package/src/shared/infrastructure/config/state/FirebaseClientState.ts +34 -12
- package/src/domains/firestore/domain/constants/QuotaLimits.ts +0 -101
- package/src/domains/firestore/domain/entities/QuotaMetrics.ts +0 -26
- package/src/domains/firestore/domain/entities/RequestLog.ts +0 -28
- package/src/domains/firestore/domain/services/QuotaCalculator.ts +0 -71
- package/src/domains/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +0 -312
- package/src/domains/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +0 -95
- package/src/domains/firestore/infrastructure/services/RequestLoggerService.ts +0 -165
- package/src/domains/firestore/presentation/hooks/useSmartFirestoreSnapshot.ts +0 -361
- package/src/domains/firestore/presentation/query-keys/createFirestoreKeys.ts +0 -32
- package/src/domains/firestore/presentation/query-keys/index.ts +0 -1
- package/src/domains/firestore/utils/deduplication/pending-query-manager.util.ts +0 -119
- package/src/domains/firestore/utils/deduplication/query-key-generator.util.ts +0 -34
- package/src/domains/firestore/utils/deduplication/timer-manager.util.ts +0 -83
- package/src/shared/infrastructure/config/base/ClientStateManager.ts +0 -82
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-firebase",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
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",
|
|
@@ -59,61 +59,6 @@ export type {
|
|
|
59
59
|
} from './types/pagination.types';
|
|
60
60
|
export { EMPTY_PAGINATED_RESULT } from './types/pagination.types';
|
|
61
61
|
|
|
62
|
-
// Domain Constants
|
|
63
|
-
export {
|
|
64
|
-
FREE_TIER_LIMITS,
|
|
65
|
-
QUOTA_THRESHOLDS,
|
|
66
|
-
calculateQuotaUsage,
|
|
67
|
-
isQuotaThresholdReached,
|
|
68
|
-
getRemainingQuota,
|
|
69
|
-
} from './domain/constants/QuotaLimits';
|
|
70
|
-
|
|
71
|
-
// Domain Entities
|
|
72
|
-
export type {
|
|
73
|
-
QuotaMetrics,
|
|
74
|
-
QuotaLimits,
|
|
75
|
-
QuotaStatus,
|
|
76
|
-
} from './domain/entities/QuotaMetrics';
|
|
77
|
-
export type {
|
|
78
|
-
RequestLog,
|
|
79
|
-
RequestStats,
|
|
80
|
-
RequestType,
|
|
81
|
-
} from './domain/entities/RequestLog';
|
|
82
|
-
|
|
83
|
-
// Domain Services
|
|
84
|
-
export { QuotaCalculator } from './domain/services/QuotaCalculator';
|
|
85
|
-
|
|
86
|
-
// Quota Error Detection
|
|
87
|
-
export {
|
|
88
|
-
isQuotaError,
|
|
89
|
-
isRetryableError,
|
|
90
|
-
} from '../../shared/domain/utils/error-handlers/error-checkers';
|
|
91
|
-
export {
|
|
92
|
-
getQuotaErrorMessage,
|
|
93
|
-
} from '../../shared/domain/utils/error-handlers/error-messages';
|
|
94
|
-
|
|
95
|
-
// Middleware
|
|
96
|
-
export {
|
|
97
|
-
QueryDeduplicationMiddleware,
|
|
98
|
-
queryDeduplicationMiddleware,
|
|
99
|
-
syncDeduplicationWithQuota,
|
|
100
|
-
useDeduplicationWithQuota,
|
|
101
|
-
} from './infrastructure/middleware/QueryDeduplicationMiddleware';
|
|
102
|
-
export type {
|
|
103
|
-
QueryDeduplicationConfig,
|
|
104
|
-
DeduplicationStatistics,
|
|
105
|
-
} from './infrastructure/middleware/QueryDeduplicationMiddleware';
|
|
106
|
-
export {
|
|
107
|
-
QuotaTrackingMiddleware,
|
|
108
|
-
quotaTrackingMiddleware,
|
|
109
|
-
} from './infrastructure/middleware/QuotaTrackingMiddleware';
|
|
110
|
-
|
|
111
|
-
// Services
|
|
112
|
-
export {
|
|
113
|
-
RequestLoggerService,
|
|
114
|
-
requestLoggerService,
|
|
115
|
-
} from './infrastructure/services/RequestLoggerService';
|
|
116
|
-
|
|
117
62
|
// Firestore Helper Utilities
|
|
118
63
|
export {
|
|
119
64
|
withFirestore,
|
|
@@ -150,13 +95,21 @@ export {
|
|
|
150
95
|
export { useFirestoreQuery } from './presentation/hooks/useFirestoreQuery';
|
|
151
96
|
export { useFirestoreMutation } from './presentation/hooks/useFirestoreMutation';
|
|
152
97
|
export { useFirestoreSnapshot } from './presentation/hooks/useFirestoreSnapshot';
|
|
153
|
-
export { useSmartFirestoreSnapshot, useSmartListenerControl } from './presentation/hooks/useSmartFirestoreSnapshot';
|
|
154
|
-
export { createFirestoreKeys } from './presentation/query-keys/createFirestoreKeys';
|
|
155
98
|
|
|
156
99
|
export type { UseFirestoreQueryOptions } from './presentation/hooks/useFirestoreQuery';
|
|
157
100
|
export type { UseFirestoreMutationOptions } from './presentation/hooks/useFirestoreMutation';
|
|
158
101
|
export type { UseFirestoreSnapshotOptions } from './presentation/hooks/useFirestoreSnapshot';
|
|
159
|
-
|
|
102
|
+
|
|
103
|
+
// Re-export commonly used Firebase Firestore types
|
|
104
|
+
export type {
|
|
105
|
+
Timestamp,
|
|
106
|
+
DocumentSnapshot,
|
|
107
|
+
QuerySnapshot,
|
|
108
|
+
DocumentReference,
|
|
109
|
+
CollectionReference,
|
|
110
|
+
Query,
|
|
111
|
+
Transaction,
|
|
112
|
+
} from 'firebase/firestore';
|
|
160
113
|
|
|
161
114
|
// Firebase types are available from the 'firebase' package directly
|
|
162
115
|
// Import them in your app: import { Timestamp } from 'firebase/firestore';
|
|
@@ -43,11 +43,9 @@ export abstract class BasePaginatedRepository extends BaseQueryRepository {
|
|
|
43
43
|
|
|
44
44
|
const collectionRef = collection(db, collectionName);
|
|
45
45
|
let q: import("firebase/firestore").Query<DocumentData>;
|
|
46
|
-
let cursorKey = 'start';
|
|
47
46
|
|
|
48
47
|
// Handle cursor-based pagination
|
|
49
48
|
if (helper.hasCursor(params) && params?.cursor) {
|
|
50
|
-
cursorKey = params.cursor;
|
|
51
49
|
|
|
52
50
|
// FIX: Validate cursor and throw error instead of silent failure
|
|
53
51
|
validateCursorOrThrow(params.cursor);
|
|
@@ -77,17 +75,13 @@ export abstract class BasePaginatedRepository extends BaseQueryRepository {
|
|
|
77
75
|
);
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
// Generate a unique key for deduplication (after cursor is resolved)
|
|
81
|
-
const uniqueKey = `${collectionName}_list_${orderByField}_${orderDirection}_${fetchLimit}_${cursorKey}`;
|
|
82
|
-
|
|
83
78
|
return this.executeQuery(
|
|
84
79
|
collectionName,
|
|
85
80
|
async () => {
|
|
86
81
|
const snapshot = await getDocs(q);
|
|
87
82
|
return snapshot.docs;
|
|
88
83
|
},
|
|
89
|
-
false
|
|
90
|
-
uniqueKey
|
|
84
|
+
false
|
|
91
85
|
);
|
|
92
86
|
}
|
|
93
87
|
|
|
@@ -1,44 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
* Base Repository - Query Operations
|
|
3
|
-
*
|
|
4
|
-
* Provides query and tracking operations for Firestore repositories.
|
|
5
|
-
* Extends BaseRepository with query-specific functionality.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { queryDeduplicationMiddleware } from "../middleware/QueryDeduplicationMiddleware";
|
|
9
|
-
import { BaseRepository } from "./BaseRepository";
|
|
1
|
+
import { BaseRepository } from './BaseRepository';
|
|
10
2
|
|
|
11
3
|
export abstract class BaseQueryRepository extends BaseRepository {
|
|
12
|
-
/**
|
|
13
|
-
* Execute query with deduplication and quota tracking
|
|
14
|
-
* Prevents duplicate queries and tracks quota usage
|
|
15
|
-
*/
|
|
16
4
|
protected async executeQuery<T>(
|
|
17
5
|
collection: string,
|
|
18
6
|
queryFn: () => Promise<T>,
|
|
19
|
-
cached: boolean = false
|
|
20
|
-
uniqueKey?: string
|
|
7
|
+
cached: boolean = false
|
|
21
8
|
): Promise<T> {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const queryKey = {
|
|
25
|
-
collection,
|
|
26
|
-
filters: safeKey,
|
|
27
|
-
limit: undefined,
|
|
28
|
-
orderBy: undefined,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return queryDeduplicationMiddleware.deduplicate(queryKey, async () => {
|
|
32
|
-
const result = await queryFn();
|
|
9
|
+
const result = await queryFn();
|
|
33
10
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.trackRead(collection, count, cached);
|
|
39
|
-
}
|
|
11
|
+
if (!cached) {
|
|
12
|
+
const count = Array.isArray(result) ? result.length : 1;
|
|
13
|
+
this.trackRead(collection, count, cached);
|
|
14
|
+
}
|
|
40
15
|
|
|
41
|
-
|
|
42
|
-
});
|
|
16
|
+
return result;
|
|
43
17
|
}
|
|
44
18
|
}
|
|
@@ -10,9 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Firestore, CollectionReference, DocumentReference, DocumentData } from 'firebase/firestore';
|
|
12
12
|
import { getFirestore, collection, doc } from 'firebase/firestore';
|
|
13
|
-
import { isQuotaError as checkQuotaError } from '../../../../shared/domain/utils/error-handlers/error-checkers';
|
|
14
|
-
import { ERROR_MESSAGES } from '../../../../shared/domain/utils/error-handlers/error-messages';
|
|
15
|
-
import { quotaTrackingMiddleware } from '../middleware/QuotaTrackingMiddleware';
|
|
16
13
|
|
|
17
14
|
enum RepositoryState {
|
|
18
15
|
ACTIVE = 'active',
|
|
@@ -49,11 +46,11 @@ export abstract class BaseRepository implements IPathResolver {
|
|
|
49
46
|
*/
|
|
50
47
|
protected getDbOrThrow(): Firestore {
|
|
51
48
|
if (this.state === RepositoryState.DESTROYED) {
|
|
52
|
-
throw new Error(
|
|
49
|
+
throw new Error('Repository is destroyed');
|
|
53
50
|
}
|
|
54
51
|
const db = getFirestore();
|
|
55
52
|
if (!db) {
|
|
56
|
-
throw new Error(
|
|
53
|
+
throw new Error('Firestore is not initialized');
|
|
57
54
|
}
|
|
58
55
|
return db;
|
|
59
56
|
}
|
|
@@ -107,55 +104,19 @@ export abstract class BaseRepository implements IPathResolver {
|
|
|
107
104
|
protected async executeOperation<T>(
|
|
108
105
|
operation: () => Promise<T>
|
|
109
106
|
): Promise<T> {
|
|
110
|
-
|
|
111
|
-
return await operation();
|
|
112
|
-
} catch (error) {
|
|
113
|
-
if (checkQuotaError(error)) {
|
|
114
|
-
throw new Error(ERROR_MESSAGES.FIRESTORE.QUOTA_EXCEEDED);
|
|
115
|
-
}
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
107
|
+
return await operation();
|
|
118
108
|
}
|
|
119
109
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*
|
|
123
|
-
* @param collection - Collection name
|
|
124
|
-
* @param count - Number of documents read
|
|
125
|
-
* @param cached - Whether the result is from cache
|
|
126
|
-
*/
|
|
127
|
-
protected trackRead(
|
|
128
|
-
collection: string,
|
|
129
|
-
count: number = 1,
|
|
130
|
-
cached: boolean = false,
|
|
131
|
-
): void {
|
|
132
|
-
quotaTrackingMiddleware.trackRead(collection, count, cached);
|
|
110
|
+
protected trackRead(_collection: string, _count: number = 1, _cached: boolean = false): void {
|
|
111
|
+
// Quota tracking removed - use Firebase console for monitoring
|
|
133
112
|
}
|
|
134
113
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
*
|
|
138
|
-
* @param collection - Collection name
|
|
139
|
-
* @param count - Number of documents written
|
|
140
|
-
*/
|
|
141
|
-
protected trackWrite(
|
|
142
|
-
collection: string,
|
|
143
|
-
count: number = 1,
|
|
144
|
-
): void {
|
|
145
|
-
quotaTrackingMiddleware.trackWrite(collection, count);
|
|
114
|
+
protected trackWrite(_collection: string, _count: number = 1): void {
|
|
115
|
+
// Quota tracking removed - use Firebase console for monitoring
|
|
146
116
|
}
|
|
147
117
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
*
|
|
151
|
-
* @param collection - Collection name
|
|
152
|
-
* @param count - Number of documents deleted
|
|
153
|
-
*/
|
|
154
|
-
protected trackDelete(
|
|
155
|
-
collection: string,
|
|
156
|
-
count: number = 1,
|
|
157
|
-
): void {
|
|
158
|
-
quotaTrackingMiddleware.trackDelete(collection, count);
|
|
118
|
+
protected trackDelete(_collection: string, _count: number = 1): void {
|
|
119
|
+
// Quota tracking removed - use Firebase console for monitoring
|
|
159
120
|
}
|
|
160
121
|
|
|
161
122
|
/**
|
|
@@ -12,13 +12,3 @@ export {
|
|
|
12
12
|
useFirestoreSnapshot,
|
|
13
13
|
type UseFirestoreSnapshotOptions,
|
|
14
14
|
} from './useFirestoreSnapshot';
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
useSmartFirestoreSnapshot,
|
|
18
|
-
useSmartListenerControl,
|
|
19
|
-
} from './useSmartFirestoreSnapshot';
|
|
20
|
-
|
|
21
|
-
export type {
|
|
22
|
-
UseSmartFirestoreSnapshotOptions,
|
|
23
|
-
BackgroundStrategy,
|
|
24
|
-
} from './useSmartFirestoreSnapshot';
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Calculation Utilities
|
|
3
|
-
* Common mathematical operations used across the codebase
|
|
4
|
-
* Optimized for performance with minimal allocations
|
|
5
3
|
*/
|
|
6
4
|
|
|
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
|
-
*/
|
|
22
5
|
export function calculatePercentage(current: number, limit: number): number {
|
|
23
6
|
if (limit <= 0) return 0;
|
|
24
7
|
if (current <= 0) return 0;
|
|
@@ -26,327 +9,56 @@ export function calculatePercentage(current: number, limit: number): number {
|
|
|
26
9
|
return current / limit;
|
|
27
10
|
}
|
|
28
11
|
|
|
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
|
-
*/
|
|
43
12
|
export function calculateRemaining(current: number, limit: number): number {
|
|
44
13
|
return Math.max(0, limit - current);
|
|
45
14
|
}
|
|
46
15
|
|
|
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
|
-
*/
|
|
62
16
|
export function safeFloor(value: number, min: number): number {
|
|
63
17
|
const floored = Math.floor(value);
|
|
64
18
|
return floored < min ? min : floored;
|
|
65
19
|
}
|
|
66
20
|
|
|
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
|
-
*/
|
|
120
21
|
export function diffMs(date1: number | Date, date2: number | Date): number {
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
return
|
|
22
|
+
const d1 = typeof date1 === 'number' ? date1 : date1.getTime();
|
|
23
|
+
const d2 = typeof date2 === 'number' ? date2 : date2.getTime();
|
|
24
|
+
return Math.abs(d1 - d2);
|
|
124
25
|
}
|
|
125
26
|
|
|
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
|
-
*/
|
|
139
27
|
export function diffMinutes(date1: number | Date, date2: number | Date): number {
|
|
140
|
-
|
|
141
|
-
return Math.floor(msDiff / 60_000);
|
|
28
|
+
return Math.floor(diffMs(date1, date2) / 60000);
|
|
142
29
|
}
|
|
143
30
|
|
|
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
|
-
*/
|
|
157
31
|
export function diffHours(date1: number | Date, date2: number | Date): number {
|
|
158
|
-
|
|
159
|
-
return Math.floor(minsDiff / 60);
|
|
32
|
+
return Math.floor(diffMs(date1, date2) / 3600000);
|
|
160
33
|
}
|
|
161
34
|
|
|
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
|
-
*/
|
|
175
35
|
export function diffDays(date1: number | Date, date2: number | Date): number {
|
|
176
|
-
|
|
177
|
-
return Math.floor(hoursDiff / 24);
|
|
36
|
+
return Math.floor(diffMs(date1, date2) / 86400000);
|
|
178
37
|
}
|
|
179
38
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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 [];
|
|
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[]);
|
|
210
44
|
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
211
47
|
|
|
212
|
-
|
|
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);
|
|
213
52
|
}
|
|
214
53
|
|
|
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
|
-
*/
|
|
227
54
|
export function getFetchLimit(pageLimit: number): number {
|
|
228
55
|
return pageLimit + 1;
|
|
229
56
|
}
|
|
230
57
|
|
|
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
|
-
*/
|
|
245
58
|
export function hasMore(itemsLength: number, pageLimit: number): boolean {
|
|
246
59
|
return itemsLength > pageLimit;
|
|
247
60
|
}
|
|
248
61
|
|
|
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
|
-
*/
|
|
263
62
|
export function getResultCount(itemsLength: number, pageLimit: number): number {
|
|
264
63
|
return Math.min(itemsLength, pageLimit);
|
|
265
64
|
}
|
|
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
|
-
}
|
|
@@ -37,8 +37,6 @@ export {
|
|
|
37
37
|
calculatePercentage,
|
|
38
38
|
calculateRemaining,
|
|
39
39
|
safeFloor,
|
|
40
|
-
safeCeil,
|
|
41
|
-
clamp,
|
|
42
40
|
diffMs,
|
|
43
41
|
diffMinutes,
|
|
44
42
|
diffHours,
|
|
@@ -48,7 +46,4 @@ export {
|
|
|
48
46
|
hasMore,
|
|
49
47
|
getResultCount,
|
|
50
48
|
chunkArray,
|
|
51
|
-
sumArray,
|
|
52
|
-
averageArray,
|
|
53
|
-
roundToDecimals,
|
|
54
49
|
} from './calculation.util';
|