@umituz/react-native-ai-fal-provider 3.2.3 → 3.2.4

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": "3.2.3",
3
+ "version": "3.2.4",
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",
@@ -112,8 +112,9 @@ export async function handleFalSubscription<T = unknown>(
112
112
  Array.isArray(update.logs) ? update.logs : undefined,
113
113
  );
114
114
 
115
- if (jobStatus.status !== lastStatus) {
116
- lastStatus = jobStatus.status;
115
+ const statusWithPosition = `${jobStatus.status}:${jobStatus.queuePosition ?? ""}`;
116
+ if (statusWithPosition !== lastStatus) {
117
+ lastStatus = statusWithPosition;
117
118
  if (options?.onProgress) {
118
119
  if (jobStatus.status === "IN_QUEUE" || jobStatus.status === "IN_PROGRESS") {
119
120
  options.onProgress({ progress: -1, status: jobStatus.status });
@@ -137,7 +138,7 @@ export async function handleFalSubscription<T = unknown>(
137
138
  if (signal) {
138
139
  const abortPromise = new Promise<never>((_, reject) => {
139
140
  abortHandler = () => reject(new Error("Request cancelled by user"));
140
- signal.addEventListener("abort", abortHandler);
141
+ signal.addEventListener("abort", abortHandler, { once: true });
141
142
  listenerAdded = true;
142
143
  // Re-check after listener to handle race
143
144
  if (signal.aborted) {
@@ -13,7 +13,7 @@ import { handleFalSubscription, handleFalRun } from "./fal-provider-subscription
13
13
  import { preprocessInput } from "../utils";
14
14
  import {
15
15
  createRequestKey, getExistingRequest, storeRequest,
16
- removeRequest, cancelAllRequests, hasActiveRequests,
16
+ removeRequest, cancelRequest, cancelAllRequests, hasActiveRequests,
17
17
  } from "./request-store";
18
18
  import * as queueOps from "./fal-queue-operations";
19
19
  import { validateInput } from "../utils/input-validator.util";
@@ -24,6 +24,7 @@ export class FalProvider implements IAIProvider {
24
24
 
25
25
  private apiKey: string | null = null;
26
26
  private initialized = false;
27
+ private lastRequestKey: string | null = null;
27
28
 
28
29
  initialize(config: AIProviderConfig): void {
29
30
  this.apiKey = config.apiKey;
@@ -110,6 +111,7 @@ export class FalProvider implements IAIProvider {
110
111
  rejectPromise = reject;
111
112
  });
112
113
 
114
+ this.lastRequestKey = key;
113
115
  storeRequest(key, { promise, abortController, createdAt: Date.now() });
114
116
 
115
117
  handleFalSubscription<T>(model, processedInput, options, abortController.signal)
@@ -143,13 +145,18 @@ export class FalProvider implements IAIProvider {
143
145
  }
144
146
 
145
147
  reset(): void {
146
- this.cancelCurrentRequest();
148
+ cancelAllRequests();
149
+ this.lastRequestKey = null;
147
150
  this.apiKey = null;
148
151
  this.initialized = false;
149
152
  }
150
153
 
151
154
  cancelCurrentRequest(): void {
152
- cancelAllRequests();
155
+ // Cancel only this provider instance's last request, not all global requests
156
+ if (this.lastRequestKey) {
157
+ cancelRequest(this.lastRequestKey);
158
+ this.lastRequestKey = null;
159
+ }
153
160
  }
154
161
 
155
162
  hasRunningRequest(): boolean {
@@ -37,11 +37,24 @@ export function getRequestStore(): RequestStore {
37
37
  return globalObj[STORE_KEY] as RequestStore;
38
38
  }
39
39
 
40
+ /**
41
+ * Recursively sort object keys for deterministic JSON output
42
+ */
43
+ function sortKeys(obj: unknown): unknown {
44
+ if (obj === null || typeof obj !== "object") return obj;
45
+ if (Array.isArray(obj)) return obj.map(sortKeys);
46
+ const sorted: Record<string, unknown> = {};
47
+ for (const key of Object.keys(obj as Record<string, unknown>).sort()) {
48
+ sorted[key] = sortKeys((obj as Record<string, unknown>)[key]);
49
+ }
50
+ return sorted;
51
+ }
52
+
40
53
  /**
41
54
  * Create a deterministic request key using model and input hash
42
55
  */
43
56
  export function createRequestKey(model: string, input: Record<string, unknown>): string {
44
- const inputStr = JSON.stringify(input, Object.keys(input).sort());
57
+ const inputStr = JSON.stringify(sortKeys(input));
45
58
  let hash = 0;
46
59
  for (let i = 0; i < inputStr.length; i++) {
47
60
  const char = inputStr.charCodeAt(i);
@@ -73,6 +86,19 @@ export function removeRequest(key: string): void {
73
86
  // This prevents the race where a new request arrives right after we stop
74
87
  }
75
88
 
89
+ /**
90
+ * Cancel a single request by key
91
+ */
92
+ export function cancelRequest(key: string): void {
93
+ const store = getRequestStore();
94
+ const req = store.get(key);
95
+ if (req) {
96
+ req.abortController.abort();
97
+ store.delete(key);
98
+ if (store.size === 0) stopCleanupTimer();
99
+ }
100
+ }
101
+
76
102
  export function cancelAllRequests(): void {
77
103
  const store = getRequestStore();
78
104
  store.forEach((req) => {
@@ -79,8 +79,8 @@ export class FalGenerationStateManager<T> {
79
79
  queuePosition: status.queuePosition,
80
80
  };
81
81
 
82
- // Only notify if status actually changed (idempotent callbacks)
83
- const statusKey = `${normalizedStatus.status}-${normalizedStatus.requestId}`;
82
+ // Only notify if status or queue position changed (idempotent callbacks)
83
+ const statusKey = `${normalizedStatus.status}-${normalizedStatus.requestId}-${normalizedStatus.queuePosition ?? ""}`;
84
84
  if (this.lastNotifiedStatus !== statusKey) {
85
85
  this.lastNotifiedStatus = statusKey;
86
86
  this.options?.onQueueUpdate?.(normalizedStatus);