@umituz/react-native-subscription 3.1.9 → 3.1.11
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/credits/presentation/useCreditsRealTime.ts +31 -73
- package/src/domains/credits/utils/creditValidation.ts +5 -26
- package/src/domains/paywall/hooks/usePaywallActions.ts +21 -133
- package/src/domains/paywall/hooks/usePaywallActions.types.ts +16 -0
- package/src/domains/paywall/hooks/usePaywallPurchase.ts +78 -0
- package/src/domains/paywall/hooks/usePaywallRestore.ts +66 -0
- package/src/domains/revenuecat/infrastructure/services/userSwitchCore.ts +116 -0
- package/src/domains/revenuecat/infrastructure/services/userSwitchHandler.ts +19 -237
- package/src/domains/revenuecat/infrastructure/services/userSwitchHelpers.ts +55 -0
- package/src/domains/revenuecat/infrastructure/services/userSwitchInitializer.ts +143 -0
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +6 -3
- package/src/domains/subscription/infrastructure/managers/initializationHandler.ts +2 -2
- package/src/domains/subscription/infrastructure/managers/packageHandlerFactory.ts +2 -2
- package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +2 -2
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.logic.ts +52 -0
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.tsx +15 -89
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.types.ts +59 -0
- package/src/domains/subscription/presentation/components/details/CreditRow.tsx +9 -0
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.tsx +23 -0
- package/src/domains/subscription/presentation/components/states/FeedbackState.tsx +36 -0
- package/src/domains/subscription/presentation/components/states/InitializingState.tsx +47 -0
- package/src/domains/subscription/presentation/components/states/OnboardingState.tsx +27 -0
- package/src/domains/subscription/presentation/components/states/PaywallState.tsx +66 -0
- package/src/domains/subscription/presentation/components/states/ReadyState.tsx +51 -0
- package/src/domains/subscription/presentation/flowInitialState.ts +22 -0
- package/src/domains/subscription/presentation/flowTypes.ts +106 -0
- package/src/domains/subscription/presentation/screens/components/SubscriptionHeaderContent.tsx +119 -103
- package/src/domains/subscription/presentation/usePremiumActions.ts +5 -6
- package/src/domains/subscription/presentation/useSubscriptionFlow.ts +25 -92
- package/src/domains/wallet/presentation/components/BalanceCard.tsx +7 -0
- package/src/domains/wallet/presentation/components/TransactionItem.tsx +11 -0
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +34 -60
- package/src/index.components.ts +1 -1
- package/src/shared/infrastructure/SubscriptionEventBus.ts +4 -2
- package/src/shared/presentation/hooks/useFirestoreRealTime.ts +230 -0
- package/src/shared/presentation/types/hookState.types.ts +97 -0
- package/src/shared/utils/errors/errorAssertions.ts +35 -0
- package/src/shared/utils/errors/errorConversion.ts +73 -0
- package/src/shared/utils/errors/errorTypeGuards.ts +27 -0
- package/src/shared/utils/errors/errorWrappers.ts +54 -0
- package/src/shared/utils/errors/index.ts +19 -0
- package/src/shared/utils/errors/serviceErrors.ts +36 -0
- package/src/shared/utils/logger.ts +140 -0
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.states.tsx +0 -187
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic real-time sync hook for Firestore.
|
|
3
|
+
*
|
|
4
|
+
* Eliminates 90% duplication from real-time hooks:
|
|
5
|
+
* - useCreditsRealTime: 116 → ~35 lines
|
|
6
|
+
* - useTransactionHistory: 98 → ~30 lines
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Generic type support for any document/collection
|
|
10
|
+
* - Automatic cleanup with unsubscribe
|
|
11
|
+
* - Consistent error handling
|
|
12
|
+
* - Loading state management
|
|
13
|
+
* - Type-safe mapper function
|
|
14
|
+
* - Support for both document and collection queries
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useEffect, useState, useCallback, useRef } from "react";
|
|
18
|
+
import {
|
|
19
|
+
onSnapshot,
|
|
20
|
+
type Query,
|
|
21
|
+
type DocumentReference,
|
|
22
|
+
} from "firebase/firestore";
|
|
23
|
+
import type { HookState, HookStateWithEmpty } from "../types/hookState.types";
|
|
24
|
+
import { logError, logWarn } from "../../utils/logger";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for building a Firestore document reference.
|
|
28
|
+
*/
|
|
29
|
+
export interface DocumentConfig {
|
|
30
|
+
/** Collection name */
|
|
31
|
+
collectionName: string;
|
|
32
|
+
|
|
33
|
+
/** Whether to use a user subcollection (users/{userId}/{collectionName}) */
|
|
34
|
+
useUserSubcollection: boolean;
|
|
35
|
+
|
|
36
|
+
/** Document ID (fixed string or function that takes userId) */
|
|
37
|
+
docId: string | ((userId: string) => string);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Configuration for building a Firestore collection query.
|
|
42
|
+
*/
|
|
43
|
+
export interface CollectionConfig {
|
|
44
|
+
/** Collection name */
|
|
45
|
+
collectionName: string;
|
|
46
|
+
|
|
47
|
+
/** Whether to use a user subcollection (users/{userId}/{collectionName}) */
|
|
48
|
+
useUserSubcollection: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Query builder for collection queries.
|
|
53
|
+
* Takes a Firestore collection reference and returns a Query with constraints.
|
|
54
|
+
*/
|
|
55
|
+
export type QueryBuilder<T> = (collection: any) => Query<T>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Mapper function to convert Firestore document data to domain entity.
|
|
59
|
+
*/
|
|
60
|
+
export type Mapper<TDocument, TEntity> = (
|
|
61
|
+
doc: TDocument,
|
|
62
|
+
docId: string
|
|
63
|
+
) => TEntity;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generic hook for real-time document sync via Firestore onSnapshot.
|
|
67
|
+
*
|
|
68
|
+
* PERFORMANCE: Uses useRef to stabilize mapper and prevent unnecessary re-subscriptions
|
|
69
|
+
*
|
|
70
|
+
* @template TDocument - Firestore document type
|
|
71
|
+
* @template TEntity - Domain entity type
|
|
72
|
+
*
|
|
73
|
+
* @param userId - User ID to fetch document for
|
|
74
|
+
* @param docRef - Document reference from Firestore
|
|
75
|
+
* @param mapper - Function to map document data to entity
|
|
76
|
+
* @param tag - Logging tag for debugging
|
|
77
|
+
*
|
|
78
|
+
* @returns Hook state with data, loading, error, and refetch
|
|
79
|
+
*/
|
|
80
|
+
export function useFirestoreDocumentRealTime<TDocument, TEntity>(
|
|
81
|
+
userId: string | null | undefined,
|
|
82
|
+
docRef: DocumentReference<TDocument> | null,
|
|
83
|
+
mapper: Mapper<TDocument, TEntity>,
|
|
84
|
+
tag: string
|
|
85
|
+
): HookState<TEntity> {
|
|
86
|
+
const [data, setData] = useState<TEntity | null>(null);
|
|
87
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
88
|
+
const [error, setError] = useState<Error | null>(null);
|
|
89
|
+
|
|
90
|
+
// Stabilize mapper to prevent re-subscriptions when parent re-renders
|
|
91
|
+
const mapperRef = useRef(mapper);
|
|
92
|
+
mapperRef.current = mapper;
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
// Reset state when userId changes
|
|
96
|
+
if (!userId) {
|
|
97
|
+
setData(null);
|
|
98
|
+
setIsLoading(false);
|
|
99
|
+
setError(null);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setIsLoading(true);
|
|
104
|
+
setError(null);
|
|
105
|
+
|
|
106
|
+
if (!docRef) {
|
|
107
|
+
setIsLoading(false);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const unsubscribe = onSnapshot(
|
|
112
|
+
docRef,
|
|
113
|
+
(snapshot) => {
|
|
114
|
+
if (snapshot.exists()) {
|
|
115
|
+
const entity = mapperRef.current(snapshot.data() as TDocument, snapshot.id);
|
|
116
|
+
setData(entity);
|
|
117
|
+
} else {
|
|
118
|
+
setData(null);
|
|
119
|
+
}
|
|
120
|
+
setIsLoading(false);
|
|
121
|
+
},
|
|
122
|
+
(err: Error) => {
|
|
123
|
+
logError(tag, "Snapshot error", err, { userId });
|
|
124
|
+
setError(err);
|
|
125
|
+
setIsLoading(false);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return () => {
|
|
130
|
+
unsubscribe();
|
|
131
|
+
};
|
|
132
|
+
}, [userId, docRef, tag]); // Removed mapper from deps
|
|
133
|
+
|
|
134
|
+
const refetch = useCallback(() => {
|
|
135
|
+
// Real-time sync doesn't need refetch, but keep for API compatibility
|
|
136
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
137
|
+
logWarn(tag, "Refetch called - not needed for real-time sync");
|
|
138
|
+
}
|
|
139
|
+
}, [tag]);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
data,
|
|
143
|
+
isLoading,
|
|
144
|
+
error,
|
|
145
|
+
refetch,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generic hook for real-time collection sync via Firestore onSnapshot.
|
|
151
|
+
*
|
|
152
|
+
* PERFORMANCE: Uses useRef to stabilize mapper and optimize snapshot processing
|
|
153
|
+
*
|
|
154
|
+
* @template TDocument - Firestore document type
|
|
155
|
+
* @template TEntity - Domain entity type
|
|
156
|
+
*
|
|
157
|
+
* @param userId - User ID to fetch collection for
|
|
158
|
+
* @param query - Firestore query to listen to
|
|
159
|
+
* @param mapper - Function to map document data to entity
|
|
160
|
+
* @param tag - Logging tag for debugging
|
|
161
|
+
*
|
|
162
|
+
* @returns Hook state with array data, loading, error, refetch, and isEmpty
|
|
163
|
+
*/
|
|
164
|
+
export function useFirestoreCollectionRealTime<TDocument, TEntity>(
|
|
165
|
+
userId: string | null | undefined,
|
|
166
|
+
query: Query<TDocument>,
|
|
167
|
+
mapper: Mapper<TDocument, TEntity>,
|
|
168
|
+
tag: string
|
|
169
|
+
): HookStateWithEmpty<TEntity> {
|
|
170
|
+
const [data, setData] = useState<TEntity[]>([]);
|
|
171
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
172
|
+
const [error, setError] = useState<Error | null>(null);
|
|
173
|
+
|
|
174
|
+
// Stabilize mapper to prevent re-subscriptions when parent re-renders
|
|
175
|
+
const mapperRef = useRef(mapper);
|
|
176
|
+
mapperRef.current = mapper;
|
|
177
|
+
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
// Reset state when userId changes
|
|
180
|
+
if (!userId) {
|
|
181
|
+
setData([]);
|
|
182
|
+
setIsLoading(false);
|
|
183
|
+
setError(null);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
setIsLoading(true);
|
|
188
|
+
setError(null);
|
|
189
|
+
|
|
190
|
+
const unsubscribe = onSnapshot(
|
|
191
|
+
query,
|
|
192
|
+
(snapshot) => {
|
|
193
|
+
// PERFORMANCE: Pre-allocate array with known size for better memory efficiency
|
|
194
|
+
const entities: TEntity[] = new Array(snapshot.size);
|
|
195
|
+
let index = 0;
|
|
196
|
+
|
|
197
|
+
snapshot.forEach((doc) => {
|
|
198
|
+
entities[index++] = mapperRef.current(doc.data() as TDocument, doc.id);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
setData(entities);
|
|
202
|
+
setIsLoading(false);
|
|
203
|
+
},
|
|
204
|
+
(err: Error) => {
|
|
205
|
+
logError(tag, "Snapshot error", err, { userId });
|
|
206
|
+
setError(err);
|
|
207
|
+
setIsLoading(false);
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
return () => {
|
|
212
|
+
unsubscribe();
|
|
213
|
+
};
|
|
214
|
+
}, [userId, query, tag]); // Removed mapper from deps
|
|
215
|
+
|
|
216
|
+
const refetch = useCallback(() => {
|
|
217
|
+
// Real-time sync doesn't need refetch, but keep for API compatibility
|
|
218
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
219
|
+
logWarn(tag, "Refetch called - not needed for real-time sync");
|
|
220
|
+
}
|
|
221
|
+
}, [tag]);
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
data,
|
|
225
|
+
isLoading,
|
|
226
|
+
error,
|
|
227
|
+
refetch,
|
|
228
|
+
isEmpty: data.length === 0,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standardized hook state types for consistent return values across all hooks.
|
|
3
|
+
*
|
|
4
|
+
* Benefits:
|
|
5
|
+
* - Type consistency across 25+ hooks
|
|
6
|
+
* - Easier to understand and maintain
|
|
7
|
+
* - Better IDE autocomplete
|
|
8
|
+
* - Predictable API surface
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Standard hook state returned by data-fetching hooks.
|
|
13
|
+
* Provides consistent interface for loading, error, and data states.
|
|
14
|
+
*
|
|
15
|
+
* @template T - The type of data returned by the hook
|
|
16
|
+
*/
|
|
17
|
+
export interface HookState<T> {
|
|
18
|
+
/** The fetched data, or null if not yet loaded or on error */
|
|
19
|
+
data: T | null;
|
|
20
|
+
|
|
21
|
+
/** Whether the hook is currently fetching data */
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
|
|
24
|
+
/** Any error that occurred during fetching, or null if no error */
|
|
25
|
+
error: Error | null;
|
|
26
|
+
|
|
27
|
+
/** Function to manually refetch data (no-op for real-time sync hooks) */
|
|
28
|
+
refetch: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extended hook state that includes isEmpty for collection queries.
|
|
33
|
+
* Useful when you need to distinguish between "no data" and "loading".
|
|
34
|
+
*
|
|
35
|
+
* @template T - The type of data in the array (typically a document type)
|
|
36
|
+
*/
|
|
37
|
+
export interface HookStateWithEmpty<T> extends HookState<T[]> {
|
|
38
|
+
/** Whether the data array is empty (only meaningful when isLoading is false) */
|
|
39
|
+
isEmpty: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extended hook state that includes metadata.
|
|
44
|
+
* Useful for hooks that need to return additional computed values.
|
|
45
|
+
*
|
|
46
|
+
* @template T - The type of data returned by the hook
|
|
47
|
+
* @template M - The type of metadata
|
|
48
|
+
*/
|
|
49
|
+
export interface HookStateWithMeta<T, M> extends HookState<T> {
|
|
50
|
+
/** Additional computed or derived metadata */
|
|
51
|
+
meta: M;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Factory function to create a standard HookState.
|
|
56
|
+
* Useful for testing or when you need to construct state objects.
|
|
57
|
+
*/
|
|
58
|
+
export function createHookState<T>(
|
|
59
|
+
data: T | null,
|
|
60
|
+
isLoading: boolean,
|
|
61
|
+
error: Error | null,
|
|
62
|
+
refetch: () => void = () => {}
|
|
63
|
+
): HookState<T> {
|
|
64
|
+
return { data, isLoading, error, refetch };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Factory function to create a HookStateWithEmpty.
|
|
69
|
+
* Automatically computes isEmpty from the data array.
|
|
70
|
+
*/
|
|
71
|
+
export function createHookStateWithEmpty<T>(
|
|
72
|
+
data: T[] | null,
|
|
73
|
+
isLoading: boolean,
|
|
74
|
+
error: Error | null,
|
|
75
|
+
refetch: () => void = () => {}
|
|
76
|
+
): HookStateWithEmpty<T> {
|
|
77
|
+
return {
|
|
78
|
+
data,
|
|
79
|
+
isLoading,
|
|
80
|
+
error,
|
|
81
|
+
refetch,
|
|
82
|
+
isEmpty: data === null ? false : data.length === 0,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Factory function to create a HookStateWithMeta.
|
|
88
|
+
*/
|
|
89
|
+
export function createHookStateWithMeta<T, M>(
|
|
90
|
+
data: T | null,
|
|
91
|
+
isLoading: boolean,
|
|
92
|
+
error: Error | null,
|
|
93
|
+
meta: M,
|
|
94
|
+
refetch: () => void = () => {}
|
|
95
|
+
): HookStateWithMeta<T, M> {
|
|
96
|
+
return { data, isLoading, error, refetch, meta };
|
|
97
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Assertion Functions
|
|
3
|
+
*
|
|
4
|
+
* Runtime validation and type narrowing utilities.
|
|
5
|
+
* Throws errors with consistent formatting.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createError } from "./errorConversion";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Assert that a condition is true, throw error otherwise.
|
|
12
|
+
* Useful for validation and runtime checks.
|
|
13
|
+
*/
|
|
14
|
+
export function assert(
|
|
15
|
+
condition: boolean,
|
|
16
|
+
message: string,
|
|
17
|
+
code: string = "ASSERTION_ERROR"
|
|
18
|
+
): asserts condition {
|
|
19
|
+
if (!condition) {
|
|
20
|
+
throw createError(message, code);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Assert that a value is not null/undefined.
|
|
26
|
+
* Throws error if value is null/undefined, returns value otherwise.
|
|
27
|
+
* Useful for type narrowing.
|
|
28
|
+
*/
|
|
29
|
+
export function assertNotNil<T>(
|
|
30
|
+
value: T | null | undefined,
|
|
31
|
+
message: string = "Value should not be null or undefined"
|
|
32
|
+
): T {
|
|
33
|
+
assert(value !== null && value !== undefined, message, "NOT_NIL_ERROR");
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Conversion Utilities
|
|
3
|
+
*
|
|
4
|
+
* Functions to normalize and convert unknown errors to Error objects.
|
|
5
|
+
* Separated for better modularity and testability.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { logError } from "../logger";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Safely convert unknown error to Error object.
|
|
12
|
+
* Useful when catching errors from external APIs.
|
|
13
|
+
*/
|
|
14
|
+
export function toError(error: unknown): Error {
|
|
15
|
+
if (error instanceof Error) {
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof error === "string") {
|
|
20
|
+
return new Error(error);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (error === null || error === undefined) {
|
|
24
|
+
return new Error("Unknown error occurred");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return new Error(JSON.stringify(error));
|
|
29
|
+
} catch {
|
|
30
|
+
return new Error(String(error));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create an error with a code and optional cause.
|
|
36
|
+
*/
|
|
37
|
+
export function createError(
|
|
38
|
+
message: string,
|
|
39
|
+
code: string,
|
|
40
|
+
cause?: Error
|
|
41
|
+
): Error {
|
|
42
|
+
const error = new Error(message);
|
|
43
|
+
error.name = code;
|
|
44
|
+
|
|
45
|
+
if (cause) {
|
|
46
|
+
// @ts-ignore - adding cause property
|
|
47
|
+
error.cause = cause;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return error;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Log an error with consistent formatting.
|
|
55
|
+
* Only logs in __DEV__ mode.
|
|
56
|
+
*/
|
|
57
|
+
export function logAndReturnError(
|
|
58
|
+
tag: string,
|
|
59
|
+
message: string,
|
|
60
|
+
error: unknown,
|
|
61
|
+
context?: Record<string, unknown>
|
|
62
|
+
): Error {
|
|
63
|
+
const normalizedError = toError(error);
|
|
64
|
+
|
|
65
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
66
|
+
logError(tag, message, normalizedError, {
|
|
67
|
+
...context,
|
|
68
|
+
originalError: error,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return normalizedError;
|
|
73
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Type Guards
|
|
3
|
+
*
|
|
4
|
+
* Type-safe error checking utilities.
|
|
5
|
+
* Provides type narrowing for error objects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Type guard to check if value is an Error.
|
|
10
|
+
*/
|
|
11
|
+
export function isError(value: unknown): value is Error {
|
|
12
|
+
return value instanceof Error;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Type guard to check if error has a code property.
|
|
17
|
+
*/
|
|
18
|
+
export function isErrorWithCode(error: Error): error is Error & { code: string } {
|
|
19
|
+
return "code" in error && typeof error.code === "string";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Type guard to check if error has a cause property.
|
|
24
|
+
*/
|
|
25
|
+
export function isErrorWithCause(error: Error): error is Error & { cause: Error } {
|
|
26
|
+
return "cause" in error && error.cause instanceof Error;
|
|
27
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Wrapper Functions
|
|
3
|
+
*
|
|
4
|
+
* Wrap functions with error handling and return Result types.
|
|
5
|
+
* Provides consistent error handling patterns.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { logAndReturnError } from "./errorConversion";
|
|
9
|
+
|
|
10
|
+
type Result<T> =
|
|
11
|
+
| { success: true; data: T }
|
|
12
|
+
| { success: false; error: Error };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Wrap an async function with error handling.
|
|
16
|
+
* Returns a Result type with success/error states.
|
|
17
|
+
*/
|
|
18
|
+
export async function tryAsync<T>(
|
|
19
|
+
fn: () => Promise<T>,
|
|
20
|
+
context: { tag: string; operation: string }
|
|
21
|
+
): Promise<Result<T>> {
|
|
22
|
+
try {
|
|
23
|
+
const data = await fn();
|
|
24
|
+
return { success: true, data };
|
|
25
|
+
} catch (error) {
|
|
26
|
+
const normalizedError = logAndReturnError(
|
|
27
|
+
context.tag,
|
|
28
|
+
`Failed to ${context.operation}`,
|
|
29
|
+
error
|
|
30
|
+
);
|
|
31
|
+
return { success: false, error: normalizedError };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Wrap a synchronous function with error handling.
|
|
37
|
+
* Returns a Result type with success/error states.
|
|
38
|
+
*/
|
|
39
|
+
export function trySync<T>(
|
|
40
|
+
fn: () => T,
|
|
41
|
+
context: { tag: string; operation: string }
|
|
42
|
+
): Result<T> {
|
|
43
|
+
try {
|
|
44
|
+
const data = fn();
|
|
45
|
+
return { success: true, data };
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const normalizedError = logAndReturnError(
|
|
48
|
+
context.tag,
|
|
49
|
+
`Failed to ${context.operation}`,
|
|
50
|
+
error
|
|
51
|
+
);
|
|
52
|
+
return { success: false, error: normalizedError };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Utilities Module
|
|
3
|
+
*
|
|
4
|
+
* Centralized error handling utilities split into focused modules.
|
|
5
|
+
* Original 195-line file split into 5 files for better maintainability.
|
|
6
|
+
*
|
|
7
|
+
* Modules:
|
|
8
|
+
* - errorConversion: Error normalization and creation
|
|
9
|
+
* - errorTypeGuards: Type-safe error checking
|
|
10
|
+
* - errorWrappers: Function wrapping with error handling
|
|
11
|
+
* - errorAssertions: Runtime validation and type narrowing
|
|
12
|
+
* - serviceErrors: Service-specific error creation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export * from './errorConversion';
|
|
16
|
+
export * from './errorTypeGuards';
|
|
17
|
+
export * from './errorWrappers';
|
|
18
|
+
export * from './errorAssertions';
|
|
19
|
+
export * from './serviceErrors';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service-Specific Error Creators
|
|
3
|
+
*
|
|
4
|
+
* Consistent error creation for external services.
|
|
5
|
+
* Provides clear error codes and messages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createError, toError } from "./errorConversion";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a Firestore-specific error with consistent formatting.
|
|
12
|
+
*/
|
|
13
|
+
export function createFirestoreError(
|
|
14
|
+
operation: string,
|
|
15
|
+
error: unknown
|
|
16
|
+
): Error {
|
|
17
|
+
return createError(
|
|
18
|
+
`Firestore ${operation} failed`,
|
|
19
|
+
"FIRESTORE_ERROR",
|
|
20
|
+
toError(error)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a RevenueCat-specific error with consistent formatting.
|
|
26
|
+
*/
|
|
27
|
+
export function createRevenueCatError(
|
|
28
|
+
operation: string,
|
|
29
|
+
error: unknown
|
|
30
|
+
): Error {
|
|
31
|
+
return createError(
|
|
32
|
+
`RevenueCat ${operation} failed`,
|
|
33
|
+
"REVENUECAT_ERROR",
|
|
34
|
+
toError(error)
|
|
35
|
+
);
|
|
36
|
+
}
|