@umituz/react-native-ai-fal-provider 1.0.93 → 1.0.94
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.0.
|
|
3
|
+
"version": "1.0.94",
|
|
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",
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FAL Provider - Implements IAIProvider interface
|
|
3
|
-
* Uses Promise Deduplication Pattern
|
|
3
|
+
* Uses Promise Deduplication Pattern with globalThis for hot reload persistence
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { fal } from "@fal-ai/client";
|
|
7
7
|
import type {
|
|
8
|
-
IAIProvider,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
JobStatus,
|
|
12
|
-
SubscribeOptions,
|
|
13
|
-
RunOptions,
|
|
14
|
-
ImageFeatureType,
|
|
15
|
-
VideoFeatureType,
|
|
16
|
-
ImageFeatureInputData,
|
|
17
|
-
VideoFeatureInputData,
|
|
18
|
-
ProviderCapabilities,
|
|
8
|
+
IAIProvider, AIProviderConfig, JobSubmission, JobStatus, SubscribeOptions,
|
|
9
|
+
RunOptions, ImageFeatureType, VideoFeatureType, ImageFeatureInputData,
|
|
10
|
+
VideoFeatureInputData, ProviderCapabilities,
|
|
19
11
|
} from "@umituz/react-native-ai-generation-content";
|
|
20
12
|
import type { FalQueueStatus } from "../../domain/entities/fal.types";
|
|
21
13
|
import type { CostTrackerConfig } from "../../domain/entities/cost-tracking.types";
|
|
@@ -33,14 +25,23 @@ declare const __DEV__: boolean | undefined;
|
|
|
33
25
|
interface ActiveRequest<T = unknown> {
|
|
34
26
|
promise: Promise<T>;
|
|
35
27
|
abortController: AbortController;
|
|
36
|
-
model: string;
|
|
37
28
|
}
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
// Global request store - survives hot reloads via globalThis
|
|
31
|
+
const STORE_KEY = "__FAL_PROVIDER_REQUESTS__";
|
|
32
|
+
type RequestStore = Map<string, ActiveRequest>;
|
|
33
|
+
|
|
34
|
+
function getRequestStore(): RequestStore {
|
|
35
|
+
if (!(globalThis as Record<string, unknown>)[STORE_KEY]) {
|
|
36
|
+
(globalThis as Record<string, unknown>)[STORE_KEY] = new Map();
|
|
37
|
+
}
|
|
38
|
+
return (globalThis as Record<string, unknown>)[STORE_KEY] as RequestStore;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createRequestKey(model: string, input: Record<string, unknown>): string {
|
|
42
|
+
const inputStr = JSON.stringify(input, Object.keys(input).sort());
|
|
43
|
+
return `${model}:${inputStr.slice(0, 100)}`; // Limit key length
|
|
44
|
+
}
|
|
44
45
|
|
|
45
46
|
export class FalProvider implements IAIProvider {
|
|
46
47
|
readonly providerId = "fal";
|
|
@@ -56,7 +57,6 @@ export class FalProvider implements IAIProvider {
|
|
|
56
57
|
this.apiKey = config.apiKey;
|
|
57
58
|
this.videoFeatureModels = config.videoFeatureModels ?? {};
|
|
58
59
|
this.imageFeatureModels = config.imageFeatureModels ?? {};
|
|
59
|
-
|
|
60
60
|
fal.config({
|
|
61
61
|
credentials: config.apiKey,
|
|
62
62
|
retry: {
|
|
@@ -65,7 +65,6 @@ export class FalProvider implements IAIProvider {
|
|
|
65
65
|
maxDelay: config.maxDelay ?? DEFAULT_FAL_CONFIG.maxDelay,
|
|
66
66
|
},
|
|
67
67
|
});
|
|
68
|
-
|
|
69
68
|
this.initialized = true;
|
|
70
69
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
71
70
|
console.log("[FalProvider] Initialized");
|
|
@@ -98,26 +97,18 @@ export class FalProvider implements IAIProvider {
|
|
|
98
97
|
|
|
99
98
|
isFeatureSupported(feature: ImageFeatureType | VideoFeatureType): boolean {
|
|
100
99
|
const caps = this.getCapabilities();
|
|
101
|
-
return (
|
|
102
|
-
|
|
103
|
-
caps.videoFeatures.includes(feature as VideoFeatureType)
|
|
104
|
-
);
|
|
100
|
+
return caps.imageFeatures.includes(feature as ImageFeatureType) ||
|
|
101
|
+
caps.videoFeatures.includes(feature as VideoFeatureType);
|
|
105
102
|
}
|
|
106
103
|
|
|
107
104
|
private validateInit(): void {
|
|
108
|
-
if (!this.apiKey || !this.initialized)
|
|
109
|
-
throw new Error("FAL provider not initialized");
|
|
110
|
-
}
|
|
105
|
+
if (!this.apiKey || !this.initialized) throw new Error("FAL provider not initialized");
|
|
111
106
|
}
|
|
112
107
|
|
|
113
108
|
async submitJob(model: string, input: Record<string, unknown>): Promise<JobSubmission> {
|
|
114
109
|
this.validateInit();
|
|
115
110
|
const result = await fal.queue.submit(model, { input });
|
|
116
|
-
return {
|
|
117
|
-
requestId: result.request_id,
|
|
118
|
-
statusUrl: result.status_url,
|
|
119
|
-
responseUrl: result.response_url,
|
|
120
|
-
};
|
|
111
|
+
return { requestId: result.request_id, statusUrl: result.status_url, responseUrl: result.response_url };
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
async getJobStatus(model: string, requestId: string): Promise<JobStatus> {
|
|
@@ -132,22 +123,22 @@ export class FalProvider implements IAIProvider {
|
|
|
132
123
|
return result.data as T;
|
|
133
124
|
}
|
|
134
125
|
|
|
135
|
-
/**
|
|
136
|
-
* Promise Deduplication: If request in progress, return existing promise
|
|
137
|
-
* Prevents duplicate API calls from React re-renders
|
|
138
|
-
*/
|
|
139
126
|
async subscribe<T = unknown>(
|
|
140
127
|
model: string,
|
|
141
128
|
input: Record<string, unknown>,
|
|
142
129
|
options?: SubscribeOptions<T>,
|
|
143
130
|
): Promise<T> {
|
|
144
131
|
this.validateInit();
|
|
132
|
+
const store = getRequestStore();
|
|
133
|
+
const key = createRequestKey(model, input);
|
|
145
134
|
|
|
146
|
-
if
|
|
135
|
+
// Return existing promise if same request is in progress
|
|
136
|
+
const existing = store.get(key);
|
|
137
|
+
if (existing) {
|
|
147
138
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
148
|
-
console.log(`[FalProvider]
|
|
139
|
+
console.log(`[FalProvider] Dedup: returning existing promise for ${model}`);
|
|
149
140
|
}
|
|
150
|
-
return
|
|
141
|
+
return existing.promise as Promise<T>;
|
|
151
142
|
}
|
|
152
143
|
|
|
153
144
|
const abortController = new AbortController();
|
|
@@ -159,19 +150,14 @@ export class FalProvider implements IAIProvider {
|
|
|
159
150
|
this.costTracker.completeOperation(operationId, model, "subscribe", requestId ?? undefined);
|
|
160
151
|
}
|
|
161
152
|
return result;
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
activeRequest = { promise, abortController, model };
|
|
165
|
-
promise.finally(() => { activeRequest = null; });
|
|
153
|
+
})
|
|
154
|
+
.finally(() => store.delete(key));
|
|
166
155
|
|
|
156
|
+
store.set(key, { promise, abortController });
|
|
167
157
|
return promise;
|
|
168
158
|
}
|
|
169
159
|
|
|
170
|
-
async run<T = unknown>(
|
|
171
|
-
model: string,
|
|
172
|
-
input: Record<string, unknown>,
|
|
173
|
-
options?: RunOptions,
|
|
174
|
-
): Promise<T> {
|
|
160
|
+
async run<T = unknown>(model: string, input: Record<string, unknown>, options?: RunOptions): Promise<T> {
|
|
175
161
|
this.validateInit();
|
|
176
162
|
const operationId = this.costTracker?.startOperation(model, "run");
|
|
177
163
|
const result = await handleFalRun<T>(model, input, options);
|
|
@@ -188,17 +174,18 @@ export class FalProvider implements IAIProvider {
|
|
|
188
174
|
}
|
|
189
175
|
|
|
190
176
|
cancelCurrentRequest(): void {
|
|
191
|
-
|
|
177
|
+
const store = getRequestStore();
|
|
178
|
+
store.forEach((req, key) => {
|
|
192
179
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
193
|
-
console.log(
|
|
180
|
+
console.log(`[FalProvider] Cancelling request: ${key}`);
|
|
194
181
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
182
|
+
req.abortController.abort();
|
|
183
|
+
});
|
|
184
|
+
store.clear();
|
|
198
185
|
}
|
|
199
186
|
|
|
200
187
|
hasRunningRequest(): boolean {
|
|
201
|
-
return
|
|
188
|
+
return getRequestStore().size > 0;
|
|
202
189
|
}
|
|
203
190
|
|
|
204
191
|
getImageFeatureModel(feature: ImageFeatureType): string {
|