@umituz/react-native-design-system 2.9.27 → 2.9.28
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/exports/init.ts +5 -0
- package/src/index.ts +5 -0
- package/src/init/createAppInitializer.ts +162 -0
- package/src/init/env/createEnvConfig.ts +170 -0
- package/src/init/env/index.ts +13 -0
- package/src/init/env/types.ts +86 -0
- package/src/init/index.ts +29 -0
- package/src/init/types.ts +63 -0
- package/src/init/useAppInitialization.ts +75 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.28",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -130,3 +130,8 @@ export * from './exports/tanstack';
|
|
|
130
130
|
// LOADING EXPORTS
|
|
131
131
|
// =============================================================================
|
|
132
132
|
export * from './exports/loading';
|
|
133
|
+
|
|
134
|
+
// =============================================================================
|
|
135
|
+
// INIT EXPORTS
|
|
136
|
+
// =============================================================================
|
|
137
|
+
export * from './exports/init';
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Initializer Factory
|
|
3
|
+
* Creates a singleton app initializer with ordered module execution
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AppInitializerConfig,
|
|
8
|
+
AppInitializerResult,
|
|
9
|
+
InitModule,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
declare const __DEV__: boolean;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create an app initializer with singleton pattern
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const initializeApp = createAppInitializer({
|
|
20
|
+
* modules: [
|
|
21
|
+
* { name: 'firebase', init: initFirebase, critical: true },
|
|
22
|
+
* { name: 'auth', init: initAuth, critical: true, dependsOn: ['firebase'] },
|
|
23
|
+
* { name: 'subscription', init: initSubscription, dependsOn: ['auth'] },
|
|
24
|
+
* { name: 'ai', init: initAI },
|
|
25
|
+
* ],
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* await initializeApp();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function createAppInitializer(
|
|
32
|
+
config: AppInitializerConfig
|
|
33
|
+
): () => Promise<AppInitializerResult> {
|
|
34
|
+
const {
|
|
35
|
+
modules,
|
|
36
|
+
debug = typeof __DEV__ !== "undefined" && __DEV__,
|
|
37
|
+
continueOnError = true,
|
|
38
|
+
onComplete,
|
|
39
|
+
onError,
|
|
40
|
+
} = config;
|
|
41
|
+
|
|
42
|
+
let initializationPromise: Promise<AppInitializerResult> | null = null;
|
|
43
|
+
let isInitialized = false;
|
|
44
|
+
|
|
45
|
+
const log = (message: string, ...args: unknown[]) => {
|
|
46
|
+
if (debug) {
|
|
47
|
+
console.log(`[AppInit] ${message}`, ...args);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const sortModulesByDependency = (mods: InitModule[]): InitModule[] => {
|
|
52
|
+
const sorted: InitModule[] = [];
|
|
53
|
+
const visited = new Set<string>();
|
|
54
|
+
const visiting = new Set<string>();
|
|
55
|
+
|
|
56
|
+
const visit = (mod: InitModule) => {
|
|
57
|
+
if (visited.has(mod.name)) return;
|
|
58
|
+
if (visiting.has(mod.name)) {
|
|
59
|
+
throw new Error(`Circular dependency detected: ${mod.name}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
visiting.add(mod.name);
|
|
63
|
+
|
|
64
|
+
if (mod.dependsOn) {
|
|
65
|
+
for (const depName of mod.dependsOn) {
|
|
66
|
+
const dep = mods.find((m) => m.name === depName);
|
|
67
|
+
if (dep) visit(dep);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
visiting.delete(mod.name);
|
|
72
|
+
visited.add(mod.name);
|
|
73
|
+
sorted.push(mod);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
for (const mod of mods) {
|
|
77
|
+
visit(mod);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return sorted;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const performInitialization = async (): Promise<AppInitializerResult> => {
|
|
84
|
+
const startTime = Date.now();
|
|
85
|
+
const failedModules: string[] = [];
|
|
86
|
+
|
|
87
|
+
log("Starting initialization...");
|
|
88
|
+
|
|
89
|
+
const sortedModules = sortModulesByDependency(modules);
|
|
90
|
+
|
|
91
|
+
for (const mod of sortedModules) {
|
|
92
|
+
log(`Initializing: ${mod.name}`);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await mod.init();
|
|
96
|
+
|
|
97
|
+
if (result === false) {
|
|
98
|
+
throw new Error(`Module ${mod.name} returned false`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
log(`Completed: ${mod.name}`);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
log(`Failed: ${mod.name}`, error);
|
|
104
|
+
failedModules.push(mod.name);
|
|
105
|
+
|
|
106
|
+
onError?.(mod.name, error);
|
|
107
|
+
|
|
108
|
+
if (mod.critical && !continueOnError) {
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
failedModules,
|
|
112
|
+
duration: Date.now() - startTime,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const duration = Date.now() - startTime;
|
|
119
|
+
const success = failedModules.length === 0;
|
|
120
|
+
|
|
121
|
+
log(`Initialization complete in ${duration}ms`, {
|
|
122
|
+
success,
|
|
123
|
+
failedModules,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
isInitialized = true;
|
|
127
|
+
onComplete?.();
|
|
128
|
+
|
|
129
|
+
return { success, failedModules, duration };
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return async () => {
|
|
133
|
+
if (isInitialized) {
|
|
134
|
+
return { success: true, failedModules: [], duration: 0 };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (initializationPromise) {
|
|
138
|
+
return initializationPromise;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
initializationPromise = performInitialization();
|
|
142
|
+
return initializationPromise;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create a simple initialization module
|
|
148
|
+
*/
|
|
149
|
+
export function createInitModule(
|
|
150
|
+
name: string,
|
|
151
|
+
init: () => Promise<void> | void,
|
|
152
|
+
options?: Partial<Omit<InitModule, "name" | "init">>
|
|
153
|
+
): InitModule {
|
|
154
|
+
return {
|
|
155
|
+
name,
|
|
156
|
+
init: async () => {
|
|
157
|
+
await init();
|
|
158
|
+
return true;
|
|
159
|
+
},
|
|
160
|
+
...options,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Configuration Factory
|
|
3
|
+
* Creates type-safe environment accessors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Platform } from "react-native";
|
|
7
|
+
import type {
|
|
8
|
+
EnvConfig,
|
|
9
|
+
EnvAccessor,
|
|
10
|
+
EnvSourceConfig,
|
|
11
|
+
FirebaseEnvConfig,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
declare const __DEV__: boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default key mappings for environment variables
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_KEY_MAPPINGS: Record<string, string> = {
|
|
20
|
+
// RevenueCat
|
|
21
|
+
EXPO_PUBLIC_REVENUECAT_PROD_API_KEY_IOS: "revenueCatApiKeyIos",
|
|
22
|
+
EXPO_PUBLIC_REVENUECAT_PROD_API_KEY_ANDROID: "revenueCatApiKeyAndroid",
|
|
23
|
+
EXPO_PUBLIC_REVENUECAT_IOS_KEY: "revenueCatApiKeyIos",
|
|
24
|
+
EXPO_PUBLIC_REVENUECAT_ANDROID_KEY: "revenueCatApiKeyAndroid",
|
|
25
|
+
// FAL AI
|
|
26
|
+
EXPO_PUBLIC_FAL_AI_API_KEY: "falApiKey",
|
|
27
|
+
EXPO_PUBLIC_FAL_AI_BASE_URL: "falBaseUrl",
|
|
28
|
+
// Firebase
|
|
29
|
+
EXPO_PUBLIC_FIREBASE_API_KEY: "firebaseApiKey",
|
|
30
|
+
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN: "firebaseAuthDomain",
|
|
31
|
+
EXPO_PUBLIC_FIREBASE_PROJECT_ID: "firebaseProjectId",
|
|
32
|
+
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET: "firebaseStorageBucket",
|
|
33
|
+
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: "firebaseMessagingSenderId",
|
|
34
|
+
EXPO_PUBLIC_FIREBASE_APP_ID: "firebaseAppId",
|
|
35
|
+
// OpenAI
|
|
36
|
+
EXPO_PUBLIC_OPENAI_API_KEY: "openAIApiKey",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get environment value with fallback chain
|
|
41
|
+
*/
|
|
42
|
+
function getEnvValue(
|
|
43
|
+
key: string,
|
|
44
|
+
sources: EnvSourceConfig,
|
|
45
|
+
keyMappings: Record<string, string>
|
|
46
|
+
): string | undefined {
|
|
47
|
+
// 1. Try process.env first
|
|
48
|
+
if (sources.processEnv?.[key]) {
|
|
49
|
+
return sources.processEnv[key];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. Try mapped extra key
|
|
53
|
+
const mappedKey = keyMappings[key];
|
|
54
|
+
if (mappedKey && sources.expoExtra) {
|
|
55
|
+
const value = sources.expoExtra[mappedKey];
|
|
56
|
+
// Filter out placeholder strings
|
|
57
|
+
if (typeof value === "string" && !value.startsWith("$EXPO_PUBLIC_")) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create environment configuration accessor
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import Constants from 'expo-constants';
|
|
71
|
+
*
|
|
72
|
+
* const env = createEnvConfig({
|
|
73
|
+
* revenueCat: {
|
|
74
|
+
* testStoreKey: 'test_xxx', // Only used in __DEV__
|
|
75
|
+
* },
|
|
76
|
+
* fal: {
|
|
77
|
+
* baseUrl: 'https://fal.run',
|
|
78
|
+
* },
|
|
79
|
+
* }, {
|
|
80
|
+
* expoExtra: Constants.expoConfig?.extra,
|
|
81
|
+
* processEnv: process.env,
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* const apiKey = env.getRevenueCatApiKey();
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function createEnvConfig(
|
|
88
|
+
config: EnvConfig = {},
|
|
89
|
+
sources: EnvSourceConfig = {}
|
|
90
|
+
): EnvAccessor {
|
|
91
|
+
// Default sources
|
|
92
|
+
const resolvedSources: EnvSourceConfig = {
|
|
93
|
+
expoExtra: sources.expoExtra ?? {},
|
|
94
|
+
processEnv: sources.processEnv ?? (process.env as Record<string, string>),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const get = (key: string): string | undefined => {
|
|
98
|
+
// Check custom config first
|
|
99
|
+
if (config.custom?.[key]) {
|
|
100
|
+
return config.custom[key];
|
|
101
|
+
}
|
|
102
|
+
return getEnvValue(key, resolvedSources, DEFAULT_KEY_MAPPINGS);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const getRevenueCatApiKey = (): string => {
|
|
106
|
+
const platformKey =
|
|
107
|
+
Platform.OS === "ios"
|
|
108
|
+
? config.revenueCat?.iosKey ||
|
|
109
|
+
get("EXPO_PUBLIC_REVENUECAT_PROD_API_KEY_IOS") ||
|
|
110
|
+
get("EXPO_PUBLIC_REVENUECAT_IOS_KEY")
|
|
111
|
+
: config.revenueCat?.androidKey ||
|
|
112
|
+
get("EXPO_PUBLIC_REVENUECAT_PROD_API_KEY_ANDROID") ||
|
|
113
|
+
get("EXPO_PUBLIC_REVENUECAT_ANDROID_KEY");
|
|
114
|
+
|
|
115
|
+
return platformKey || "";
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const getRevenueCatTestStoreKey = (): string | undefined => {
|
|
119
|
+
// Only return test store key in development
|
|
120
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
121
|
+
return config.revenueCat?.testStoreKey;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const getFalApiKey = (): string => {
|
|
127
|
+
return (
|
|
128
|
+
config.fal?.apiKey || get("EXPO_PUBLIC_FAL_AI_API_KEY") || ""
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const getFalBaseUrl = (): string => {
|
|
133
|
+
return (
|
|
134
|
+
config.fal?.baseUrl ||
|
|
135
|
+
get("EXPO_PUBLIC_FAL_AI_BASE_URL") ||
|
|
136
|
+
"https://fal.run"
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const getFirebaseConfig = (): FirebaseEnvConfig => ({
|
|
141
|
+
apiKey:
|
|
142
|
+
config.firebase?.apiKey || get("EXPO_PUBLIC_FIREBASE_API_KEY") || "",
|
|
143
|
+
authDomain:
|
|
144
|
+
config.firebase?.authDomain ||
|
|
145
|
+
get("EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN") ||
|
|
146
|
+
"",
|
|
147
|
+
projectId:
|
|
148
|
+
config.firebase?.projectId ||
|
|
149
|
+
get("EXPO_PUBLIC_FIREBASE_PROJECT_ID") ||
|
|
150
|
+
"",
|
|
151
|
+
storageBucket:
|
|
152
|
+
config.firebase?.storageBucket ||
|
|
153
|
+
get("EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET") ||
|
|
154
|
+
"",
|
|
155
|
+
messagingSenderId:
|
|
156
|
+
config.firebase?.messagingSenderId ||
|
|
157
|
+
get("EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID") ||
|
|
158
|
+
"",
|
|
159
|
+
appId: config.firebase?.appId || get("EXPO_PUBLIC_FIREBASE_APP_ID") || "",
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
getRevenueCatApiKey,
|
|
164
|
+
getRevenueCatTestStoreKey,
|
|
165
|
+
getFalApiKey,
|
|
166
|
+
getFalBaseUrl,
|
|
167
|
+
getFirebaseConfig,
|
|
168
|
+
get,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Configuration Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Environment source configuration
|
|
7
|
+
*/
|
|
8
|
+
export interface EnvSourceConfig {
|
|
9
|
+
/** Expo Constants extra config */
|
|
10
|
+
expoExtra?: Record<string, unknown>;
|
|
11
|
+
/** Process env (for EXPO_PUBLIC_ vars) */
|
|
12
|
+
processEnv?: Record<string, string | undefined>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Key mapping from EXPO_PUBLIC_ to extra key
|
|
17
|
+
*/
|
|
18
|
+
export interface EnvKeyMapping {
|
|
19
|
+
/** Environment variable name (e.g., 'EXPO_PUBLIC_FAL_API_KEY') */
|
|
20
|
+
envKey: string;
|
|
21
|
+
/** Extra config key (e.g., 'falApiKey') */
|
|
22
|
+
extraKey: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* RevenueCat configuration
|
|
27
|
+
*/
|
|
28
|
+
export interface RevenueCatEnvConfig {
|
|
29
|
+
/** iOS API key */
|
|
30
|
+
iosKey?: string;
|
|
31
|
+
/** Android API key */
|
|
32
|
+
androidKey?: string;
|
|
33
|
+
/** Test store key (for development) */
|
|
34
|
+
testStoreKey?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* FAL AI configuration
|
|
39
|
+
*/
|
|
40
|
+
export interface FalEnvConfig {
|
|
41
|
+
/** API key */
|
|
42
|
+
apiKey?: string;
|
|
43
|
+
/** Base URL (default: https://fal.run) */
|
|
44
|
+
baseUrl?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Firebase configuration
|
|
49
|
+
*/
|
|
50
|
+
export interface FirebaseEnvConfig {
|
|
51
|
+
apiKey?: string;
|
|
52
|
+
authDomain?: string;
|
|
53
|
+
projectId?: string;
|
|
54
|
+
storageBucket?: string;
|
|
55
|
+
messagingSenderId?: string;
|
|
56
|
+
appId?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Full environment configuration
|
|
61
|
+
*/
|
|
62
|
+
export interface EnvConfig {
|
|
63
|
+
revenueCat?: RevenueCatEnvConfig;
|
|
64
|
+
fal?: FalEnvConfig;
|
|
65
|
+
firebase?: FirebaseEnvConfig;
|
|
66
|
+
/** Custom keys */
|
|
67
|
+
custom?: Record<string, string | undefined>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Created environment accessor
|
|
72
|
+
*/
|
|
73
|
+
export interface EnvAccessor {
|
|
74
|
+
/** Get RevenueCat API key (platform-specific) */
|
|
75
|
+
getRevenueCatApiKey: () => string;
|
|
76
|
+
/** Get RevenueCat test store key (dev only) */
|
|
77
|
+
getRevenueCatTestStoreKey: () => string | undefined;
|
|
78
|
+
/** Get FAL API key */
|
|
79
|
+
getFalApiKey: () => string;
|
|
80
|
+
/** Get FAL base URL */
|
|
81
|
+
getFalBaseUrl: () => string;
|
|
82
|
+
/** Get Firebase config */
|
|
83
|
+
getFirebaseConfig: () => FirebaseEnvConfig;
|
|
84
|
+
/** Get custom env value */
|
|
85
|
+
get: (key: string) => string | undefined;
|
|
86
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Initialization Module
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for app initialization:
|
|
5
|
+
* - createAppInitializer: Factory for creating app initializers
|
|
6
|
+
* - useAppInitialization: Hook for managing initialization state
|
|
7
|
+
* - createEnvConfig: Environment configuration factory
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// App Initializer
|
|
11
|
+
export {
|
|
12
|
+
createAppInitializer,
|
|
13
|
+
createInitModule,
|
|
14
|
+
} from "./createAppInitializer";
|
|
15
|
+
|
|
16
|
+
// Initialization Hook
|
|
17
|
+
export { useAppInitialization } from "./useAppInitialization";
|
|
18
|
+
|
|
19
|
+
// Environment Configuration
|
|
20
|
+
export * from "./env";
|
|
21
|
+
|
|
22
|
+
// Types
|
|
23
|
+
export type {
|
|
24
|
+
InitModule,
|
|
25
|
+
AppInitializerConfig,
|
|
26
|
+
AppInitializerResult,
|
|
27
|
+
UseAppInitializationOptions,
|
|
28
|
+
UseAppInitializationReturn,
|
|
29
|
+
} from "./types";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Initialization Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Single initialization module
|
|
7
|
+
*/
|
|
8
|
+
export interface InitModule {
|
|
9
|
+
/** Module name for logging */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Initialize function - returns success status */
|
|
12
|
+
init: () => Promise<boolean> | boolean;
|
|
13
|
+
/** Optional: Whether this module is critical (default: false) */
|
|
14
|
+
critical?: boolean;
|
|
15
|
+
/** Optional: Dependencies - other module names that must run first */
|
|
16
|
+
dependsOn?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* App initializer configuration
|
|
21
|
+
*/
|
|
22
|
+
export interface AppInitializerConfig {
|
|
23
|
+
/** Initialization modules to run */
|
|
24
|
+
modules: InitModule[];
|
|
25
|
+
/** Enable debug logging (default: __DEV__) */
|
|
26
|
+
debug?: boolean;
|
|
27
|
+
/** Continue on non-critical module failure (default: true) */
|
|
28
|
+
continueOnError?: boolean;
|
|
29
|
+
/** Callback when all modules complete */
|
|
30
|
+
onComplete?: () => void;
|
|
31
|
+
/** Callback on error */
|
|
32
|
+
onError?: (moduleName: string, error: unknown) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* App initializer result
|
|
37
|
+
*/
|
|
38
|
+
export interface AppInitializerResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
failedModules: string[];
|
|
41
|
+
duration: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* useAppInitialization hook options
|
|
46
|
+
*/
|
|
47
|
+
export interface UseAppInitializationOptions {
|
|
48
|
+
/** Skip initialization (useful for testing) */
|
|
49
|
+
skip?: boolean;
|
|
50
|
+
/** Callback when initialization completes */
|
|
51
|
+
onReady?: () => void;
|
|
52
|
+
/** Callback on error */
|
|
53
|
+
onError?: (error: Error) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* useAppInitialization hook return type
|
|
58
|
+
*/
|
|
59
|
+
export interface UseAppInitializationReturn {
|
|
60
|
+
isReady: boolean;
|
|
61
|
+
isLoading: boolean;
|
|
62
|
+
error: Error | null;
|
|
63
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAppInitialization Hook
|
|
3
|
+
* Manages app initialization state in React components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect, useCallback } from "react";
|
|
7
|
+
import type {
|
|
8
|
+
UseAppInitializationOptions,
|
|
9
|
+
UseAppInitializationReturn,
|
|
10
|
+
AppInitializerResult,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hook to manage app initialization
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const { isReady, isLoading, error } = useAppInitialization(initializeApp, {
|
|
19
|
+
* onReady: () => console.log('App ready!'),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* if (isLoading) return <SplashScreen />;
|
|
23
|
+
* if (error) return <ErrorScreen error={error} />;
|
|
24
|
+
* return <App />;
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function useAppInitialization(
|
|
28
|
+
initializer: () => Promise<AppInitializerResult>,
|
|
29
|
+
options: UseAppInitializationOptions = {}
|
|
30
|
+
): UseAppInitializationReturn {
|
|
31
|
+
const { skip = false, onReady, onError } = options;
|
|
32
|
+
|
|
33
|
+
const [isReady, setIsReady] = useState(false);
|
|
34
|
+
const [isLoading, setIsLoading] = useState(!skip);
|
|
35
|
+
const [error, setError] = useState<Error | null>(null);
|
|
36
|
+
|
|
37
|
+
const initialize = useCallback(async () => {
|
|
38
|
+
if (skip) {
|
|
39
|
+
setIsReady(true);
|
|
40
|
+
setIsLoading(false);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
setIsLoading(true);
|
|
46
|
+
setError(null);
|
|
47
|
+
|
|
48
|
+
const result = await initializer();
|
|
49
|
+
|
|
50
|
+
if (!result.success && result.failedModules.length > 0) {
|
|
51
|
+
const criticalFailed = result.failedModules.length > 0;
|
|
52
|
+
if (criticalFailed) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Initialization failed: ${result.failedModules.join(", ")}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setIsReady(true);
|
|
60
|
+
onReady?.();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
63
|
+
setError(error);
|
|
64
|
+
onError?.(error);
|
|
65
|
+
} finally {
|
|
66
|
+
setIsLoading(false);
|
|
67
|
+
}
|
|
68
|
+
}, [initializer, skip, onReady, onError]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
initialize();
|
|
72
|
+
}, [initialize]);
|
|
73
|
+
|
|
74
|
+
return { isReady, isLoading, error };
|
|
75
|
+
}
|