@umituz/react-native-ai-fal-provider 1.0.94 → 1.0.96

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.94",
3
+ "version": "1.0.96",
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",
@@ -0,0 +1,46 @@
1
+ /**
2
+ * FAL Feature Models - Model resolution and input building
3
+ */
4
+
5
+ import type {
6
+ ImageFeatureType,
7
+ VideoFeatureType,
8
+ ImageFeatureInputData,
9
+ VideoFeatureInputData,
10
+ } from "@umituz/react-native-ai-generation-content";
11
+ import {
12
+ buildImageFeatureInput as buildImageFeatureInputImpl,
13
+ buildVideoFeatureInput as buildVideoFeatureInputImpl,
14
+ } from "../builders";
15
+
16
+ export function getImageFeatureModel(
17
+ imageFeatureModels: Record<string, string>,
18
+ feature: ImageFeatureType,
19
+ ): string {
20
+ const model = imageFeatureModels[feature];
21
+ if (!model) throw new Error(`No model for image feature: ${feature}`);
22
+ return model;
23
+ }
24
+
25
+ export function getVideoFeatureModel(
26
+ videoFeatureModels: Record<string, string>,
27
+ feature: VideoFeatureType,
28
+ ): string {
29
+ const model = videoFeatureModels[feature];
30
+ if (!model) throw new Error(`No model for video feature: ${feature}`);
31
+ return model;
32
+ }
33
+
34
+ export function buildImageFeatureInput(
35
+ feature: ImageFeatureType,
36
+ data: ImageFeatureInputData,
37
+ ): Record<string, unknown> {
38
+ return buildImageFeatureInputImpl(feature, data);
39
+ }
40
+
41
+ export function buildVideoFeatureInput(
42
+ feature: VideoFeatureType,
43
+ data: VideoFeatureInputData,
44
+ ): Record<string, unknown> {
45
+ return buildVideoFeatureInputImpl(feature, data);
46
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * FAL Provider - Implements IAIProvider interface
3
- * Uses Promise Deduplication Pattern with globalThis for hot reload persistence
3
+ * Orchestrates FAL AI operations with Promise Deduplication
4
4
  */
5
5
 
6
6
  import { fal } from "@fal-ai/client";
@@ -9,40 +9,28 @@ import type {
9
9
  RunOptions, ImageFeatureType, VideoFeatureType, ImageFeatureInputData,
10
10
  VideoFeatureInputData, ProviderCapabilities,
11
11
  } from "@umituz/react-native-ai-generation-content";
12
- import type { FalQueueStatus } from "../../domain/entities/fal.types";
13
12
  import type { CostTrackerConfig } from "../../domain/entities/cost-tracking.types";
14
13
  import { DEFAULT_FAL_CONFIG, FAL_CAPABILITIES } from "./fal-provider.constants";
15
- import { mapFalStatusToJobStatus } from "./fal-status-mapper";
14
+ import { handleFalSubscription, handleFalRun } from "./fal-provider-subscription";
15
+ import { CostTracker } from "../utils/cost-tracker";
16
+ import {
17
+ createRequestKey, getExistingRequest, storeRequest,
18
+ removeRequest, cancelAllRequests, hasActiveRequests,
19
+ } from "./request-store";
16
20
  import {
21
+ submitJob as submitJobImpl,
22
+ getJobStatus as getJobStatusImpl,
23
+ getJobResult as getJobResultImpl,
24
+ } from "./fal-queue-operations";
25
+ import {
26
+ getImageFeatureModel as getImageFeatureModelImpl,
27
+ getVideoFeatureModel as getVideoFeatureModelImpl,
17
28
  buildImageFeatureInput as buildImageFeatureInputImpl,
18
29
  buildVideoFeatureInput as buildVideoFeatureInputImpl,
19
- } from "../builders";
20
- import { handleFalSubscription, handleFalRun } from "./fal-provider-subscription";
21
- import { CostTracker } from "../utils/cost-tracker";
30
+ } from "./fal-feature-models";
22
31
 
23
32
  declare const __DEV__: boolean | undefined;
24
33
 
25
- interface ActiveRequest<T = unknown> {
26
- promise: Promise<T>;
27
- abortController: AbortController;
28
- }
29
-
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
- }
45
-
46
34
  export class FalProvider implements IAIProvider {
47
35
  readonly providerId = "fal";
48
36
  readonly providerName = "FAL AI";
@@ -107,20 +95,17 @@ export class FalProvider implements IAIProvider {
107
95
 
108
96
  async submitJob(model: string, input: Record<string, unknown>): Promise<JobSubmission> {
109
97
  this.validateInit();
110
- const result = await fal.queue.submit(model, { input });
111
- return { requestId: result.request_id, statusUrl: result.status_url, responseUrl: result.response_url };
98
+ return submitJobImpl(model, input);
112
99
  }
113
100
 
114
101
  async getJobStatus(model: string, requestId: string): Promise<JobStatus> {
115
102
  this.validateInit();
116
- const status = await fal.queue.status(model, { requestId, logs: true });
117
- return mapFalStatusToJobStatus(status as unknown as FalQueueStatus);
103
+ return getJobStatusImpl(model, requestId);
118
104
  }
119
105
 
120
106
  async getJobResult<T = unknown>(model: string, requestId: string): Promise<T> {
121
107
  this.validateInit();
122
- const result = await fal.queue.result(model, { requestId });
123
- return result.data as T;
108
+ return getJobResultImpl<T>(model, requestId);
124
109
  }
125
110
 
126
111
  async subscribe<T = unknown>(
@@ -129,16 +114,14 @@ export class FalProvider implements IAIProvider {
129
114
  options?: SubscribeOptions<T>,
130
115
  ): Promise<T> {
131
116
  this.validateInit();
132
- const store = getRequestStore();
133
117
  const key = createRequestKey(model, input);
134
118
 
135
- // Return existing promise if same request is in progress
136
- const existing = store.get(key);
119
+ const existing = getExistingRequest<T>(key);
137
120
  if (existing) {
138
121
  if (typeof __DEV__ !== "undefined" && __DEV__) {
139
122
  console.log(`[FalProvider] Dedup: returning existing promise for ${model}`);
140
123
  }
141
- return existing.promise as Promise<T>;
124
+ return existing.promise;
142
125
  }
143
126
 
144
127
  const abortController = new AbortController();
@@ -151,9 +134,9 @@ export class FalProvider implements IAIProvider {
151
134
  }
152
135
  return result;
153
136
  })
154
- .finally(() => store.delete(key));
137
+ .finally(() => removeRequest(key));
155
138
 
156
- store.set(key, { promise, abortController });
139
+ storeRequest(key, { promise, abortController });
157
140
  return promise;
158
141
  }
159
142
 
@@ -174,24 +157,15 @@ export class FalProvider implements IAIProvider {
174
157
  }
175
158
 
176
159
  cancelCurrentRequest(): void {
177
- const store = getRequestStore();
178
- store.forEach((req, key) => {
179
- if (typeof __DEV__ !== "undefined" && __DEV__) {
180
- console.log(`[FalProvider] Cancelling request: ${key}`);
181
- }
182
- req.abortController.abort();
183
- });
184
- store.clear();
160
+ cancelAllRequests();
185
161
  }
186
162
 
187
163
  hasRunningRequest(): boolean {
188
- return getRequestStore().size > 0;
164
+ return hasActiveRequests();
189
165
  }
190
166
 
191
167
  getImageFeatureModel(feature: ImageFeatureType): string {
192
- const model = this.imageFeatureModels[feature];
193
- if (!model) throw new Error(`No model for image feature: ${feature}`);
194
- return model;
168
+ return getImageFeatureModelImpl(this.imageFeatureModels, feature);
195
169
  }
196
170
 
197
171
  buildImageFeatureInput(feature: ImageFeatureType, data: ImageFeatureInputData): Record<string, unknown> {
@@ -199,9 +173,7 @@ export class FalProvider implements IAIProvider {
199
173
  }
200
174
 
201
175
  getVideoFeatureModel(feature: VideoFeatureType): string {
202
- const model = this.videoFeatureModels[feature];
203
- if (!model) throw new Error(`No model for video feature: ${feature}`);
204
- return model;
176
+ return getVideoFeatureModelImpl(this.videoFeatureModels, feature);
205
177
  }
206
178
 
207
179
  buildVideoFeatureInput(feature: VideoFeatureType, data: VideoFeatureInputData): Record<string, unknown> {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * FAL Queue Operations - Direct FAL API queue interactions
3
+ */
4
+
5
+ import { fal } from "@fal-ai/client";
6
+ import type { JobSubmission, JobStatus } from "@umituz/react-native-ai-generation-content";
7
+ import type { FalQueueStatus } from "../../domain/entities/fal.types";
8
+ import { mapFalStatusToJobStatus } from "./fal-status-mapper";
9
+
10
+ export async function submitJob(model: string, input: Record<string, unknown>): Promise<JobSubmission> {
11
+ const result = await fal.queue.submit(model, { input });
12
+ return {
13
+ requestId: result.request_id,
14
+ statusUrl: result.status_url,
15
+ responseUrl: result.response_url,
16
+ };
17
+ }
18
+
19
+ export async function getJobStatus(model: string, requestId: string): Promise<JobStatus> {
20
+ const status = await fal.queue.status(model, { requestId, logs: true });
21
+ return mapFalStatusToJobStatus(status as unknown as FalQueueStatus);
22
+ }
23
+
24
+ export async function getJobResult<T = unknown>(model: string, requestId: string): Promise<T> {
25
+ const result = await fal.queue.result(model, { requestId });
26
+ return result.data as T;
27
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Request Store - Promise Deduplication with globalThis
3
+ * Survives hot reloads for React Native development
4
+ */
5
+
6
+ declare const __DEV__: boolean | undefined;
7
+
8
+ export interface ActiveRequest<T = unknown> {
9
+ promise: Promise<T>;
10
+ abortController: AbortController;
11
+ }
12
+
13
+ const STORE_KEY = "__FAL_PROVIDER_REQUESTS__";
14
+ type RequestStore = Map<string, ActiveRequest>;
15
+
16
+ export function getRequestStore(): RequestStore {
17
+ if (!(globalThis as Record<string, unknown>)[STORE_KEY]) {
18
+ (globalThis as Record<string, unknown>)[STORE_KEY] = new Map();
19
+ }
20
+ return (globalThis as Record<string, unknown>)[STORE_KEY] as RequestStore;
21
+ }
22
+
23
+ export function createRequestKey(model: string, input: Record<string, unknown>): string {
24
+ const inputStr = JSON.stringify(input, Object.keys(input).sort());
25
+ return `${model}:${inputStr.slice(0, 100)}`;
26
+ }
27
+
28
+ export function getExistingRequest<T>(key: string): ActiveRequest<T> | undefined {
29
+ return getRequestStore().get(key) as ActiveRequest<T> | undefined;
30
+ }
31
+
32
+ export function storeRequest<T>(key: string, request: ActiveRequest<T>): void {
33
+ getRequestStore().set(key, request);
34
+ }
35
+
36
+ export function removeRequest(key: string): void {
37
+ getRequestStore().delete(key);
38
+ }
39
+
40
+ export function cancelAllRequests(): void {
41
+ const store = getRequestStore();
42
+ store.forEach((req, key) => {
43
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
44
+ console.log(`[RequestStore] Cancelling: ${key}`);
45
+ }
46
+ req.abortController.abort();
47
+ });
48
+ store.clear();
49
+ }
50
+
51
+ export function hasActiveRequests(): boolean {
52
+ return getRequestStore().size > 0;
53
+ }