@umituz/react-native-ai-fal-provider 1.1.8 → 1.1.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-fal-provider",
3
- "version": "1.1.8",
3
+ "version": "1.1.9",
4
4
  "description": "FAL AI provider for React Native - implements IAIProvider interface for unified AI generation",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -8,6 +8,7 @@ export enum FalErrorType {
8
8
  TIMEOUT = "timeout",
9
9
  API_ERROR = "api_error",
10
10
  VALIDATION = "validation",
11
+ IMAGE_TOO_SMALL = "image_too_small",
11
12
  CONTENT_POLICY = "content_policy",
12
13
  RATE_LIMIT = "rate_limit",
13
14
  AUTHENTICATION = "authentication",
@@ -12,6 +12,42 @@ import { validateNSFWContent } from "../validators/nsfw-validator";
12
12
 
13
13
  declare const __DEV__: boolean | undefined;
14
14
 
15
+ interface FalApiErrorDetail {
16
+ msg?: string;
17
+ type?: string;
18
+ loc?: string[];
19
+ }
20
+
21
+ interface FalApiError {
22
+ body?: { detail?: FalApiErrorDetail[] } | string;
23
+ message?: string;
24
+ }
25
+
26
+ /**
27
+ * Parse FAL API error and extract user-friendly message
28
+ */
29
+ function parseFalError(error: unknown): string {
30
+ const fallback = error instanceof Error ? error.message : String(error);
31
+
32
+ const falError = error as FalApiError;
33
+ if (!falError?.body) return fallback;
34
+
35
+ const body = typeof falError.body === "string"
36
+ ? safeJsonParse(falError.body)
37
+ : falError.body;
38
+
39
+ const detail = body?.detail?.[0];
40
+ return detail?.msg ?? falError.message ?? fallback;
41
+ }
42
+
43
+ function safeJsonParse(str: string): { detail?: FalApiErrorDetail[] } | null {
44
+ try {
45
+ return JSON.parse(str);
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
15
51
  /**
16
52
  * Handle FAL subscription with timeout and cancellation
17
53
  */
@@ -91,6 +127,13 @@ export async function handleFalSubscription<T = unknown>(
91
127
 
92
128
  options?.onResult?.(result as T);
93
129
  return { result: result as T, requestId: currentRequestId };
130
+ } catch (error) {
131
+ // Parse FAL error and throw with user-friendly message
132
+ const userMessage = parseFalError(error);
133
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
134
+ console.error("[FalProvider] Error:", userMessage);
135
+ }
136
+ throw new Error(userMessage);
94
137
  } finally {
95
138
  if (timeoutId) clearTimeout(timeoutId);
96
139
  }
@@ -110,16 +153,24 @@ export async function handleFalRun<T = unknown>(
110
153
  console.log("[FalProvider] run() model:", model, "inputKeys:", Object.keys(input));
111
154
  }
112
155
 
113
- const result = await fal.run(model, { input });
156
+ try {
157
+ const result = await fal.run(model, { input });
114
158
 
115
- if (typeof __DEV__ !== "undefined" && __DEV__) {
116
- console.log("[FalProvider] run() raw result:", JSON.stringify(result, null, 2));
117
- console.log("[FalProvider] run() result type:", typeof result);
118
- console.log("[FalProvider] run() result keys:", result ? Object.keys(result as object) : "null");
119
- }
159
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
160
+ console.log("[FalProvider] run() raw result:", JSON.stringify(result, null, 2));
161
+ console.log("[FalProvider] run() result type:", typeof result);
162
+ console.log("[FalProvider] run() result keys:", result ? Object.keys(result as object) : "null");
163
+ }
120
164
 
121
- validateNSFWContent(result as Record<string, unknown>);
165
+ validateNSFWContent(result as Record<string, unknown>);
122
166
 
123
- options?.onProgress?.({ progress: 100, status: "COMPLETED" as const });
124
- return result as T;
167
+ options?.onProgress?.({ progress: 100, status: "COMPLETED" as const });
168
+ return result as T;
169
+ } catch (error) {
170
+ const userMessage = parseFalError(error);
171
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
172
+ console.error("[FalProvider] run() Error:", userMessage);
173
+ }
174
+ throw new Error(userMessage);
175
+ }
125
176
  }
@@ -7,6 +7,7 @@ import { FalErrorType, type FalErrorCategory } from "../../domain/entities/error
7
7
  const PATTERNS: Record<FalErrorType, string[]> = {
8
8
  [FalErrorType.NETWORK]: ["network", "fetch", "connection", "econnrefused", "enotfound", "etimedout"],
9
9
  [FalErrorType.TIMEOUT]: ["timeout", "timed out"],
10
+ [FalErrorType.IMAGE_TOO_SMALL]: ["image_too_small", "image dimensions are too small", "minimum dimensions"],
10
11
  [FalErrorType.VALIDATION]: ["validation", "invalid", "unprocessable", "422", "bad request", "400"],
11
12
  [FalErrorType.CONTENT_POLICY]: ["content_policy", "content policy", "policy violation", "nsfw", "inappropriate"],
12
13
  [FalErrorType.RATE_LIMIT]: ["rate limit", "too many requests", "429", "quota"],
@@ -10,71 +10,17 @@ import {
10
10
  getFileSize,
11
11
  detectMimeType,
12
12
  } from "@umituz/react-native-design-system/filesystem";
13
- import { ImageTransformService } from "@umituz/react-native-design-system/image";
14
13
 
15
14
  declare const __DEV__: boolean | undefined;
16
15
 
17
- // FAL.ai minimum image dimensions
18
- const MIN_WIDTH = 300;
19
- const MIN_HEIGHT = 300;
20
-
21
- /**
22
- * Ensure image meets minimum dimension requirements
23
- * Upscales small images to 300x300 minimum
24
- */
25
- async function ensureMinimumDimensions(uri: string): Promise<string> {
26
- try {
27
- // Get image dimensions by reading the file
28
- const { Image } = await import("react-native");
29
-
30
- return new Promise((resolve) => {
31
- Image.getSize(
32
- uri,
33
- async (width, height) => {
34
- if (width >= MIN_WIDTH && height >= MIN_HEIGHT) {
35
- resolve(uri);
36
- return;
37
- }
38
-
39
- // Calculate new dimensions (maintain aspect ratio, ensure minimum)
40
- const scale = Math.max(MIN_WIDTH / width, MIN_HEIGHT / height);
41
- const newWidth = Math.ceil(width * scale);
42
- const newHeight = Math.ceil(height * scale);
43
-
44
- if (typeof __DEV__ !== "undefined" && __DEV__) {
45
- console.log("[FalStorage] Upscaling image", {
46
- original: `${width}x${height}`,
47
- upscaled: `${newWidth}x${newHeight}`,
48
- });
49
- }
50
-
51
- const result = await ImageTransformService.resize(uri, newWidth, newHeight);
52
- resolve(result.uri);
53
- },
54
- () => {
55
- // If getSize fails, return original URI
56
- resolve(uri);
57
- }
58
- );
59
- });
60
- } catch {
61
- return uri;
62
- }
63
- }
64
-
65
16
  /**
66
17
  * Upload base64 image to FAL storage
67
18
  * Uses design system's filesystem utilities for React Native compatibility
68
- * Automatically upscales images below 300x300 minimum
69
19
  */
70
20
  export async function uploadToFalStorage(base64: string): Promise<string> {
71
- let tempUri = await base64ToTempFile(base64);
72
- const mimeType = detectMimeType(base64);
73
-
74
- // Ensure minimum dimensions (FAL requires 300x300)
75
- tempUri = await ensureMinimumDimensions(tempUri);
76
-
21
+ const tempUri = await base64ToTempFile(base64);
77
22
  const fileSize = getFileSize(tempUri);
23
+ const mimeType = detectMimeType(base64);
78
24
 
79
25
  if (typeof __DEV__ !== "undefined" && __DEV__) {
80
26
  console.log("[FalStorage] Uploading image", {