@umituz/react-native-firebase 1.13.149 → 1.13.150
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/auth/infrastructure/services/apple-auth.service.ts +10 -2
- package/src/auth/infrastructure/services/google-auth.service.ts +10 -2
- package/src/auth/infrastructure/services/reauthentication.service.ts +5 -1
- package/src/auth/presentation/hooks/shared/auth-hooks.util.ts +60 -0
- package/src/auth/presentation/hooks/shared/hook-utils.util.ts +14 -216
- package/src/auth/presentation/hooks/shared/safe-state-hooks.util.ts +76 -0
- package/src/auth/presentation/hooks/shared/state-hooks.util.ts +97 -0
- package/src/domain/utils/async-executor.util.ts +16 -169
- package/src/domain/utils/error-handler.util.ts +18 -170
- package/src/domain/utils/error-handlers/error-checkers.ts +120 -0
- package/src/domain/utils/error-handlers/error-converters.ts +48 -0
- package/src/domain/utils/error-handlers/error-messages.ts +18 -0
- package/src/domain/utils/executors/advanced-executors.util.ts +59 -0
- package/src/domain/utils/executors/basic-executors.util.ts +56 -0
- package/src/domain/utils/executors/batch-executors.util.ts +42 -0
- package/src/domain/utils/executors/error-converters.util.ts +45 -0
- package/src/domain/utils/result/result-creators.ts +49 -0
- package/src/domain/utils/result/result-helpers.ts +60 -0
- package/src/domain/utils/result/result-types.ts +40 -0
- package/src/domain/utils/result.util.ts +28 -127
- package/src/domain/utils/validation.util.ts +38 -209
- package/src/domain/utils/validators/composite.validator.ts +24 -0
- package/src/domain/utils/validators/firebase.validator.ts +45 -0
- package/src/domain/utils/validators/generic.validator.ts +59 -0
- package/src/domain/utils/validators/string.validator.ts +25 -0
- package/src/domain/utils/validators/url.validator.ts +36 -0
- package/src/domain/utils/validators/user-input.validator.ts +54 -0
- package/src/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +10 -3
- package/src/firestore/utils/deduplication/timer-manager.util.ts +2 -2
- package/src/firestore/utils/pagination.helper.ts +3 -1
- package/src/infrastructure/config/FirebaseClient.ts +28 -189
- package/src/infrastructure/config/clients/FirebaseClientSingleton.ts +82 -0
- package/src/infrastructure/config/services/FirebaseInitializationService.ts +115 -0
- package/src/init/createFirebaseInitModule.ts +9 -3
|
@@ -1,176 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Async Operation Executor Utility
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Re-exports all async executors for backward compatibility
|
|
4
|
+
* @deprecated Import from specific executor files instead
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Error Converters
|
|
8
|
+
export type { ErrorConverter } from './executors/error-converters.util';
|
|
9
|
+
export { authErrorConverter, defaultErrorConverter } from './executors/error-converters.util';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* Default error converter for auth operations
|
|
18
|
-
*/
|
|
19
|
-
export function authErrorConverter(error: unknown): { code: string; message: string } {
|
|
20
|
-
if (error instanceof Error) {
|
|
21
|
-
return {
|
|
22
|
-
code: (error as { code?: string }).code ?? 'auth/failed',
|
|
23
|
-
message: error.message,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
code: 'auth/failed',
|
|
28
|
-
message: typeof error === 'string' ? error : 'Authentication failed',
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Default error converter for operations
|
|
34
|
-
*/
|
|
35
|
-
export function defaultErrorConverter(
|
|
36
|
-
error: unknown,
|
|
37
|
-
defaultCode = 'operation/failed'
|
|
38
|
-
): { code: string; message: string } {
|
|
39
|
-
if (error instanceof Error) {
|
|
40
|
-
return {
|
|
41
|
-
code: (error as { code?: string }).code ?? defaultCode,
|
|
42
|
-
message: error.message,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
code: defaultCode,
|
|
47
|
-
message: typeof error === 'string' ? error : 'Operation failed',
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Execute async operation with error handling
|
|
53
|
-
* Returns Result type with success/failure
|
|
54
|
-
*/
|
|
55
|
-
export async function executeOperation<T>(
|
|
56
|
-
operation: () => Promise<T>,
|
|
57
|
-
errorConverter?: ErrorConverter
|
|
58
|
-
): Promise<Result<T>> {
|
|
59
|
-
try {
|
|
60
|
-
const data = await operation();
|
|
61
|
-
return successResult(data);
|
|
62
|
-
} catch (error) {
|
|
63
|
-
const converter = errorConverter ?? defaultErrorConverter;
|
|
64
|
-
return { success: false, error: converter(error) };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Execute async operation with error handling and default code
|
|
70
|
-
*/
|
|
71
|
-
export async function executeOperationWithCode<T>(
|
|
72
|
-
operation: () => Promise<T>,
|
|
73
|
-
defaultErrorCode = 'operation/failed'
|
|
74
|
-
): Promise<Result<T>> {
|
|
75
|
-
return executeOperation(operation, (error) => defaultErrorConverter(error, defaultErrorCode));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Execute async void operation
|
|
80
|
-
* Useful for operations that don't return data
|
|
81
|
-
*/
|
|
82
|
-
export async function executeVoidOperation(
|
|
83
|
-
operation: () => Promise<void>,
|
|
84
|
-
errorConverter?: ErrorConverter
|
|
85
|
-
): Promise<Result<void>> {
|
|
86
|
-
return executeOperation(operation, errorConverter) as Promise<Result<void>>;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Execute async operation with auth error handling
|
|
91
|
-
*/
|
|
92
|
-
export async function executeAuthOperation<T>(
|
|
93
|
-
operation: () => Promise<T>
|
|
94
|
-
): Promise<Result<T>> {
|
|
95
|
-
return executeOperation(operation, authErrorConverter);
|
|
96
|
-
}
|
|
11
|
+
// Basic Executors
|
|
12
|
+
export {
|
|
13
|
+
executeOperation,
|
|
14
|
+
executeOperationWithCode,
|
|
15
|
+
executeVoidOperation,
|
|
16
|
+
executeAuthOperation,
|
|
17
|
+
} from './executors/basic-executors.util';
|
|
97
18
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
* Returns success only if all operations succeed
|
|
101
|
-
*/
|
|
102
|
-
export async function executeAll<T>(
|
|
103
|
-
...operations: (() => Promise<Result<T>>)[]
|
|
104
|
-
): Promise<Result<T[]>> {
|
|
105
|
-
try {
|
|
106
|
-
const results = await Promise.all(operations.map((op) => op()));
|
|
107
|
-
const failures = results.filter((r) => !r.success);
|
|
108
|
-
if (failures.length > 0) {
|
|
109
|
-
return failures[0] as FailureResult;
|
|
110
|
-
}
|
|
111
|
-
const data = results.map((r) => (r as { success: true; data: T }).data);
|
|
112
|
-
return successResult(data);
|
|
113
|
-
} catch (error) {
|
|
114
|
-
return failureResultFromError(error);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
19
|
+
// Batch Executors
|
|
20
|
+
export { executeAll, executeSequence } from './executors/batch-executors.util';
|
|
117
21
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
*/
|
|
121
|
-
export async function executeSequence<T>(
|
|
122
|
-
...operations: (() => Promise<Result<T>>)[]
|
|
123
|
-
): Promise<Result<void>> {
|
|
124
|
-
for (const operation of operations) {
|
|
125
|
-
const result = await operation();
|
|
126
|
-
if (!result.success) {
|
|
127
|
-
return result as Result<void>;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return successResult();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Execute operation with retry
|
|
135
|
-
*/
|
|
136
|
-
export async function executeWithRetry<T>(
|
|
137
|
-
operation: () => Promise<T>,
|
|
138
|
-
maxRetries = 3,
|
|
139
|
-
delayMs = 1000
|
|
140
|
-
): Promise<Result<T>> {
|
|
141
|
-
let lastError: unknown;
|
|
142
|
-
for (let i = 0; i <= maxRetries; i++) {
|
|
143
|
-
try {
|
|
144
|
-
const data = await operation();
|
|
145
|
-
return successResult(data);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
lastError = error;
|
|
148
|
-
if (i < maxRetries) {
|
|
149
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs * (i + 1)));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return failureResultFromError(lastError);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Execute operation with timeout
|
|
158
|
-
*/
|
|
159
|
-
export async function executeWithTimeout<T>(
|
|
160
|
-
operation: () => Promise<T>,
|
|
161
|
-
timeoutMs: number
|
|
162
|
-
): Promise<Result<T>> {
|
|
163
|
-
return Promise.race([
|
|
164
|
-
executeOperation(operation),
|
|
165
|
-
new Promise<Result<T>>((resolve) =>
|
|
166
|
-
setTimeout(
|
|
167
|
-
() =>
|
|
168
|
-
resolve({
|
|
169
|
-
success: false,
|
|
170
|
-
error: { code: 'timeout', message: `Operation timed out after ${timeoutMs}ms` },
|
|
171
|
-
}),
|
|
172
|
-
timeoutMs
|
|
173
|
-
)
|
|
174
|
-
),
|
|
175
|
-
]);
|
|
176
|
-
}
|
|
22
|
+
// Advanced Executors
|
|
23
|
+
export { executeWithRetry, executeWithTimeout } from './executors/advanced-executors.util';
|
|
@@ -1,178 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error Handler Utility
|
|
3
|
-
*
|
|
3
|
+
* Re-exports all error handling utilities for backward compatibility
|
|
4
|
+
* @deprecated Import from specific error-handler files instead
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
// Error Converters
|
|
8
|
+
export type { ErrorInfo } from './error-handlers/error-converters';
|
|
9
|
+
export { toErrorInfo, toAuthErrorInfo } from './error-handlers/error-converters';
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
const QUOTA_ERROR_CODES = [
|
|
20
|
-
'resource-exhausted',
|
|
21
|
-
'quota-exceeded',
|
|
22
|
-
'RESOURCE_EXHAUSTED',
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Quota error message patterns
|
|
27
|
-
*/
|
|
28
|
-
const QUOTA_ERROR_MESSAGES = [
|
|
29
|
-
'quota exceeded',
|
|
30
|
-
'quota limit',
|
|
31
|
-
'daily limit',
|
|
32
|
-
'resource exhausted',
|
|
33
|
-
'too many requests',
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Retryable error codes
|
|
38
|
-
*/
|
|
39
|
-
const RETRYABLE_ERROR_CODES = ['unavailable', 'deadline-exceeded', 'aborted'];
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Convert unknown error to standard error info
|
|
43
|
-
* Handles Error objects, strings, and unknown types
|
|
44
|
-
*/
|
|
45
|
-
export function toErrorInfo(error: unknown): ErrorInfo {
|
|
46
|
-
if (error instanceof Error) {
|
|
47
|
-
return {
|
|
48
|
-
code: hasCodeProperty(error) ? error.code : 'unknown',
|
|
49
|
-
message: error.message,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
code: 'unknown',
|
|
54
|
-
message: typeof error === 'string' ? error : 'Unknown error',
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Convert unknown error to auth error info
|
|
60
|
-
* Auth-specific error codes are prefixed with 'auth/'
|
|
61
|
-
*/
|
|
62
|
-
export function toAuthErrorInfo(error: unknown): ErrorInfo {
|
|
63
|
-
if (error instanceof Error) {
|
|
64
|
-
return {
|
|
65
|
-
code: hasCodeProperty(error) && error.code ? error.code : 'auth/failed',
|
|
66
|
-
message: error.message,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
code: 'auth/failed',
|
|
71
|
-
message: typeof error === 'string' ? error : 'Unknown error',
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Check if error info has a specific error code
|
|
77
|
-
*/
|
|
78
|
-
export function hasErrorCode(error: ErrorInfo, code: string): boolean {
|
|
79
|
-
return error.code === code;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Check if error is a cancelled/auth cancelled error
|
|
84
|
-
*/
|
|
85
|
-
export function isCancelledError(error: ErrorInfo): boolean {
|
|
86
|
-
return (
|
|
87
|
-
error.code === 'auth/cancelled' ||
|
|
88
|
-
error.message.includes('ERR_CANCELED')
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Check if error info is a quota error
|
|
94
|
-
*/
|
|
95
|
-
export function isQuotaErrorInfo(error: ErrorInfo): boolean {
|
|
96
|
-
return (
|
|
97
|
-
error.code === 'quota-exceeded' ||
|
|
98
|
-
error.code.includes('quota') ||
|
|
99
|
-
error.message.toLowerCase().includes('quota')
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Check if error info is a network error
|
|
105
|
-
*/
|
|
106
|
-
export function isNetworkError(error: ErrorInfo): boolean {
|
|
107
|
-
return (
|
|
108
|
-
error.code.includes('network') ||
|
|
109
|
-
error.message.toLowerCase().includes('network')
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Check if error info is an authentication error
|
|
115
|
-
*/
|
|
116
|
-
export function isAuthError(error: ErrorInfo): boolean {
|
|
117
|
-
return error.code.startsWith('auth/');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Check if unknown error is a Firestore quota error
|
|
122
|
-
* Enhanced type guard with proper error checking
|
|
123
|
-
*/
|
|
124
|
-
export function isQuotaError(error: unknown): boolean {
|
|
125
|
-
if (!error || typeof error !== 'object') return false;
|
|
126
|
-
|
|
127
|
-
if (hasCodeProperty(error)) {
|
|
128
|
-
const code = error.code;
|
|
129
|
-
return QUOTA_ERROR_CODES.some(
|
|
130
|
-
(c) => code === c || code.endsWith(`/${c}`) || code.startsWith(`${c}/`)
|
|
131
|
-
);
|
|
132
|
-
}
|
|
11
|
+
// Error Checkers
|
|
12
|
+
export {
|
|
13
|
+
hasErrorCode,
|
|
14
|
+
isCancelledError,
|
|
15
|
+
isQuotaErrorInfo,
|
|
16
|
+
isNetworkError,
|
|
17
|
+
isAuthError,
|
|
18
|
+
isQuotaError,
|
|
19
|
+
isRetryableError,
|
|
20
|
+
} from './error-handlers/error-checkers';
|
|
133
21
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return QUOTA_ERROR_MESSAGES.some((m) => {
|
|
137
|
-
const pattern = m.toLowerCase();
|
|
138
|
-
return (
|
|
139
|
-
message.includes(` ${pattern} `) ||
|
|
140
|
-
message.startsWith(`${pattern} `) ||
|
|
141
|
-
message.endsWith(` ${pattern}`) ||
|
|
142
|
-
message === pattern
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Check if error is retryable
|
|
152
|
-
*/
|
|
153
|
-
export function isRetryableError(error: unknown): boolean {
|
|
154
|
-
if (!error || typeof error !== 'object') return false;
|
|
155
|
-
|
|
156
|
-
if (hasCodeProperty(error)) {
|
|
157
|
-
return RETRYABLE_ERROR_CODES.some((code) => error.code.includes(code));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Get user-friendly quota error message
|
|
165
|
-
*/
|
|
166
|
-
export function getQuotaErrorMessage(): string {
|
|
167
|
-
return 'Daily quota exceeded. Please try again tomorrow or upgrade your plan.';
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get user-friendly retryable error message
|
|
172
|
-
*/
|
|
173
|
-
export function getRetryableErrorMessage(): string {
|
|
174
|
-
return 'Temporary error occurred. Please try again.';
|
|
175
|
-
}
|
|
22
|
+
// Error Messages
|
|
23
|
+
export { getQuotaErrorMessage, getRetryableErrorMessage } from './error-handlers/error-messages';
|
|
176
24
|
|
|
177
25
|
// Re-export type guards for convenience
|
|
178
|
-
export { hasCodeProperty, hasMessageProperty, hasCodeAndMessageProperties };
|
|
26
|
+
export { hasCodeProperty, hasMessageProperty, hasCodeAndMessageProperties } from './type-guards.util';
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Type Checkers
|
|
3
|
+
* Check error types and categories
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ErrorInfo } from './error-converters';
|
|
7
|
+
import { hasCodeProperty, hasMessageProperty } from '../type-guards.util';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Quota error codes
|
|
11
|
+
*/
|
|
12
|
+
const QUOTA_ERROR_CODES = [
|
|
13
|
+
'resource-exhausted',
|
|
14
|
+
'quota-exceeded',
|
|
15
|
+
'RESOURCE_EXHAUSTED',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Quota error message patterns
|
|
20
|
+
*/
|
|
21
|
+
const QUOTA_ERROR_MESSAGES = [
|
|
22
|
+
'quota exceeded',
|
|
23
|
+
'quota limit',
|
|
24
|
+
'daily limit',
|
|
25
|
+
'resource exhausted',
|
|
26
|
+
'too many requests',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Retryable error codes
|
|
31
|
+
*/
|
|
32
|
+
const RETRYABLE_ERROR_CODES = ['unavailable', 'deadline-exceeded', 'aborted'];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if error info has a specific error code
|
|
36
|
+
*/
|
|
37
|
+
export function hasErrorCode(error: ErrorInfo, code: string): boolean {
|
|
38
|
+
return error.code === code;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if error is a cancelled/auth cancelled error
|
|
43
|
+
*/
|
|
44
|
+
export function isCancelledError(error: ErrorInfo): boolean {
|
|
45
|
+
return (
|
|
46
|
+
error.code === 'auth/cancelled' ||
|
|
47
|
+
error.message.includes('ERR_CANCELED')
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if error info is a quota error
|
|
53
|
+
*/
|
|
54
|
+
export function isQuotaErrorInfo(error: ErrorInfo): boolean {
|
|
55
|
+
return (
|
|
56
|
+
error.code === 'quota-exceeded' ||
|
|
57
|
+
error.code.includes('quota') ||
|
|
58
|
+
error.message.toLowerCase().includes('quota')
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if error info is a network error
|
|
64
|
+
*/
|
|
65
|
+
export function isNetworkError(error: ErrorInfo): boolean {
|
|
66
|
+
return (
|
|
67
|
+
error.code.includes('network') ||
|
|
68
|
+
error.message.toLowerCase().includes('network')
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if error info is an authentication error
|
|
74
|
+
*/
|
|
75
|
+
export function isAuthError(error: ErrorInfo): boolean {
|
|
76
|
+
return error.code.startsWith('auth/');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if unknown error is a Firestore quota error
|
|
81
|
+
* Enhanced type guard with proper error checking
|
|
82
|
+
*/
|
|
83
|
+
export function isQuotaError(error: unknown): boolean {
|
|
84
|
+
if (!error || typeof error !== 'object') return false;
|
|
85
|
+
|
|
86
|
+
if (hasCodeProperty(error)) {
|
|
87
|
+
const code = error.code;
|
|
88
|
+
return QUOTA_ERROR_CODES.some(
|
|
89
|
+
(c) => code === c || code.endsWith(`/${c}`) || code.startsWith(`${c}/`)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (hasMessageProperty(error)) {
|
|
94
|
+
const message = error.message.toLowerCase();
|
|
95
|
+
return QUOTA_ERROR_MESSAGES.some((m) => {
|
|
96
|
+
const pattern = m.toLowerCase();
|
|
97
|
+
return (
|
|
98
|
+
message.includes(` ${pattern} `) ||
|
|
99
|
+
message.startsWith(`${pattern} `) ||
|
|
100
|
+
message.endsWith(` ${pattern}`) ||
|
|
101
|
+
message === pattern
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if error is retryable
|
|
111
|
+
*/
|
|
112
|
+
export function isRetryableError(error: unknown): boolean {
|
|
113
|
+
if (!error || typeof error !== 'object') return false;
|
|
114
|
+
|
|
115
|
+
if (hasCodeProperty(error)) {
|
|
116
|
+
return RETRYABLE_ERROR_CODES.some((code) => error.code.includes(code));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Converters
|
|
3
|
+
* Convert unknown errors to ErrorInfo
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { hasCodeProperty } from '../type-guards.util';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Standard error structure with code and message
|
|
10
|
+
*/
|
|
11
|
+
export interface ErrorInfo {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Convert unknown error to standard error info
|
|
18
|
+
* Handles Error objects, strings, and unknown types
|
|
19
|
+
*/
|
|
20
|
+
export function toErrorInfo(error: unknown): ErrorInfo {
|
|
21
|
+
if (error instanceof Error) {
|
|
22
|
+
return {
|
|
23
|
+
code: hasCodeProperty(error) ? error.code : 'unknown',
|
|
24
|
+
message: error.message,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
code: 'unknown',
|
|
29
|
+
message: typeof error === 'string' ? error : 'Unknown error',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Convert unknown error to auth error info
|
|
35
|
+
* Auth-specific error codes are prefixed with 'auth/'
|
|
36
|
+
*/
|
|
37
|
+
export function toAuthErrorInfo(error: unknown): ErrorInfo {
|
|
38
|
+
if (error instanceof Error) {
|
|
39
|
+
return {
|
|
40
|
+
code: hasCodeProperty(error) && error.code ? error.code : 'auth/failed',
|
|
41
|
+
message: error.message,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
code: 'auth/failed',
|
|
46
|
+
message: typeof error === 'string' ? error : 'Unknown error',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Messages
|
|
3
|
+
* User-friendly error messages for common error types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get user-friendly quota error message
|
|
8
|
+
*/
|
|
9
|
+
export function getQuotaErrorMessage(): string {
|
|
10
|
+
return 'Daily quota exceeded. Please try again tomorrow or upgrade your plan.';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get user-friendly retryable error message
|
|
15
|
+
*/
|
|
16
|
+
export function getRetryableErrorMessage(): string {
|
|
17
|
+
return 'Temporary error occurred. Please try again.';
|
|
18
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Async Executors
|
|
3
|
+
* Retry and timeout support for async operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Result } from '../result.util';
|
|
7
|
+
import { failureResultFromError, successResult } from '../result.util';
|
|
8
|
+
import { executeOperation } from './basic-executors.util';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Execute operation with retry
|
|
12
|
+
* @param operation - Operation to execute
|
|
13
|
+
* @param maxRetries - Maximum number of retries (default: 3)
|
|
14
|
+
* @param delayMs - Base delay between retries in milliseconds (default: 1000)
|
|
15
|
+
*/
|
|
16
|
+
export async function executeWithRetry<T>(
|
|
17
|
+
operation: () => Promise<T>,
|
|
18
|
+
maxRetries = 3,
|
|
19
|
+
delayMs = 1000
|
|
20
|
+
): Promise<Result<T>> {
|
|
21
|
+
let lastError: unknown;
|
|
22
|
+
for (let i = 0; i <= maxRetries; i++) {
|
|
23
|
+
try {
|
|
24
|
+
const data = await operation();
|
|
25
|
+
return successResult(data);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
lastError = error;
|
|
28
|
+
if (i < maxRetries) {
|
|
29
|
+
// Exponential backoff: delay * (attempt + 1)
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs * (i + 1)));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return failureResultFromError(lastError);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute operation with timeout
|
|
39
|
+
* @param operation - Operation to execute
|
|
40
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
41
|
+
*/
|
|
42
|
+
export async function executeWithTimeout<T>(
|
|
43
|
+
operation: () => Promise<T>,
|
|
44
|
+
timeoutMs: number
|
|
45
|
+
): Promise<Result<T>> {
|
|
46
|
+
return Promise.race([
|
|
47
|
+
executeOperation(operation),
|
|
48
|
+
new Promise<Result<T>>((resolve) =>
|
|
49
|
+
setTimeout(
|
|
50
|
+
() =>
|
|
51
|
+
resolve({
|
|
52
|
+
success: false,
|
|
53
|
+
error: { code: 'timeout', message: `Operation timed out after ${timeoutMs}ms` },
|
|
54
|
+
}),
|
|
55
|
+
timeoutMs
|
|
56
|
+
)
|
|
57
|
+
),
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic Async Executors
|
|
3
|
+
* Core async operation execution with error handling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Result } from '../result.util';
|
|
7
|
+
import { successResult } from '../result.util';
|
|
8
|
+
import type { ErrorConverter } from './error-converters.util';
|
|
9
|
+
import { authErrorConverter, defaultErrorConverter } from './error-converters.util';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Execute async operation with error handling
|
|
13
|
+
* Returns Result type with success/failure
|
|
14
|
+
*/
|
|
15
|
+
export async function executeOperation<T>(
|
|
16
|
+
operation: () => Promise<T>,
|
|
17
|
+
errorConverter?: ErrorConverter
|
|
18
|
+
): Promise<Result<T>> {
|
|
19
|
+
try {
|
|
20
|
+
const data = await operation();
|
|
21
|
+
return successResult(data);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const converter = errorConverter ?? defaultErrorConverter;
|
|
24
|
+
return { success: false, error: converter(error) };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute async operation with error handling and default code
|
|
30
|
+
*/
|
|
31
|
+
export async function executeOperationWithCode<T>(
|
|
32
|
+
operation: () => Promise<T>,
|
|
33
|
+
defaultErrorCode = 'operation/failed'
|
|
34
|
+
): Promise<Result<T>> {
|
|
35
|
+
return executeOperation(operation, (error) => defaultErrorConverter(error, defaultErrorCode));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Execute async void operation
|
|
40
|
+
* Useful for operations that don't return data
|
|
41
|
+
*/
|
|
42
|
+
export async function executeVoidOperation(
|
|
43
|
+
operation: () => Promise<void>,
|
|
44
|
+
errorConverter?: ErrorConverter
|
|
45
|
+
): Promise<Result<void>> {
|
|
46
|
+
return executeOperation(operation, errorConverter) as Promise<Result<void>>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Execute async operation with auth error handling
|
|
51
|
+
*/
|
|
52
|
+
export async function executeAuthOperation<T>(
|
|
53
|
+
operation: () => Promise<T>
|
|
54
|
+
): Promise<Result<T>> {
|
|
55
|
+
return executeOperation(operation, authErrorConverter);
|
|
56
|
+
}
|