@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.93",
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 to prevent duplicate requests
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
- AIProviderConfig,
10
- JobSubmission,
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
- * Module-level state for Promise Deduplication
41
- * Persists across hot reloads (module cache is preserved)
42
- */
43
- let activeRequest: ActiveRequest | null = null;
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
- caps.imageFeatures.includes(feature as ImageFeatureType) ||
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 (activeRequest) {
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] Returning existing promise for ${activeRequest.model}`);
139
+ console.log(`[FalProvider] Dedup: returning existing promise for ${model}`);
149
140
  }
150
- return activeRequest.promise as Promise<T>;
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
- if (activeRequest) {
177
+ const store = getRequestStore();
178
+ store.forEach((req, key) => {
192
179
  if (typeof __DEV__ !== "undefined" && __DEV__) {
193
- console.log("[FalProvider] Cancelling current request");
180
+ console.log(`[FalProvider] Cancelling request: ${key}`);
194
181
  }
195
- activeRequest.abortController.abort();
196
- activeRequest = null;
197
- }
182
+ req.abortController.abort();
183
+ });
184
+ store.clear();
198
185
  }
199
186
 
200
187
  hasRunningRequest(): boolean {
201
- return activeRequest !== null;
188
+ return getRequestStore().size > 0;
202
189
  }
203
190
 
204
191
  getImageFeatureModel(feature: ImageFeatureType): string {