@umituz/react-native-ai-fal-provider 2.0.37 → 2.0.39

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 (37) hide show
  1. package/package.json +1 -1
  2. package/src/infrastructure/services/fal-models.service.ts +4 -1
  3. package/src/infrastructure/services/fal-provider-subscription.ts +17 -7
  4. package/src/infrastructure/services/fal-provider.ts +11 -1
  5. package/src/infrastructure/services/fal-queue-operations.ts +10 -0
  6. package/src/infrastructure/services/request-store.ts +53 -12
  7. package/src/infrastructure/utils/collection-filters.util.ts +5 -167
  8. package/src/infrastructure/utils/collections/array-filters.util.ts +52 -0
  9. package/src/infrastructure/utils/collections/array-reducers.util.ts +67 -0
  10. package/src/infrastructure/utils/collections/array-sorters.util.ts +60 -0
  11. package/src/infrastructure/utils/collections/index.ts +8 -0
  12. package/src/infrastructure/utils/cost-tracker.ts +6 -1
  13. package/src/infrastructure/utils/data-parsers.util.ts +5 -187
  14. package/src/infrastructure/utils/error-mapper.ts +10 -0
  15. package/src/infrastructure/utils/fal-error-handler.util.ts +21 -18
  16. package/src/infrastructure/utils/fal-generation-state-manager.util.ts +9 -2
  17. package/src/infrastructure/utils/fal-storage.util.ts +7 -2
  18. package/src/infrastructure/utils/general-helpers.util.ts +5 -146
  19. package/src/infrastructure/utils/helpers/function-helpers.util.ts +25 -0
  20. package/src/infrastructure/utils/helpers/index.ts +8 -0
  21. package/src/infrastructure/utils/helpers/object-helpers.util.ts +44 -0
  22. package/src/infrastructure/utils/helpers/timing-helpers.util.ts +89 -0
  23. package/src/infrastructure/utils/input-preprocessor.util.ts +12 -6
  24. package/src/infrastructure/utils/input-validator.util.ts +34 -3
  25. package/src/infrastructure/utils/parsers/index.ts +10 -0
  26. package/src/infrastructure/utils/parsers/json-parsers.util.ts +55 -0
  27. package/src/infrastructure/utils/parsers/number-helpers.util.ts +19 -0
  28. package/src/infrastructure/utils/parsers/object-transformers.util.ts +67 -0
  29. package/src/infrastructure/utils/parsers/object-validators.util.ts +38 -0
  30. package/src/infrastructure/utils/parsers/value-parsers.util.ts +45 -0
  31. package/src/infrastructure/utils/type-guards/constants.ts +10 -0
  32. package/src/infrastructure/utils/type-guards/index.ts +8 -0
  33. package/src/infrastructure/utils/type-guards/model-type-guards.util.ts +56 -0
  34. package/src/infrastructure/utils/type-guards/validation-guards.util.ts +80 -0
  35. package/src/infrastructure/utils/type-guards.util.ts +5 -115
  36. package/src/presentation/hooks/use-fal-generation.ts +15 -4
  37. package/src/presentation/hooks/use-models.ts +26 -9
@@ -1,191 +1,9 @@
1
1
  /**
2
2
  * Data Parser Utilities
3
- * Common patterns for parsing, validating, and transforming data
3
+ * @deprecated This file is now split into smaller modules for better maintainability.
4
+ * Import from './parsers' submodules instead.
5
+ *
6
+ * This file re-exports all functions for backward compatibility.
4
7
  */
5
8
 
6
- /**
7
- * Safely parse JSON with fallback
8
- */
9
- export function safeJsonParse<T = unknown>(
10
- data: string,
11
- fallback: T
12
- ): T {
13
- try {
14
- return JSON.parse(data) as T;
15
- } catch {
16
- return fallback;
17
- }
18
- }
19
-
20
- /**
21
- * Safely parse JSON with null fallback
22
- */
23
- export function safeJsonParseOrNull<T = unknown>(data: string): T | null {
24
- try {
25
- return JSON.parse(data) as T;
26
- } catch {
27
- return null;
28
- }
29
- }
30
-
31
- /**
32
- * Safely stringify object with fallback
33
- */
34
- export function safeJsonStringify(
35
- data: unknown,
36
- fallback: string
37
- ): string {
38
- try {
39
- return JSON.stringify(data);
40
- } catch {
41
- return fallback;
42
- }
43
- }
44
-
45
- /**
46
- * Check if string is valid JSON
47
- */
48
- export function isValidJson(data: string): boolean {
49
- try {
50
- JSON.parse(data);
51
- return true;
52
- } catch {
53
- return false;
54
- }
55
- }
56
-
57
- /**
58
- * Validate object structure
59
- */
60
- export function validateObjectStructure<T extends Record<string, unknown>>(
61
- data: unknown,
62
- requiredKeys: readonly (keyof T)[]
63
- ): data is T {
64
- if (!data || typeof data !== "object") {
65
- return false;
66
- }
67
-
68
- for (const key of requiredKeys) {
69
- if (!(key in data)) {
70
- return false;
71
- }
72
- }
73
-
74
- return true;
75
- }
76
-
77
- /**
78
- * Validate array of objects
79
- */
80
- export function validateObjectArray<T>(
81
- data: unknown,
82
- validator: (item: unknown) => item is T
83
- ): data is T[] {
84
- if (!Array.isArray(data)) {
85
- return false;
86
- }
87
-
88
- return data.every(validator);
89
- }
90
-
91
- /**
92
- * Parse number with fallback
93
- */
94
- export function parseNumber(value: unknown, fallback: number): number {
95
- if (typeof value === "number") {
96
- return value;
97
- }
98
-
99
- if (typeof value === "string") {
100
- const parsed = Number.parseFloat(value);
101
- return Number.isNaN(parsed) ? fallback : parsed;
102
- }
103
-
104
- return fallback;
105
- }
106
-
107
- /**
108
- * Parse boolean with fallback
109
- */
110
- export function parseBoolean(value: unknown, fallback: boolean): boolean {
111
- if (typeof value === "boolean") {
112
- return value;
113
- }
114
-
115
- if (typeof value === "string") {
116
- const lower = value.toLowerCase().trim();
117
- if (lower === "true" || lower === "yes" || lower === "1") {
118
- return true;
119
- }
120
- if (lower === "false" || lower === "no" || lower === "0") {
121
- return false;
122
- }
123
- }
124
-
125
- if (typeof value === "number") {
126
- return value !== 0;
127
- }
128
-
129
- return fallback;
130
- }
131
-
132
- /**
133
- * Clamp number between min and max
134
- */
135
- export function clampNumber(value: number, min: number, max: number): number {
136
- return Math.min(Math.max(value, min), max);
137
- }
138
-
139
- /**
140
- * Round to decimal places
141
- */
142
- export function roundToDecimals(value: number, decimals: number): number {
143
- const multiplier = Math.pow(10, decimals);
144
- return Math.round(value * multiplier) / multiplier;
145
- }
146
-
147
- /**
148
- * Deep clone object using JSON
149
- */
150
- export function deepClone<T>(data: T): T {
151
- return safeJsonParse(safeJsonStringify(data, "{}"), data);
152
- }
153
-
154
- /**
155
- * Merge objects with later objects overriding earlier ones
156
- */
157
- export function mergeObjects<T extends Record<string, unknown>>(
158
- ...objects: Partial<T>[]
159
- ): T {
160
- return Object.assign({}, ...objects) as T;
161
- }
162
-
163
- /**
164
- * Pick specified properties from object
165
- */
166
- export function pickProperties<T extends Record<string, unknown>, K extends keyof T>(
167
- obj: T,
168
- keys: readonly K[]
169
- ): Pick<T, K> {
170
- const result = {} as Pick<T, K>;
171
- for (const key of keys) {
172
- if (key in obj) {
173
- result[key] = obj[key];
174
- }
175
- }
176
- return result;
177
- }
178
-
179
- /**
180
- * Omit specified properties from object
181
- */
182
- export function omitProperties<T extends Record<string, unknown>, K extends keyof T>(
183
- obj: T,
184
- keys: readonly K[]
185
- ): Omit<T, K> {
186
- const result = { ...obj };
187
- for (const key of keys) {
188
- delete result[key];
189
- }
190
- return result as Omit<T, K>;
191
- }
9
+ export * from './parsers';
@@ -1,8 +1,18 @@
1
1
  /**
2
2
  * FAL Error Mapper - Maps errors to user-friendly info
3
3
  *
4
+ * @deprecated This module is a re-export for backward compatibility.
5
+ * Import directly from './fal-error-handler.util' instead.
6
+ *
4
7
  * This module re-exports error handling functions from the unified
5
8
  * fal-error-handler.util module for backward compatibility.
9
+ *
10
+ * @example
11
+ * // Instead of:
12
+ * import { mapFalError } from './error-mapper';
13
+ *
14
+ * // Use:
15
+ * import { mapFalError } from './fal-error-handler.util';
6
16
  */
7
17
 
8
18
  export {
@@ -94,34 +94,37 @@ function categorizeError(error: unknown): FalErrorCategory {
94
94
  }
95
95
 
96
96
  /**
97
- * Map error to FalErrorInfo with full error details
97
+ * Build FalErrorInfo from error string and category
98
98
  */
99
- export function mapFalError(error: unknown): FalErrorInfo {
100
- const category = categorizeError(error);
101
-
102
- if (error instanceof Error) {
103
- return {
104
- type: category.type,
105
- messageKey: `fal.errors.${category.messageKey}`,
106
- retryable: category.retryable,
107
- originalError: error.message,
108
- originalErrorName: error.name,
109
- stack: error.stack,
110
- statusCode: extractStatusCode(error.message),
111
- };
112
- }
113
-
114
- const errorString = String(error);
115
-
99
+ function buildErrorInfo(
100
+ category: FalErrorCategory,
101
+ errorString: string,
102
+ errorInstance?: Error
103
+ ): FalErrorInfo {
116
104
  return {
117
105
  type: category.type,
118
106
  messageKey: `fal.errors.${category.messageKey}`,
119
107
  retryable: category.retryable,
120
108
  originalError: errorString,
109
+ originalErrorName: errorInstance?.name,
110
+ stack: errorInstance?.stack,
121
111
  statusCode: extractStatusCode(errorString),
122
112
  };
123
113
  }
124
114
 
115
+ /**
116
+ * Map error to FalErrorInfo with full error details
117
+ */
118
+ export function mapFalError(error: unknown): FalErrorInfo {
119
+ const category = categorizeError(error);
120
+
121
+ if (error instanceof Error) {
122
+ return buildErrorInfo(category, error.message, error);
123
+ }
124
+
125
+ return buildErrorInfo(category, String(error));
126
+ }
127
+
125
128
  /**
126
129
  * Parse FAL error and return user-friendly message
127
130
  */
@@ -25,6 +25,7 @@ export class FalGenerationStateManager<T> {
25
25
  private isMounted = true;
26
26
  private currentRequestId: string | null = null;
27
27
  private lastRequest: { endpoint: string; input: FalJobInput } | null = null;
28
+ private lastNotifiedStatus: string | null = null; // Track last status to prevent duplicate callbacks
28
29
 
29
30
  constructor(
30
31
  private options?: GenerationStateOptions<T>
@@ -57,6 +58,7 @@ export class FalGenerationStateManager<T> {
57
58
  clearLastRequest(): void {
58
59
  this.lastRequest = null;
59
60
  this.currentRequestId = null;
61
+ this.lastNotifiedStatus = null;
60
62
  }
61
63
 
62
64
  handleQueueUpdate(status: FalQueueStatus): void {
@@ -77,8 +79,13 @@ export class FalGenerationStateManager<T> {
77
79
  queuePosition: status.queuePosition,
78
80
  };
79
81
 
80
- this.options?.onQueueUpdate?.(normalizedStatus);
81
- this.options?.onProgress?.(normalizedStatus);
82
+ // Only notify if status actually changed (idempotent callbacks)
83
+ const statusKey = `${normalizedStatus.status}-${normalizedStatus.requestId}`;
84
+ if (this.lastNotifiedStatus !== statusKey) {
85
+ this.lastNotifiedStatus = statusKey;
86
+ this.options?.onQueueUpdate?.(normalizedStatus);
87
+ this.options?.onProgress?.(normalizedStatus);
88
+ }
82
89
  }
83
90
 
84
91
  handleResult(result: T): void {
@@ -30,8 +30,13 @@ export async function uploadToFalStorage(base64: string): Promise<string> {
30
30
  try {
31
31
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
32
32
  await deleteTempFile(tempUri);
33
- } catch {
34
- // Silently ignore cleanup errors
33
+ } catch (cleanupError) {
34
+ // Log cleanup errors to prevent disk space leaks
35
+ console.warn(
36
+ `[fal-storage] Failed to delete temp file: ${tempUri}`,
37
+ cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
38
+ );
39
+ // Don't throw - cleanup errors shouldn't fail the upload
35
40
  }
36
41
  }
37
42
  }
@@ -1,150 +1,9 @@
1
1
  /**
2
2
  * General Helper Utilities
3
- * Common utility functions
3
+ * @deprecated This file is now split into smaller modules for better maintainability.
4
+ * Import from './helpers' submodules instead.
5
+ *
6
+ * This file re-exports all functions for backward compatibility.
4
7
  */
5
8
 
6
- /**
7
- * Build error message with context
8
- */
9
- export function buildErrorMessage(
10
- type: string,
11
- context: Record<string, unknown>
12
- ): string {
13
- const contextStr = Object.entries(context)
14
- .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
15
- .join(", ");
16
- return `${type}${contextStr ? ` (${contextStr})` : ""}`;
17
- }
18
-
19
- /**
20
- * Check if value is defined (not null or undefined)
21
- */
22
- export function isDefined<T>(value: T | null | undefined): value is T {
23
- return value !== null && value !== undefined;
24
- }
25
-
26
- /**
27
- * Filter out null and undefined values from object
28
- */
29
- export function removeNullish<T extends Record<string, unknown>>(
30
- obj: T
31
- ): Partial<T> {
32
- return Object.fromEntries(
33
- Object.entries(obj).filter(([_, value]) => isDefined(value))
34
- ) as Partial<T>;
35
- }
36
-
37
- /**
38
- * Generate unique ID
39
- */
40
- export function generateUniqueId(prefix: string = ""): string {
41
- const timestamp = Date.now().toString(36);
42
- const randomStr = Math.random().toString(36).substring(2, 9);
43
- return prefix ? `${prefix}_${timestamp}${randomStr}` : `${timestamp}${randomStr}`;
44
- }
45
-
46
- /**
47
- * Create debounced function
48
- */
49
- export function debounce<T extends (...args: never[]) => void>(
50
- func: T,
51
- wait: number
52
- ): (...args: Parameters<T>) => void {
53
- let timeoutId: ReturnType<typeof setTimeout> | null = null;
54
-
55
- return function debounced(...args: Parameters<T>) {
56
- if (timeoutId) {
57
- clearTimeout(timeoutId);
58
- }
59
- timeoutId = setTimeout(() => {
60
- func(...args);
61
- }, wait);
62
- };
63
- }
64
-
65
- /**
66
- * Create throttled function
67
- */
68
- export function throttle<T extends (...args: never[]) => void>(
69
- func: T,
70
- limit: number
71
- ): (...args: Parameters<T>) => void {
72
- let inThrottle = false;
73
-
74
- return function throttled(...args: Parameters<T>) {
75
- if (!inThrottle) {
76
- func(...args);
77
- inThrottle = true;
78
- setTimeout(() => {
79
- inThrottle = false;
80
- }, limit);
81
- }
82
- };
83
- }
84
-
85
- /**
86
- * Sleep for specified milliseconds
87
- */
88
- export function sleep(ms: number): Promise<void> {
89
- return new Promise((resolve) => setTimeout(resolve, ms));
90
- }
91
-
92
- /**
93
- * Retry function with exponential backoff
94
- */
95
- export async function retry<T>(
96
- func: () => Promise<T>,
97
- options: {
98
- maxRetries?: number;
99
- baseDelay?: number;
100
- maxDelay?: number;
101
- shouldRetry?: (error: unknown) => boolean;
102
- } = {}
103
- ): Promise<T> {
104
- const {
105
- maxRetries = 3,
106
- baseDelay = 1000,
107
- maxDelay = 10000,
108
- shouldRetry = () => true,
109
- } = options;
110
-
111
- let lastError: unknown;
112
-
113
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
114
- try {
115
- return await func();
116
- } catch (error) {
117
- lastError = error;
118
-
119
- if (attempt === maxRetries || !shouldRetry(error)) {
120
- throw error;
121
- }
122
-
123
- const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
124
- await sleep(delay);
125
- }
126
- }
127
-
128
- throw lastError;
129
- }
130
-
131
- /**
132
- * No-op function
133
- */
134
- export function noop(): void {
135
- // Intentionally empty
136
- }
137
-
138
- /**
139
- * Identity function
140
- */
141
- export function identity<T>(value: T): T {
142
- return value;
143
- }
144
-
145
- /**
146
- * Constant function (returns same value regardless of input)
147
- */
148
- export function constant<T>(value: T): () => T {
149
- return () => value;
150
- }
9
+ export * from './helpers';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Function Helper Utilities
3
+ * Common functional programming helpers
4
+ */
5
+
6
+ /**
7
+ * No-op function
8
+ */
9
+ export function noop(): void {
10
+ // Intentionally empty
11
+ }
12
+
13
+ /**
14
+ * Identity function
15
+ */
16
+ export function identity<T>(value: T): T {
17
+ return value;
18
+ }
19
+
20
+ /**
21
+ * Constant function (returns same value regardless of input)
22
+ */
23
+ export function constant<T>(value: T): () => T {
24
+ return () => value;
25
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Helper Utilities - Centralized Exports
3
+ * Re-exports all helper utilities from submodules
4
+ */
5
+
6
+ export * from './timing-helpers.util';
7
+ export * from './object-helpers.util';
8
+ export * from './function-helpers.util';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Object Helper Utilities
3
+ * Object manipulation and validation helpers
4
+ */
5
+
6
+ /**
7
+ * Build error message with context
8
+ */
9
+ export function buildErrorMessage(
10
+ type: string,
11
+ context: Record<string, unknown>
12
+ ): string {
13
+ const contextStr = Object.entries(context)
14
+ .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
15
+ .join(", ");
16
+ return `${type}${contextStr ? ` (${contextStr})` : ""}`;
17
+ }
18
+
19
+ /**
20
+ * Check if value is defined (not null or undefined)
21
+ */
22
+ export function isDefined<T>(value: T | null | undefined): value is T {
23
+ return value !== null && value !== undefined;
24
+ }
25
+
26
+ /**
27
+ * Filter out null and undefined values from object
28
+ */
29
+ export function removeNullish<T extends Record<string, unknown>>(
30
+ obj: T
31
+ ): Partial<T> {
32
+ return Object.fromEntries(
33
+ Object.entries(obj).filter(([_, value]) => isDefined(value))
34
+ ) as Partial<T>;
35
+ }
36
+
37
+ /**
38
+ * Generate unique ID
39
+ */
40
+ export function generateUniqueId(prefix: string = ""): string {
41
+ const timestamp = Date.now().toString(36);
42
+ const randomStr = Math.random().toString(36).substring(2, 9);
43
+ return prefix ? `${prefix}_${timestamp}${randomStr}` : `${timestamp}${randomStr}`;
44
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Timing Helper Utilities
3
+ * Debounce, throttle, sleep, and retry operations
4
+ */
5
+
6
+ /**
7
+ * Create debounced function
8
+ */
9
+ export function debounce<T extends (...args: never[]) => void>(
10
+ func: T,
11
+ wait: number
12
+ ): (...args: Parameters<T>) => void {
13
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
14
+
15
+ return function debounced(...args: Parameters<T>) {
16
+ if (timeoutId) {
17
+ clearTimeout(timeoutId);
18
+ }
19
+ timeoutId = setTimeout(() => {
20
+ func(...args);
21
+ }, wait);
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Create throttled function
27
+ */
28
+ export function throttle<T extends (...args: never[]) => void>(
29
+ func: T,
30
+ limit: number
31
+ ): (...args: Parameters<T>) => void {
32
+ let inThrottle = false;
33
+
34
+ return function throttled(...args: Parameters<T>) {
35
+ if (!inThrottle) {
36
+ func(...args);
37
+ inThrottle = true;
38
+ setTimeout(() => {
39
+ inThrottle = false;
40
+ }, limit);
41
+ }
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Sleep for specified milliseconds
47
+ */
48
+ export function sleep(ms: number): Promise<void> {
49
+ return new Promise((resolve) => setTimeout(resolve, ms));
50
+ }
51
+
52
+ /**
53
+ * Retry function with exponential backoff
54
+ */
55
+ export async function retry<T>(
56
+ func: () => Promise<T>,
57
+ options: {
58
+ maxRetries?: number;
59
+ baseDelay?: number;
60
+ maxDelay?: number;
61
+ shouldRetry?: (error: unknown) => boolean;
62
+ } = {}
63
+ ): Promise<T> {
64
+ const {
65
+ maxRetries = 3,
66
+ baseDelay = 1000,
67
+ maxDelay = 10000,
68
+ shouldRetry = () => true,
69
+ } = options;
70
+
71
+ let lastError: unknown;
72
+
73
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
74
+ try {
75
+ return await func();
76
+ } catch (error) {
77
+ lastError = error;
78
+
79
+ if (attempt === maxRetries || !shouldRetry(error)) {
80
+ throw error;
81
+ }
82
+
83
+ const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
84
+ await sleep(delay);
85
+ }
86
+ }
87
+
88
+ throw lastError;
89
+ }
@@ -38,9 +38,12 @@ export async function preprocessInput(
38
38
  const uploadPromise = uploadToFalStorage(value)
39
39
  .then((url) => {
40
40
  result[key] = url;
41
+ return url;
41
42
  })
42
43
  .catch((error) => {
43
- throw new Error(`Failed to upload ${key}: ${error instanceof Error ? error.message : "Unknown error"}`);
44
+ const errorMessage = `Failed to upload ${key}: ${error instanceof Error ? error.message : "Unknown error"}`;
45
+ console.error(`[preprocessInput] ${errorMessage}`);
46
+ throw new Error(errorMessage);
44
47
  });
45
48
 
46
49
  uploadPromises.push(uploadPromise);
@@ -62,11 +65,14 @@ export async function preprocessInput(
62
65
  }
63
66
 
64
67
  if (isBase64DataUri(imageUrl)) {
65
- const uploadPromise = uploadToFalStorage(imageUrl).catch((error) => {
66
- const errorMessage = `Failed to upload image_urls[${i}]: ${error instanceof Error ? error.message : "Unknown error"}`;
67
- errors.push(errorMessage);
68
- throw new Error(errorMessage);
69
- });
68
+ const uploadPromise = uploadToFalStorage(imageUrl)
69
+ .then((url) => url)
70
+ .catch((error) => {
71
+ const errorMessage = `Failed to upload image_urls[${i}]: ${error instanceof Error ? error.message : "Unknown error"}`;
72
+ console.error(`[preprocessInput] ${errorMessage}`);
73
+ errors.push(errorMessage);
74
+ throw new Error(errorMessage);
75
+ });
70
76
  uploadTasks.push({ index: i, url: uploadPromise });
71
77
  } else if (typeof imageUrl === "string") {
72
78
  uploadTasks.push({ index: i, url: imageUrl });