@umituz/react-native-ai-fal-provider 2.1.3 → 2.1.5
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/constants/default-models.constants.ts +13 -4
- package/src/domain/types/model-selection.types.ts +9 -5
- package/src/exports/infrastructure.ts +2 -9
- package/src/infrastructure/services/fal-models.service.ts +8 -7
- package/src/infrastructure/services/fal-provider.ts +9 -8
- package/src/infrastructure/services/fal-queue-operations.ts +8 -1
- package/src/infrastructure/services/nsfw-content-error.ts +1 -1
- package/src/infrastructure/services/request-store.ts +78 -22
- package/src/infrastructure/utils/collections/array-filters.util.ts +12 -1
- package/src/infrastructure/utils/collections/array-sorters.util.ts +34 -0
- package/src/infrastructure/utils/collections/index.ts +0 -1
- package/src/infrastructure/utils/cost-tracker.ts +3 -1
- package/src/infrastructure/utils/cost-tracking-executor.util.ts +14 -3
- package/src/infrastructure/utils/date-format.util.ts +13 -47
- package/src/infrastructure/utils/fal-storage.util.ts +40 -1
- package/src/infrastructure/utils/formatting.util.ts +3 -21
- package/src/infrastructure/utils/helpers/index.ts +0 -1
- package/src/infrastructure/utils/helpers/timing-helpers.util.ts +1 -79
- package/src/infrastructure/utils/image-helpers.util.ts +9 -5
- package/src/infrastructure/utils/index.ts +11 -50
- package/src/infrastructure/utils/input-preprocessor.util.ts +53 -6
- package/src/infrastructure/utils/input-validator.util.ts +71 -28
- package/src/infrastructure/utils/job-metadata/job-metadata-format.util.ts +26 -1
- package/src/infrastructure/utils/number-format.util.ts +27 -20
- package/src/infrastructure/utils/parsers/index.ts +0 -1
- package/src/infrastructure/utils/parsers/json-parsers.util.ts +19 -3
- package/src/infrastructure/utils/string-format.util.ts +0 -59
- package/src/infrastructure/utils/type-guards/validation-guards.util.ts +12 -0
- package/src/presentation/hooks/use-models.ts +12 -4
- package/src/infrastructure/utils/collections/array-reducers.util.ts +0 -67
- package/src/infrastructure/utils/helpers/function-helpers.util.ts +0 -25
- package/src/infrastructure/utils/parsers/number-helpers.util.ts +0 -19
|
@@ -12,7 +12,12 @@ export function safeJsonParse<T = unknown>(
|
|
|
12
12
|
): T {
|
|
13
13
|
try {
|
|
14
14
|
return JSON.parse(data) as T;
|
|
15
|
-
} catch {
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.warn(
|
|
17
|
+
'[json-parsers] Failed to parse JSON, using fallback:',
|
|
18
|
+
error instanceof Error ? error.message : String(error),
|
|
19
|
+
{ dataPreview: data.substring(0, 100) }
|
|
20
|
+
);
|
|
16
21
|
return fallback;
|
|
17
22
|
}
|
|
18
23
|
}
|
|
@@ -23,7 +28,12 @@ export function safeJsonParse<T = unknown>(
|
|
|
23
28
|
export function safeJsonParseOrNull<T = unknown>(data: string): T | null {
|
|
24
29
|
try {
|
|
25
30
|
return JSON.parse(data) as T;
|
|
26
|
-
} catch {
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.warn(
|
|
33
|
+
'[json-parsers] Failed to parse JSON, returning null:',
|
|
34
|
+
error instanceof Error ? error.message : String(error),
|
|
35
|
+
{ dataPreview: data.substring(0, 100) }
|
|
36
|
+
);
|
|
27
37
|
return null;
|
|
28
38
|
}
|
|
29
39
|
}
|
|
@@ -37,7 +47,12 @@ export function safeJsonStringify(
|
|
|
37
47
|
): string {
|
|
38
48
|
try {
|
|
39
49
|
return JSON.stringify(data);
|
|
40
|
-
} catch {
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.warn(
|
|
52
|
+
'[json-parsers] Failed to stringify object, using fallback:',
|
|
53
|
+
error instanceof Error ? error.message : String(error),
|
|
54
|
+
{ dataType: typeof data }
|
|
55
|
+
);
|
|
41
56
|
return fallback;
|
|
42
57
|
}
|
|
43
58
|
}
|
|
@@ -50,6 +65,7 @@ export function isValidJson(data: string): boolean {
|
|
|
50
65
|
JSON.parse(data);
|
|
51
66
|
return true;
|
|
52
67
|
} catch {
|
|
68
|
+
// Don't log here - this is expected to fail for validation checks
|
|
53
69
|
return false;
|
|
54
70
|
}
|
|
55
71
|
}
|
|
@@ -12,62 +12,3 @@ export function truncateText(text: string, maxLength: number): string {
|
|
|
12
12
|
}
|
|
13
13
|
return text.slice(0, maxLength - 3) + "...";
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Capitalize first letter of string
|
|
18
|
-
*/
|
|
19
|
-
export function capitalize(text: string): string {
|
|
20
|
-
if (!text) return text;
|
|
21
|
-
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Convert string to title case
|
|
26
|
-
*/
|
|
27
|
-
export function toTitleCase(text: string): string {
|
|
28
|
-
return text
|
|
29
|
-
.toLowerCase()
|
|
30
|
-
.split(" ")
|
|
31
|
-
.map((word) => capitalize(word))
|
|
32
|
-
.join(" ");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Convert string to slug
|
|
37
|
-
*/
|
|
38
|
-
export function toSlug(text: string): string {
|
|
39
|
-
return text
|
|
40
|
-
.toLowerCase()
|
|
41
|
-
.trim()
|
|
42
|
-
.replace(/[^\w\s-]/g, "")
|
|
43
|
-
.replace(/[\s_-]+/g, "-")
|
|
44
|
-
.replace(/^-+|-+$/g, "");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Format list of items with conjunction
|
|
49
|
-
*/
|
|
50
|
-
export function formatList(items: readonly string[], conjunction: string = "and"): string {
|
|
51
|
-
if (items.length === 0) return "";
|
|
52
|
-
if (items.length === 1) return items[0] ?? "";
|
|
53
|
-
if (items.length === 2) return items.join(` ${conjunction} `);
|
|
54
|
-
|
|
55
|
-
const allButLast = items.slice(0, -1);
|
|
56
|
-
const last = items[items.length - 1];
|
|
57
|
-
return `${allButLast.join(", ")}, ${conjunction} ${last}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Pluralize word based on count
|
|
62
|
-
*/
|
|
63
|
-
export function pluralize(word: string, count: number): string {
|
|
64
|
-
if (count === 1) return word;
|
|
65
|
-
return `${word}s`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Format count with plural word
|
|
70
|
-
*/
|
|
71
|
-
export function formatCount(word: string, count: number): string {
|
|
72
|
-
return `${count} ${pluralize(word, count)}`;
|
|
73
|
-
}
|
|
@@ -40,6 +40,8 @@ export function isValidApiKey(value: unknown): boolean {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Validate model ID format
|
|
43
|
+
* Pattern: org/model or org/model/version
|
|
44
|
+
* Allows dots for versions (e.g., v1.5) but prevents path traversal (..)
|
|
43
45
|
*/
|
|
44
46
|
const MODEL_ID_PATTERN = /^[a-zA-Z0-9-_]+\/[a-zA-Z0-9-_.]+(\/[a-zA-Z0-9-_.]+)?$/;
|
|
45
47
|
|
|
@@ -48,6 +50,16 @@ export function isValidModelId(value: unknown): boolean {
|
|
|
48
50
|
return false;
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
// Prevent path traversal attacks
|
|
54
|
+
if (value.includes('..')) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Ensure it doesn't start or end with dots
|
|
59
|
+
if (value.startsWith('.') || value.endsWith('.')) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
return MODEL_ID_PATTERN.test(value) && value.length >= MIN_MODEL_ID_LENGTH;
|
|
52
64
|
}
|
|
53
65
|
|
|
@@ -35,13 +35,21 @@ export function useModels(props: UseModelsProps): UseModelsReturn {
|
|
|
35
35
|
const [isLoading, setIsLoading] = useState(true);
|
|
36
36
|
const [error, setError] = useState<string | null>(null);
|
|
37
37
|
|
|
38
|
+
// Memoize config to prevent unnecessary re-renders when parent re-renders
|
|
39
|
+
// Only recreate when actual config values change
|
|
40
|
+
const memoizedConfig = useMemo(() => config, [
|
|
41
|
+
config?.initialModelId,
|
|
42
|
+
config?.defaultCreditCost,
|
|
43
|
+
config?.defaultModelId,
|
|
44
|
+
]);
|
|
45
|
+
|
|
38
46
|
// Direct effect - no intermediate callback needed
|
|
39
47
|
useEffect(() => {
|
|
40
48
|
setIsLoading(true);
|
|
41
49
|
setError(null);
|
|
42
50
|
|
|
43
51
|
try {
|
|
44
|
-
const selectionData = falModelsService.getModelSelectionData(type,
|
|
52
|
+
const selectionData = falModelsService.getModelSelectionData(type, memoizedConfig);
|
|
45
53
|
setModels(selectionData.models);
|
|
46
54
|
setSelectedModel(selectionData.selectedModel);
|
|
47
55
|
} catch (err) {
|
|
@@ -49,7 +57,7 @@ export function useModels(props: UseModelsProps): UseModelsReturn {
|
|
|
49
57
|
} finally {
|
|
50
58
|
setIsLoading(false);
|
|
51
59
|
}
|
|
52
|
-
}, [type,
|
|
60
|
+
}, [type, memoizedConfig]);
|
|
53
61
|
|
|
54
62
|
// Separate refresh callback for manual reloads
|
|
55
63
|
const loadModels = useCallback(() => {
|
|
@@ -57,7 +65,7 @@ export function useModels(props: UseModelsProps): UseModelsReturn {
|
|
|
57
65
|
setError(null);
|
|
58
66
|
|
|
59
67
|
try {
|
|
60
|
-
const selectionData = falModelsService.getModelSelectionData(type,
|
|
68
|
+
const selectionData = falModelsService.getModelSelectionData(type, memoizedConfig);
|
|
61
69
|
setModels(selectionData.models);
|
|
62
70
|
setSelectedModel(selectionData.selectedModel);
|
|
63
71
|
} catch (err) {
|
|
@@ -65,7 +73,7 @@ export function useModels(props: UseModelsProps): UseModelsReturn {
|
|
|
65
73
|
} finally {
|
|
66
74
|
setIsLoading(false);
|
|
67
75
|
}
|
|
68
|
-
}, [type,
|
|
76
|
+
}, [type, memoizedConfig]);
|
|
69
77
|
|
|
70
78
|
const selectModel = useCallback(
|
|
71
79
|
(modelId: string) => {
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Array Reducer Utilities
|
|
3
|
-
* Reduce, group, chunk, and aggregation operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Reduce array to sum of number property
|
|
8
|
-
*/
|
|
9
|
-
export function sumByProperty<T>(
|
|
10
|
-
items: readonly T[],
|
|
11
|
-
numberProperty: keyof T
|
|
12
|
-
): number {
|
|
13
|
-
return items.reduce((sum, item) => {
|
|
14
|
-
const value = item[numberProperty] as unknown as number;
|
|
15
|
-
return sum + (typeof value === "number" ? value : 0);
|
|
16
|
-
}, 0);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Group array by property value
|
|
21
|
-
*/
|
|
22
|
-
export function groupByProperty<T>(
|
|
23
|
-
items: readonly T[],
|
|
24
|
-
property: keyof T
|
|
25
|
-
): Map<unknown, T[]> {
|
|
26
|
-
const groups = new Map<unknown, T[]>();
|
|
27
|
-
for (const item of items) {
|
|
28
|
-
const key = item[property];
|
|
29
|
-
const existing = groups.get(key);
|
|
30
|
-
if (existing) {
|
|
31
|
-
existing.push(item);
|
|
32
|
-
} else {
|
|
33
|
-
groups.set(key, [item]);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return groups;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Chunk array into smaller arrays of specified size
|
|
41
|
-
*/
|
|
42
|
-
export function chunkArray<T>(items: readonly T[], chunkSize: number): T[][] {
|
|
43
|
-
const result: T[][] = [];
|
|
44
|
-
for (let i = 0; i < items.length; i += chunkSize) {
|
|
45
|
-
result.push([...items.slice(i, i + chunkSize)]);
|
|
46
|
-
}
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Get distinct values of a property from array
|
|
52
|
-
*/
|
|
53
|
-
export function distinctByProperty<T>(
|
|
54
|
-
items: readonly T[],
|
|
55
|
-
property: keyof T
|
|
56
|
-
): unknown[] {
|
|
57
|
-
const seen = new Set<unknown>();
|
|
58
|
-
const result: unknown[] = [];
|
|
59
|
-
for (const item of items) {
|
|
60
|
-
const value = item[property];
|
|
61
|
-
if (!seen.has(value)) {
|
|
62
|
-
seen.add(value);
|
|
63
|
-
result.push(value);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Number Helper Utilities
|
|
3
|
-
* Number manipulation and formatting
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Clamp number between min and max
|
|
8
|
-
*/
|
|
9
|
-
export function clampNumber(value: number, min: number, max: number): number {
|
|
10
|
-
return Math.min(Math.max(value, min), max);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Round to decimal places
|
|
15
|
-
*/
|
|
16
|
-
export function roundToDecimals(value: number, decimals: number): number {
|
|
17
|
-
const multiplier = Math.pow(10, decimals);
|
|
18
|
-
return Math.round(value * multiplier) / multiplier;
|
|
19
|
-
}
|