@umituz/react-native-subscription 2.25.1 → 2.26.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/domain/value-objects/Result.ts +128 -0
- package/src/index.ts +16 -0
- package/src/infrastructure/utils/Logger.ts +114 -0
- package/src/presentation/hooks/useCredits.ts +7 -1
- package/src/presentation/hooks/useFeatureGate.ts +0 -2
- package/src/revenuecat/presentation/hooks/usePurchasePackage.ts +3 -2
- package/src/utils/index.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.0",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result Pattern
|
|
3
|
+
* Type-safe error handling without exceptions.
|
|
4
|
+
* Inspired by Rust's Result<T, E> and functional programming patterns.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Success result containing data */
|
|
8
|
+
export interface Success<T> {
|
|
9
|
+
readonly success: true;
|
|
10
|
+
readonly data: T;
|
|
11
|
+
readonly error?: never;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Failure result containing error */
|
|
15
|
+
export interface Failure<E = Error> {
|
|
16
|
+
readonly success: false;
|
|
17
|
+
readonly data?: never;
|
|
18
|
+
readonly error: E;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Union type representing either success or failure */
|
|
22
|
+
export type Result<T, E = Error> = Success<T> | Failure<E>;
|
|
23
|
+
|
|
24
|
+
/** Create a success result */
|
|
25
|
+
export function success<T>(data: T): Success<T> {
|
|
26
|
+
return { success: true, data };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Create a failure result */
|
|
30
|
+
export function failure<E = Error>(error: E): Failure<E> {
|
|
31
|
+
return { success: false, error };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Type guard to check if result is success */
|
|
35
|
+
export function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {
|
|
36
|
+
return result.success === true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Type guard to check if result is failure */
|
|
40
|
+
export function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {
|
|
41
|
+
return result.success === false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Unwrap result or throw if failure */
|
|
45
|
+
export function unwrap<T, E extends Error>(result: Result<T, E>): T {
|
|
46
|
+
if (isSuccess(result)) {
|
|
47
|
+
return result.data;
|
|
48
|
+
}
|
|
49
|
+
throw result.error;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Unwrap result or return default value */
|
|
53
|
+
export function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
|
|
54
|
+
if (isSuccess(result)) {
|
|
55
|
+
return result.data;
|
|
56
|
+
}
|
|
57
|
+
return defaultValue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Map success value to new value */
|
|
61
|
+
export function map<T, U, E>(
|
|
62
|
+
result: Result<T, E>,
|
|
63
|
+
fn: (data: T) => U
|
|
64
|
+
): Result<U, E> {
|
|
65
|
+
if (isSuccess(result)) {
|
|
66
|
+
return success(fn(result.data));
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Chain results (flatMap) */
|
|
72
|
+
export function flatMap<T, U, E>(
|
|
73
|
+
result: Result<T, E>,
|
|
74
|
+
fn: (data: T) => Result<U, E>
|
|
75
|
+
): Result<U, E> {
|
|
76
|
+
if (isSuccess(result)) {
|
|
77
|
+
return fn(result.data);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Execute async function and wrap result */
|
|
83
|
+
export async function tryCatch<T>(
|
|
84
|
+
fn: () => Promise<T>,
|
|
85
|
+
errorMapper?: (error: unknown) => Error
|
|
86
|
+
): Promise<Result<T, Error>> {
|
|
87
|
+
try {
|
|
88
|
+
const data = await fn();
|
|
89
|
+
return success(data);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const mappedError = errorMapper
|
|
92
|
+
? errorMapper(error)
|
|
93
|
+
: error instanceof Error
|
|
94
|
+
? error
|
|
95
|
+
: new Error(String(error));
|
|
96
|
+
return failure(mappedError);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Execute sync function and wrap result */
|
|
101
|
+
export function tryCatchSync<T>(
|
|
102
|
+
fn: () => T,
|
|
103
|
+
errorMapper?: (error: unknown) => Error
|
|
104
|
+
): Result<T, Error> {
|
|
105
|
+
try {
|
|
106
|
+
const data = fn();
|
|
107
|
+
return success(data);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const mappedError = errorMapper
|
|
110
|
+
? errorMapper(error)
|
|
111
|
+
: error instanceof Error
|
|
112
|
+
? error
|
|
113
|
+
: new Error(String(error));
|
|
114
|
+
return failure(mappedError);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Combine multiple results into one */
|
|
119
|
+
export function combine<T, E>(results: Result<T, E>[]): Result<T[], E> {
|
|
120
|
+
const data: T[] = [];
|
|
121
|
+
for (const result of results) {
|
|
122
|
+
if (isFailure(result)) {
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
data.push(result.data);
|
|
126
|
+
}
|
|
127
|
+
return success(data);
|
|
128
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,22 @@ export type { SubscriptionStatus, SubscriptionStatusType, PeriodType, StatusReso
|
|
|
18
18
|
export type { SubscriptionConfig } from "./domain/value-objects/SubscriptionConfig";
|
|
19
19
|
export type { ISubscriptionRepository } from "./application/ports/ISubscriptionRepository";
|
|
20
20
|
|
|
21
|
+
// Result Pattern (Functional Error Handling)
|
|
22
|
+
export {
|
|
23
|
+
success,
|
|
24
|
+
failure,
|
|
25
|
+
isSuccess,
|
|
26
|
+
isFailure,
|
|
27
|
+
unwrap,
|
|
28
|
+
unwrapOr,
|
|
29
|
+
map,
|
|
30
|
+
flatMap,
|
|
31
|
+
tryCatch,
|
|
32
|
+
tryCatchSync,
|
|
33
|
+
combine,
|
|
34
|
+
} from "./domain/value-objects/Result";
|
|
35
|
+
export type { Result, Success, Failure } from "./domain/value-objects/Result";
|
|
36
|
+
|
|
21
37
|
// Infrastructure Layer
|
|
22
38
|
export { SubscriptionService, initializeSubscriptionService } from "./infrastructure/services/SubscriptionService";
|
|
23
39
|
export {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription Logger
|
|
3
|
+
* Centralized logging utility for the subscription package.
|
|
4
|
+
* All logs are dev-only and automatically stripped in production.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
declare const __DEV__: boolean;
|
|
8
|
+
|
|
9
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
10
|
+
|
|
11
|
+
export interface LogContext {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Log categories for filtering and grouping */
|
|
16
|
+
export const LOG_CATEGORY = {
|
|
17
|
+
REVENUECAT: "RevenueCat",
|
|
18
|
+
CREDITS: "Credits",
|
|
19
|
+
TRIAL: "Trial",
|
|
20
|
+
PURCHASE: "Purchase",
|
|
21
|
+
FEATURE_GATE: "FeatureGate",
|
|
22
|
+
SUBSCRIPTION: "Subscription",
|
|
23
|
+
SYNC: "Sync",
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export type LogCategory = (typeof LOG_CATEGORY)[keyof typeof LOG_CATEGORY];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Development-only logger that automatically respects __DEV__ flag.
|
|
30
|
+
* All methods are no-ops in production builds.
|
|
31
|
+
*/
|
|
32
|
+
class SubscriptionLogger {
|
|
33
|
+
private enabled: boolean = true;
|
|
34
|
+
private categories: Set<LogCategory> = new Set();
|
|
35
|
+
|
|
36
|
+
/** Enable or disable all logging */
|
|
37
|
+
setEnabled(enabled: boolean): void {
|
|
38
|
+
this.enabled = enabled;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Enable logging for specific categories only */
|
|
42
|
+
setCategories(categories: LogCategory[]): void {
|
|
43
|
+
this.categories = new Set(categories);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Clear category filter (log all categories) */
|
|
47
|
+
clearCategories(): void {
|
|
48
|
+
this.categories.clear();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private shouldLog(category?: LogCategory): boolean {
|
|
52
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return false;
|
|
53
|
+
if (!this.enabled) return false;
|
|
54
|
+
if (this.categories.size > 0 && category && !this.categories.has(category)) return false;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private formatMessage(category: LogCategory | string, message: string): string {
|
|
59
|
+
return `[${category}] ${message}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
debug(category: LogCategory, message: string, context?: LogContext): void {
|
|
63
|
+
if (!this.shouldLog(category)) return;
|
|
64
|
+
console.log(this.formatMessage(category, message), context ?? "");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
info(category: LogCategory, message: string, context?: LogContext): void {
|
|
68
|
+
if (!this.shouldLog(category)) return;
|
|
69
|
+
console.log(this.formatMessage(category, message), context ?? "");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
warn(category: LogCategory, message: string, context?: LogContext): void {
|
|
73
|
+
if (!this.shouldLog(category)) return;
|
|
74
|
+
console.warn(this.formatMessage(category, message), context ?? "");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
error(category: LogCategory, message: string, error?: unknown, context?: LogContext): void {
|
|
78
|
+
if (!this.shouldLog(category)) return;
|
|
79
|
+
console.error(this.formatMessage(category, message), { error, ...context });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Log purchase flow events */
|
|
83
|
+
purchase(message: string, context?: LogContext): void {
|
|
84
|
+
this.debug(LOG_CATEGORY.PURCHASE, message, context);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Log credits-related events */
|
|
88
|
+
credits(message: string, context?: LogContext): void {
|
|
89
|
+
this.debug(LOG_CATEGORY.CREDITS, message, context);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Log trial-related events */
|
|
93
|
+
trial(message: string, context?: LogContext): void {
|
|
94
|
+
this.debug(LOG_CATEGORY.TRIAL, message, context);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Log RevenueCat SDK events */
|
|
98
|
+
revenueCat(message: string, context?: LogContext): void {
|
|
99
|
+
this.debug(LOG_CATEGORY.REVENUECAT, message, context);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Log feature gate events */
|
|
103
|
+
featureGate(message: string, context?: LogContext): void {
|
|
104
|
+
this.debug(LOG_CATEGORY.FEATURE_GATE, message, context);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Log sync operations */
|
|
108
|
+
sync(message: string, context?: LogContext): void {
|
|
109
|
+
this.debug(LOG_CATEGORY.SYNC, message, context);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Singleton logger instance */
|
|
114
|
+
export const Logger = new SubscriptionLogger();
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
isCreditsRepositoryConfigured,
|
|
15
15
|
} from "../../infrastructure/repositories/CreditsRepositoryProvider";
|
|
16
16
|
|
|
17
|
+
declare const __DEV__: boolean;
|
|
18
|
+
|
|
17
19
|
export const creditsQueryKeys = {
|
|
18
20
|
all: ["credits"] as const,
|
|
19
21
|
user: (userId: string) => ["credits", userId] as const,
|
|
@@ -77,7 +79,11 @@ export const useCredits = ({
|
|
|
77
79
|
|
|
78
80
|
// Background sync: If mapper detected expired status, sync to Firestore
|
|
79
81
|
if (result.data?.status === "expired") {
|
|
80
|
-
repository.syncExpiredStatus(userId).catch(() => {
|
|
82
|
+
repository.syncExpiredStatus(userId).catch((syncError) => {
|
|
83
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
84
|
+
console.warn("[useCredits] Background sync failed:", syncError);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
return result.data || null;
|
|
@@ -13,7 +13,8 @@ import { creditsQueryKeys } from "../../../presentation/hooks/useCredits";
|
|
|
13
13
|
|
|
14
14
|
declare const __DEV__: boolean;
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/** Purchase mutation result - simplified for presentation layer */
|
|
17
|
+
export interface PurchaseMutationResult {
|
|
17
18
|
success: boolean;
|
|
18
19
|
productId: string;
|
|
19
20
|
}
|
|
@@ -28,7 +29,7 @@ export const usePurchasePackage = (userId: string | undefined) => {
|
|
|
28
29
|
const { showSuccess, showError } = useAlert();
|
|
29
30
|
|
|
30
31
|
return useMutation({
|
|
31
|
-
mutationFn: async (pkg: PurchasesPackage): Promise<
|
|
32
|
+
mutationFn: async (pkg: PurchasesPackage): Promise<PurchaseMutationResult> => {
|
|
32
33
|
if (!userId) {
|
|
33
34
|
throw new Error("User not authenticated");
|
|
34
35
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -10,3 +10,7 @@ export * from "./priceUtils";
|
|
|
10
10
|
export * from "./tierUtils";
|
|
11
11
|
export * from "./types";
|
|
12
12
|
export * from "./validation";
|
|
13
|
+
|
|
14
|
+
// Logger (infrastructure utility re-exported for convenience)
|
|
15
|
+
export { Logger, LOG_CATEGORY } from "../infrastructure/utils/Logger";
|
|
16
|
+
export type { LogLevel, LogCategory, LogContext } from "../infrastructure/utils/Logger";
|