@umituz/react-native-subscription 3.1.10 → 3.1.12

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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/presentation/useCreditsRealTime.ts +10 -5
  3. package/src/domains/credits/utils/creditValidation.ts +5 -26
  4. package/src/domains/paywall/hooks/usePaywallActions.ts +21 -133
  5. package/src/domains/paywall/hooks/usePaywallActions.types.ts +16 -0
  6. package/src/domains/paywall/hooks/usePaywallPurchase.ts +78 -0
  7. package/src/domains/paywall/hooks/usePaywallRestore.ts +66 -0
  8. package/src/domains/revenuecat/infrastructure/services/userSwitchCore.ts +116 -0
  9. package/src/domains/revenuecat/infrastructure/services/userSwitchHandler.ts +19 -237
  10. package/src/domains/revenuecat/infrastructure/services/userSwitchHelpers.ts +55 -0
  11. package/src/domains/revenuecat/infrastructure/services/userSwitchInitializer.ts +143 -0
  12. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +6 -3
  13. package/src/domains/subscription/infrastructure/managers/initializationHandler.ts +2 -2
  14. package/src/domains/subscription/infrastructure/managers/packageHandlerFactory.ts +2 -2
  15. package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +2 -2
  16. package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.logic.ts +52 -0
  17. package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.tsx +15 -89
  18. package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.types.ts +59 -0
  19. package/src/domains/subscription/presentation/components/details/CreditRow.tsx +9 -0
  20. package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.tsx +23 -0
  21. package/src/domains/subscription/presentation/components/states/FeedbackState.tsx +36 -0
  22. package/src/domains/subscription/presentation/components/states/InitializingState.tsx +47 -0
  23. package/src/domains/subscription/presentation/components/states/OnboardingState.tsx +27 -0
  24. package/src/domains/subscription/presentation/components/states/PaywallState.tsx +66 -0
  25. package/src/domains/subscription/presentation/components/states/ReadyState.tsx +51 -0
  26. package/src/domains/subscription/presentation/providers/SubscriptionFlowProvider.tsx +8 -2
  27. package/src/domains/subscription/presentation/screens/components/SubscriptionHeaderContent.tsx +119 -103
  28. package/src/domains/wallet/presentation/components/BalanceCard.tsx +7 -0
  29. package/src/domains/wallet/presentation/components/TransactionItem.tsx +11 -0
  30. package/src/index.components.ts +1 -1
  31. package/src/shared/infrastructure/SubscriptionEventBus.ts +4 -2
  32. package/src/shared/presentation/hooks/useFirestoreRealTime.ts +22 -6
  33. package/src/shared/utils/errors/errorAssertions.ts +35 -0
  34. package/src/shared/utils/errors/errorConversion.ts +73 -0
  35. package/src/shared/utils/errors/errorTypeGuards.ts +27 -0
  36. package/src/shared/utils/errors/errorWrappers.ts +54 -0
  37. package/src/shared/utils/errors/index.ts +19 -0
  38. package/src/shared/utils/errors/serviceErrors.ts +36 -0
  39. package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.states.tsx +0 -187
  40. package/src/shared/utils/errorUtils.ts +0 -195
@@ -11,6 +11,12 @@ interface SubscriptionHeaderContentStyles {
11
11
  value: TextStyle;
12
12
  }
13
13
 
14
+ interface DetailInfo {
15
+ label?: string;
16
+ value: string;
17
+ highlight?: boolean;
18
+ }
19
+
14
20
  interface SubscriptionHeaderContentProps {
15
21
  showExpirationDate: boolean;
16
22
  expirationDate?: string;
@@ -28,106 +34,116 @@ interface SubscriptionHeaderContentProps {
28
34
  isSandbox?: boolean;
29
35
  }
30
36
 
31
- export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps> = ({
32
- showExpirationDate,
33
- expirationDate,
34
- purchaseDate,
35
- showExpiring,
36
- translations,
37
- styles,
38
- willRenew,
39
- periodType: _periodType,
40
- packageType,
41
- store,
42
- originalPurchaseDate,
43
- latestPurchaseDate,
44
- billingIssuesDetected,
45
- isSandbox,
46
- }) => (
47
- <View style={styles.details}>
48
- {showExpirationDate && expirationDate && (
49
- <DetailRow
50
- label={translations.expiresLabel}
51
- value={expirationDate}
52
- highlight={showExpiring}
53
- style={styles.row}
54
- labelStyle={styles.label}
55
- valueStyle={styles.value}
56
- />
57
- )}
58
- {purchaseDate && (
59
- <DetailRow
60
- label={translations.purchasedLabel}
61
- value={purchaseDate}
62
- style={styles.row}
63
- labelStyle={styles.label}
64
- valueStyle={styles.value}
65
- />
66
- )}
67
- {willRenew !== null && willRenew !== undefined && translations.willRenewLabel && (
68
- <DetailRow
69
- label={translations.willRenewLabel}
70
- value={willRenew ? (translations.willRenewYes ?? "Yes") : (translations.willRenewNo ?? "No")}
71
- highlight={!willRenew}
72
- style={styles.row}
73
- labelStyle={styles.label}
74
- valueStyle={styles.value}
75
- />
76
- )}
77
- {packageType && translations.periodTypeLabel && (
78
- <DetailRow
79
- label={translations.periodTypeLabel}
80
- value={formatPackageTypeForDisplay(packageType)}
81
- style={styles.row}
82
- labelStyle={styles.label}
83
- valueStyle={styles.value}
84
- />
85
- )}
86
- {store && translations.storeLabel && (
87
- <DetailRow
88
- label={translations.storeLabel}
89
- value={store}
90
- style={styles.row}
91
- labelStyle={styles.label}
92
- valueStyle={styles.value}
93
- />
94
- )}
95
- {originalPurchaseDate && translations.originalPurchaseDateLabel && (
96
- <DetailRow
97
- label={translations.originalPurchaseDateLabel}
98
- value={originalPurchaseDate}
99
- style={styles.row}
100
- labelStyle={styles.label}
101
- valueStyle={styles.value}
102
- />
103
- )}
104
- {latestPurchaseDate && translations.latestPurchaseDateLabel && (
105
- <DetailRow
106
- label={translations.latestPurchaseDateLabel}
107
- value={latestPurchaseDate}
108
- style={styles.row}
109
- labelStyle={styles.label}
110
- valueStyle={styles.value}
111
- />
112
- )}
113
- {billingIssuesDetected && translations.billingIssuesLabel && (
114
- <DetailRow
115
- label={translations.billingIssuesLabel}
116
- value={translations.billingIssuesDetected ?? "Detected"}
117
- highlight={true}
118
- style={styles.row}
119
- labelStyle={styles.label}
120
- valueStyle={styles.value}
121
- />
122
- )}
123
- {typeof __DEV__ !== 'undefined' && __DEV__ && isSandbox && translations.sandboxLabel && (
124
- <DetailRow
125
- label={translations.sandboxLabel}
126
- value={translations.sandboxTestMode ?? "Test Mode"}
127
- style={styles.row}
128
- labelStyle={styles.label}
129
- valueStyle={styles.value}
130
- />
131
- )}
132
- </View>
133
- );
37
+ /**
38
+ * Helper to build detail row data array.
39
+ * Reduces code duplication by centralizing detail row creation logic.
40
+ */
41
+ function buildDetails(
42
+ props: SubscriptionHeaderContentProps
43
+ ): DetailInfo[] {
44
+ const {
45
+ showExpirationDate,
46
+ expirationDate,
47
+ purchaseDate,
48
+ showExpiring,
49
+ translations,
50
+ willRenew,
51
+ packageType,
52
+ store,
53
+ originalPurchaseDate,
54
+ latestPurchaseDate,
55
+ billingIssuesDetected,
56
+ isSandbox,
57
+ } = props;
58
+
59
+ const details: DetailInfo[] = [];
60
+
61
+ if (showExpirationDate && expirationDate) {
62
+ details.push({
63
+ label: translations.expiresLabel,
64
+ value: expirationDate,
65
+ highlight: showExpiring,
66
+ });
67
+ }
68
+
69
+ if (purchaseDate) {
70
+ details.push({
71
+ label: translations.purchasedLabel,
72
+ value: purchaseDate,
73
+ });
74
+ }
75
+
76
+ if (willRenew !== null && willRenew !== undefined && translations.willRenewLabel) {
77
+ details.push({
78
+ label: translations.willRenewLabel,
79
+ value: willRenew ? (translations.willRenewYes ?? "Yes") : (translations.willRenewNo ?? "No"),
80
+ highlight: !willRenew,
81
+ });
82
+ }
83
+
84
+ if (packageType && translations.periodTypeLabel) {
85
+ details.push({
86
+ label: translations.periodTypeLabel,
87
+ value: formatPackageTypeForDisplay(packageType),
88
+ });
89
+ }
90
+
91
+ if (store && translations.storeLabel) {
92
+ details.push({
93
+ label: translations.storeLabel,
94
+ value: store,
95
+ });
96
+ }
97
+
98
+ if (originalPurchaseDate && translations.originalPurchaseDateLabel) {
99
+ details.push({
100
+ label: translations.originalPurchaseDateLabel,
101
+ value: originalPurchaseDate,
102
+ });
103
+ }
104
+
105
+ if (latestPurchaseDate && translations.latestPurchaseDateLabel) {
106
+ details.push({
107
+ label: translations.latestPurchaseDateLabel,
108
+ value: latestPurchaseDate,
109
+ });
110
+ }
111
+
112
+ if (billingIssuesDetected && translations.billingIssuesLabel) {
113
+ details.push({
114
+ label: translations.billingIssuesLabel,
115
+ value: translations.billingIssuesDetected ?? "Detected",
116
+ highlight: true,
117
+ });
118
+ }
119
+
120
+ if (typeof __DEV__ !== 'undefined' && __DEV__ && isSandbox && translations.sandboxLabel) {
121
+ details.push({
122
+ label: translations.sandboxLabel,
123
+ value: translations.sandboxTestMode ?? "Test Mode",
124
+ });
125
+ }
126
+
127
+ return details;
128
+ }
129
+
130
+ export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps> = (props) => {
131
+ const { styles } = props;
132
+ const details = buildDetails(props);
133
+
134
+ return (
135
+ <View style={styles.details}>
136
+ {details.map((detail) => (
137
+ <DetailRow
138
+ key={detail.label}
139
+ label={detail.label!}
140
+ value={detail.value}
141
+ highlight={detail.highlight}
142
+ style={styles.row}
143
+ labelStyle={styles.label}
144
+ valueStyle={styles.value}
145
+ />
146
+ ))}
147
+ </View>
148
+ );
149
+ };
@@ -58,6 +58,13 @@ export const BalanceCard: React.FC<BalanceCardProps> = React.memo(({
58
58
  </View>
59
59
  </View>
60
60
  );
61
+ }, (prevProps, nextProps) => {
62
+ // PERFORMANCE: Custom comparison to prevent unnecessary re-renders
63
+ return (
64
+ prevProps.balance === nextProps.balance &&
65
+ prevProps.translations === nextProps.translations &&
66
+ prevProps.iconName === nextProps.iconName
67
+ );
61
68
  });
62
69
 
63
70
  const styles = StyleSheet.create({
@@ -44,4 +44,15 @@ export const TransactionItem: React.FC<TransactionItemProps> = React.memo(({
44
44
  </AtomicText>
45
45
  </View>
46
46
  );
47
+ }, (prevProps, nextProps) => {
48
+ // PERFORMANCE: Custom comparison to prevent unnecessary re-renders
49
+ return (
50
+ prevProps.transaction.id === nextProps.transaction.id &&
51
+ prevProps.transaction.change === nextProps.transaction.change &&
52
+ prevProps.transaction.reason === nextProps.transaction.reason &&
53
+ prevProps.transaction.description === nextProps.transaction.description &&
54
+ prevProps.transaction.createdAt === nextProps.transaction.createdAt &&
55
+ prevProps.translations === nextProps.translations &&
56
+ prevProps.dateFormatter === nextProps.dateFormatter
57
+ );
47
58
  });
@@ -35,6 +35,6 @@ export type { PaywallScreenProps } from "./domains/paywall/components/PaywallScr
35
35
 
36
36
  // Root Flow Components
37
37
  export { ManagedSubscriptionFlow } from "./domains/subscription/presentation/components/ManagedSubscriptionFlow";
38
- export type { ManagedSubscriptionFlowProps } from "./domains/subscription/presentation/components/ManagedSubscriptionFlow";
38
+ export type { ManagedSubscriptionFlowProps } from "./domains/subscription/presentation/components/ManagedSubscriptionFlow.types";
39
39
  export { SubscriptionFlowStatus } from "./domains/subscription/presentation/useSubscriptionFlow";
40
40
  export { SubscriptionFlowProvider, useSubscriptionFlowStatus } from "./domains/subscription/presentation/providers/SubscriptionFlowProvider";
@@ -36,8 +36,10 @@ class SubscriptionEventBus {
36
36
  const listeners = this.listeners.get(event);
37
37
  if (!listeners || listeners.size === 0) return;
38
38
 
39
- listeners.forEach(callback => {
40
- queueMicrotask(() => {
39
+ // PERFORMANCE: Batch all callbacks in a single microtask to reduce call stack overhead
40
+ // This prevents UI jank when multiple listeners are registered
41
+ queueMicrotask(() => {
42
+ listeners.forEach(callback => {
41
43
  try {
42
44
  callback(data);
43
45
  } catch (error) {
@@ -14,7 +14,7 @@
14
14
  * - Support for both document and collection queries
15
15
  */
16
16
 
17
- import { useEffect, useState, useCallback } from "react";
17
+ import { useEffect, useState, useCallback, useRef } from "react";
18
18
  import {
19
19
  onSnapshot,
20
20
  type Query,
@@ -65,6 +65,8 @@ export type Mapper<TDocument, TEntity> = (
65
65
  /**
66
66
  * Generic hook for real-time document sync via Firestore onSnapshot.
67
67
  *
68
+ * PERFORMANCE: Uses useRef to stabilize mapper and prevent unnecessary re-subscriptions
69
+ *
68
70
  * @template TDocument - Firestore document type
69
71
  * @template TEntity - Domain entity type
70
72
  *
@@ -85,6 +87,10 @@ export function useFirestoreDocumentRealTime<TDocument, TEntity>(
85
87
  const [isLoading, setIsLoading] = useState(true);
86
88
  const [error, setError] = useState<Error | null>(null);
87
89
 
90
+ // Stabilize mapper to prevent re-subscriptions when parent re-renders
91
+ const mapperRef = useRef(mapper);
92
+ mapperRef.current = mapper;
93
+
88
94
  useEffect(() => {
89
95
  // Reset state when userId changes
90
96
  if (!userId) {
@@ -106,7 +112,7 @@ export function useFirestoreDocumentRealTime<TDocument, TEntity>(
106
112
  docRef,
107
113
  (snapshot) => {
108
114
  if (snapshot.exists()) {
109
- const entity = mapper(snapshot.data() as TDocument, snapshot.id);
115
+ const entity = mapperRef.current(snapshot.data() as TDocument, snapshot.id);
110
116
  setData(entity);
111
117
  } else {
112
118
  setData(null);
@@ -123,7 +129,7 @@ export function useFirestoreDocumentRealTime<TDocument, TEntity>(
123
129
  return () => {
124
130
  unsubscribe();
125
131
  };
126
- }, [userId, docRef, mapper, tag]);
132
+ }, [userId, docRef, tag]); // Removed mapper from deps
127
133
 
128
134
  const refetch = useCallback(() => {
129
135
  // Real-time sync doesn't need refetch, but keep for API compatibility
@@ -143,6 +149,8 @@ export function useFirestoreDocumentRealTime<TDocument, TEntity>(
143
149
  /**
144
150
  * Generic hook for real-time collection sync via Firestore onSnapshot.
145
151
  *
152
+ * PERFORMANCE: Uses useRef to stabilize mapper and optimize snapshot processing
153
+ *
146
154
  * @template TDocument - Firestore document type
147
155
  * @template TEntity - Domain entity type
148
156
  *
@@ -163,6 +171,10 @@ export function useFirestoreCollectionRealTime<TDocument, TEntity>(
163
171
  const [isLoading, setIsLoading] = useState(true);
164
172
  const [error, setError] = useState<Error | null>(null);
165
173
 
174
+ // Stabilize mapper to prevent re-subscriptions when parent re-renders
175
+ const mapperRef = useRef(mapper);
176
+ mapperRef.current = mapper;
177
+
166
178
  useEffect(() => {
167
179
  // Reset state when userId changes
168
180
  if (!userId) {
@@ -178,10 +190,14 @@ export function useFirestoreCollectionRealTime<TDocument, TEntity>(
178
190
  const unsubscribe = onSnapshot(
179
191
  query,
180
192
  (snapshot) => {
181
- const entities: TEntity[] = [];
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
+
182
197
  snapshot.forEach((doc) => {
183
- entities.push(mapper(doc.data() as TDocument, doc.id));
198
+ entities[index++] = mapperRef.current(doc.data() as TDocument, doc.id);
184
199
  });
200
+
185
201
  setData(entities);
186
202
  setIsLoading(false);
187
203
  },
@@ -195,7 +211,7 @@ export function useFirestoreCollectionRealTime<TDocument, TEntity>(
195
211
  return () => {
196
212
  unsubscribe();
197
213
  };
198
- }, [userId, query, mapper, tag]);
214
+ }, [userId, query, tag]); // Removed mapper from deps
199
215
 
200
216
  const refetch = useCallback(() => {
201
217
  // Real-time sync doesn't need refetch, but keep for API compatibility
@@ -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
+ }