@uploadista/react 0.0.13 → 0.0.14

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.
Files changed (25) hide show
  1. package/dist/components/index.d.mts +2 -2
  2. package/dist/components/index.mjs +1 -1
  3. package/dist/hooks/index.d.mts +2 -2
  4. package/dist/hooks/index.mjs +1 -1
  5. package/dist/index.d.mts +3 -3
  6. package/dist/index.mjs +1 -1
  7. package/dist/{upload-zone-CsaXN9yD.mjs → upload-zone-8khVLLYF.mjs} +2 -2
  8. package/dist/{upload-zone-CsaXN9yD.mjs.map → upload-zone-8khVLLYF.mjs.map} +1 -1
  9. package/dist/{uploadista-provider-Cj34PdHg.d.mts → uploadista-provider-CrS6TmpJ.d.mts} +2 -2
  10. package/dist/{uploadista-provider-Cj34PdHg.d.mts.map → uploadista-provider-CrS6TmpJ.d.mts.map} +1 -1
  11. package/dist/{use-upload-metrics-2HKWi6GZ.mjs → use-upload-metrics-BE7UcAaz.mjs} +2 -2
  12. package/dist/{use-upload-metrics-2HKWi6GZ.mjs.map → use-upload-metrics-BE7UcAaz.mjs.map} +1 -1
  13. package/dist/{use-upload-metrics-DESSCpN5.d.mts → use-upload-metrics-IXxUORce.d.mts} +10 -10
  14. package/dist/{use-upload-metrics-DESSCpN5.d.mts.map → use-upload-metrics-IXxUORce.d.mts.map} +1 -1
  15. package/dist/use-upload-xcqz090n.mjs +2 -0
  16. package/dist/use-upload-xcqz090n.mjs.map +1 -0
  17. package/dist/{use-uploadista-client-Dp8_ZO7d.d.mts → use-uploadista-client--ivZPO88.d.mts} +21 -110
  18. package/dist/use-uploadista-client--ivZPO88.d.mts.map +1 -0
  19. package/package.json +7 -7
  20. package/src/hooks/use-flow-upload.ts +84 -342
  21. package/src/hooks/use-multi-upload.ts +2 -6
  22. package/src/hooks/use-upload.ts +78 -239
  23. package/dist/use-upload-CvPqdohl.mjs +0 -2
  24. package/dist/use-upload-CvPqdohl.mjs.map +0 -1
  25. package/dist/use-uploadista-client-Dp8_ZO7d.d.mts.map +0 -1
@@ -2,6 +2,12 @@ import type {
2
2
  FlowUploadOptions,
3
3
  UploadistaEvent,
4
4
  } from "@uploadista/client-browser";
5
+ import {
6
+ FlowManager,
7
+ type FlowUploadState,
8
+ type FlowUploadStatus,
9
+ type InternalFlowUploadOptions,
10
+ } from "@uploadista/client-core";
5
11
  import { EventType, type FlowEvent } from "@uploadista/core/flow";
6
12
  import type { UploadFile } from "@uploadista/core/types";
7
13
  import { UploadEventType } from "@uploadista/core/types";
@@ -26,51 +32,8 @@ function isFlowEvent(event: UploadistaEvent): event is FlowEvent {
26
32
  );
27
33
  }
28
34
 
29
- /**
30
- * Possible states for a flow upload lifecycle.
31
- * Flow uploads progress through: idle → uploading → processing → success/error/aborted
32
- */
33
- export type FlowUploadStatus =
34
- | "idle"
35
- | "uploading"
36
- | "processing"
37
- | "success"
38
- | "error"
39
- | "aborted";
40
-
41
- /**
42
- * Complete state information for a flow upload operation.
43
- * Tracks both the upload phase (file transfer) and processing phase (flow execution).
44
- *
45
- * @template TOutput - Type of the final output from the flow (defaults to UploadFile)
46
- *
47
- * @property status - Current upload status (idle, uploading, processing, success, error, aborted)
48
- * @property progress - Upload progress percentage (0-100)
49
- * @property bytesUploaded - Number of bytes successfully uploaded
50
- * @property totalBytes - Total file size in bytes (null if unknown)
51
- * @property error - Error object if upload or processing failed
52
- * @property result - Final output from the flow (available when status is "success")
53
- * @property jobId - Unique identifier for the flow execution job
54
- * @property flowStarted - Whether the flow processing has started
55
- * @property currentNodeName - Name of the currently executing flow node
56
- * @property currentNodeType - Type of the currently executing flow node
57
- * @property flowOutputs - Complete outputs from all output nodes in the flow
58
- */
59
- export interface FlowUploadState<TOutput = UploadFile> {
60
- status: FlowUploadStatus;
61
- progress: number;
62
- bytesUploaded: number;
63
- totalBytes: number | null;
64
- error: Error | null;
65
- result: TOutput | null;
66
- jobId: string | null;
67
- // Flow execution tracking
68
- flowStarted: boolean;
69
- currentNodeName: string | null;
70
- currentNodeType: string | null;
71
- // Full flow outputs (all output nodes)
72
- flowOutputs: Record<string, unknown> | null;
73
- }
35
+ // Re-export types from core for convenience
36
+ export type { FlowUploadState, FlowUploadStatus };
74
37
 
75
38
  /**
76
39
  * Return value from the useFlowUpload hook with upload control methods and state.
@@ -80,6 +43,7 @@ export interface FlowUploadState<TOutput = UploadFile> {
80
43
  * @property state - Complete flow upload state with progress and outputs
81
44
  * @property upload - Function to initiate file upload through the flow
82
45
  * @property abort - Cancel the current upload and flow execution
46
+ * @property pause - Pause the current upload
83
47
  * @property reset - Reset state to idle (clears all data)
84
48
  * @property isUploading - True when upload or processing is active
85
49
  * @property isUploadingFile - True only during file upload phase
@@ -232,202 +196,71 @@ const initialState: FlowUploadState = {
232
196
  export function useFlowUpload<TOutput = UploadFile>(
233
197
  options: FlowUploadOptions<TOutput>,
234
198
  ): UseFlowUploadReturn<TOutput> {
235
- // Get client and event subscription from context
199
+ // Get client from context
236
200
  const client = useUploadistaContext();
237
201
  const [state, setState] = useState<FlowUploadState<TOutput>>(
238
202
  initialState as FlowUploadState<TOutput>,
239
203
  );
240
- const abortRef = useRef<(() => void) | null>(null);
241
- const pauseRef = useRef<(() => void) | null>(null);
242
- const onSuccessRef = useRef(options.onSuccess);
243
- const onErrorRef = useRef(options.onError);
244
- const onFlowCompleteRef = useRef(options.onFlowComplete);
245
- const outputNodeIdRef = useRef(options.flowConfig.outputNodeId);
204
+ const managerRef = useRef<FlowManager<File | Blob, TOutput> | null>(null);
246
205
 
247
- // Update refs when callbacks change
206
+ // Create FlowManager instance once (only recreate if client changes)
207
+ // Note: We don't include options in deps to avoid recreating the manager on every render
208
+ // The manager will use the latest options values through closures
248
209
  useEffect(() => {
249
- onSuccessRef.current = options.onSuccess;
250
- onErrorRef.current = options.onError;
251
- onFlowCompleteRef.current = options.onFlowComplete;
252
- outputNodeIdRef.current = options.flowConfig.outputNodeId;
253
- }, [
254
- options.onSuccess,
255
- options.onError,
256
- options.onFlowComplete,
257
- options.flowConfig.outputNodeId,
258
- ]);
259
-
260
- // Store jobId in ref for event handling
261
- const jobIdRef = useRef<string | null>(null);
262
-
263
- useEffect(() => {
264
- jobIdRef.current = state.jobId;
265
- }, [state.jobId]);
266
-
267
- // Create stable event handler
268
- const handleFlowEvent = useCallback((event: FlowEvent) => {
269
- console.log(
270
- "[useFlowUpload] Received event:",
271
- event,
272
- "Current jobId:",
273
- jobIdRef.current,
274
- );
275
-
276
- // Only handle events for the current job
277
- if (!jobIdRef.current || event.jobId !== jobIdRef.current) {
278
- console.log("[useFlowUpload] Ignoring event - jobId mismatch");
279
- return;
280
- }
281
-
282
- console.log("[useFlowUpload] Processing event type:", event.eventType);
283
-
284
- switch (event.eventType) {
285
- case EventType.FlowStart:
286
- console.log("[useFlowUpload] Flow started");
287
- setState((prev) => ({
288
- ...prev,
289
- flowStarted: true,
290
- status: "processing",
291
- }));
292
- break;
293
-
294
- case EventType.NodeStart:
295
- console.log("[useFlowUpload] Node started:", event.nodeName);
296
- setState((prev) => ({
297
- ...prev,
298
- status: "processing",
299
- currentNodeName: event.nodeName,
300
- currentNodeType: event.nodeType,
301
- }));
302
- break;
303
-
304
- case EventType.NodePause:
305
- console.log(
306
- "[useFlowUpload] Node paused (waiting for upload):",
307
- event.nodeName,
308
- );
309
- // When input node pauses, it's waiting for upload - switch to uploading state
310
- setState((prev) => ({
311
- ...prev,
312
- status: "uploading",
313
- currentNodeName: event.nodeName,
314
- // NodePause doesn't have nodeType, keep previous value
315
- }));
316
- break;
317
-
318
- case EventType.NodeResume:
319
- console.log(
320
- "[useFlowUpload] Node resumed (upload complete):",
321
- event.nodeName,
322
- );
323
- // When node resumes, upload is complete - switch to processing state
324
- setState((prev) => ({
325
- ...prev,
326
- status: "processing",
327
- currentNodeName: event.nodeName,
328
- currentNodeType: event.nodeType,
329
- }));
330
- break;
331
-
332
- case EventType.NodeEnd:
333
- console.log("[useFlowUpload] Node ended:", event.nodeName);
334
- setState((prev) => ({
335
- ...prev,
336
- status: prev.status === "uploading" ? "processing" : prev.status,
337
- currentNodeName: null,
338
- currentNodeType: null,
339
- }));
340
- break;
341
-
342
- case EventType.FlowEnd:
343
- console.log("[useFlowUpload] Flow ended, processing outputs");
344
- setState((prev) => {
345
- // Get flow outputs from the event result
346
- const flowOutputs = (event.result as Record<string, unknown>) || null;
347
-
348
- console.log("[useFlowUpload] Flow outputs:", flowOutputs);
349
-
350
- // Call onFlowComplete with full outputs
351
- if (flowOutputs && onFlowCompleteRef.current) {
352
- console.log(
353
- "[useFlowUpload] Calling onFlowComplete with outputs:",
354
- flowOutputs,
355
- );
356
- onFlowCompleteRef.current(flowOutputs);
357
- }
358
-
359
- // Extract single output for onSuccess callback
360
- let extractedOutput: TOutput | null = null;
361
- if (flowOutputs) {
362
- if (
363
- outputNodeIdRef.current &&
364
- outputNodeIdRef.current in flowOutputs
365
- ) {
366
- // Use specified output node
367
- extractedOutput = flowOutputs[outputNodeIdRef.current] as TOutput;
368
- console.log(
369
- "[useFlowUpload] Extracted output from specified node:",
370
- outputNodeIdRef.current,
371
- );
372
- } else {
373
- // Use first output node
374
- const firstOutputValue = Object.values(flowOutputs)[0];
375
- extractedOutput = firstOutputValue as TOutput;
376
- console.log("[useFlowUpload] Extracted output from first node");
377
- }
378
- }
379
-
380
- // Call onSuccess with extracted output
381
- if (extractedOutput && onSuccessRef.current) {
382
- console.log(
383
- "[useFlowUpload] Calling onSuccess with result:",
384
- extractedOutput,
385
- );
386
- onSuccessRef.current(extractedOutput);
387
- } else if (!extractedOutput && onSuccessRef.current) {
388
- console.warn("[useFlowUpload] No result available for onSuccess");
389
- }
390
-
391
- return {
392
- ...prev,
393
- status: "success",
394
- currentNodeName: null,
395
- currentNodeType: null,
396
- result: extractedOutput,
397
- flowOutputs,
398
- };
210
+ managerRef.current = new FlowManager(
211
+ async (
212
+ file: File | Blob,
213
+ flowConfig: {
214
+ flowId: string;
215
+ storageId: string;
216
+ outputNodeId?: string;
217
+ metadata?: Record<string, string>;
218
+ },
219
+ internalOptions: InternalFlowUploadOptions,
220
+ ) => {
221
+ const result = await client.client.uploadWithFlow(file, flowConfig, {
222
+ onJobStart: internalOptions.onJobStart,
223
+ onProgress: internalOptions.onProgress,
224
+ onChunkComplete: internalOptions.onChunkComplete,
225
+ onSuccess: internalOptions.onSuccess,
226
+ onError: internalOptions.onError,
227
+ onShouldRetry: internalOptions.onShouldRetry,
399
228
  });
400
- break;
401
-
402
- case EventType.FlowError:
403
- console.log("[useFlowUpload] Flow error:", event.error);
404
- setState((prev) => ({
405
- ...prev,
406
- status: "error",
407
- error: new Error(event.error),
408
- }));
409
- onErrorRef.current?.(new Error(event.error));
410
- break;
229
+ // Return only abort and pause (ignore jobId and return value)
230
+ return {
231
+ abort: async () => {
232
+ await result.abort();
233
+ },
234
+ pause: async () => {
235
+ await result.pause();
236
+ // Ignore the FlowJob return value
237
+ },
238
+ };
239
+ },
240
+ {
241
+ onStateChange: setState,
242
+ onProgress: options.onProgress,
243
+ onChunkComplete: options.onChunkComplete,
244
+ onFlowComplete: options.onFlowComplete,
245
+ onSuccess: options.onSuccess,
246
+ onError: options.onError,
247
+ onAbort: options.onAbort,
248
+ },
249
+ options,
250
+ );
411
251
 
412
- case EventType.NodeError:
413
- console.log("[useFlowUpload] Node error:", event.error);
414
- setState((prev) => ({
415
- ...prev,
416
- status: "error",
417
- error: new Error(event.error),
418
- }));
419
- onErrorRef.current?.(new Error(event.error));
420
- break;
421
- }
422
- }, []);
252
+ return () => {
253
+ managerRef.current?.cleanup();
254
+ };
255
+ // eslint-disable-next-line react-hooks/exhaustive-deps
256
+ }, [client]);
423
257
 
424
- // Automatically subscribe to flow events and upload events from context
258
+ // Subscribe to events and forward them to the manager
425
259
  useEffect(() => {
426
- console.log("[useFlowUpload] Subscribing to events from context");
427
260
  const unsubscribe = client.subscribeToEvents((event: UploadistaEvent) => {
428
261
  // Handle flow events
429
262
  if (isFlowEvent(event)) {
430
- handleFlowEvent(event);
263
+ managerRef.current?.handleFlowEvent(event);
431
264
  return;
432
265
  }
433
266
 
@@ -437,147 +270,56 @@ export function useFlowUpload<TOutput = UploadFile>(
437
270
  data?: { id: string; progress: number; total: number };
438
271
  flow?: { jobId: string };
439
272
  };
273
+
440
274
  if (
441
275
  uploadEvent.type === UploadEventType.UPLOAD_PROGRESS &&
442
- uploadEvent.flow?.jobId === jobIdRef.current &&
276
+ uploadEvent.flow?.jobId === managerRef.current?.getJobId() &&
443
277
  uploadEvent.data
444
278
  ) {
445
279
  const { progress: bytesUploaded, total: totalBytes } = uploadEvent.data;
446
- const progress = totalBytes
447
- ? Math.round((bytesUploaded / totalBytes) * 100)
448
- : 0;
449
280
 
450
- console.log("[useFlowUpload] Upload progress event:", {
451
- progress,
281
+ managerRef.current?.handleUploadProgress(
282
+ uploadEvent.data.id,
452
283
  bytesUploaded,
453
284
  totalBytes,
454
- jobId: uploadEvent.flow.jobId,
455
- });
456
-
457
- setState((prev) => ({
458
- ...prev,
459
- progress,
460
- bytesUploaded,
461
- totalBytes,
462
- }));
285
+ );
463
286
  }
464
287
  });
465
288
 
466
289
  return unsubscribe;
467
- }, [client, handleFlowEvent]);
468
-
469
- const upload = useCallback(
470
- async (file: File | Blob) => {
471
- jobIdRef.current = null;
472
-
473
- setState({
474
- ...initialState,
475
- status: "uploading",
476
- totalBytes: file.size,
477
- } as FlowUploadState<TOutput>);
290
+ }, [client]);
478
291
 
479
- try {
480
- const { abort, pause } = await client.client.uploadWithFlow(
481
- file,
482
- options.flowConfig,
483
- {
484
- onJobStart: (jobId: string) => {
485
- jobIdRef.current = jobId;
486
- setState((prev) => ({ ...prev, jobId }));
487
- },
488
- onProgress: (
489
- _uploadId: string,
490
- bytesUploaded: number,
491
- totalBytes: number | null,
492
- ) => {
493
- const progress = totalBytes
494
- ? Math.round((bytesUploaded / totalBytes) * 100)
495
- : 0;
496
-
497
- setState((prev) => ({
498
- ...prev,
499
- progress,
500
- bytesUploaded,
501
- totalBytes,
502
- }));
503
-
504
- options.onProgress?.(progress, bytesUploaded, totalBytes);
505
- },
506
- onChunkComplete: options.onChunkComplete,
507
- onSuccess: (_result: UploadFile) => {
508
- // Upload phase is complete, now waiting for flow execution
509
- // Note: we don't store the upload result as our final result
510
- // The final result will come from the FlowEnd event
511
- // Status transition from "uploading" to "processing" is handled by NodeResume event
512
- setState((prev) => ({
513
- ...prev,
514
- progress: 100,
515
- }));
516
- // Don't call onSuccess here - wait for FlowEnd event
517
- },
518
- onError: (error: Error) => {
519
- setState((prev) => ({
520
- ...prev,
521
- status: "error",
522
- error,
523
- }));
524
-
525
- options.onError?.(error);
526
- },
527
- onShouldRetry: options.onShouldRetry,
528
- },
529
- );
530
-
531
- abortRef.current = abort;
532
- pauseRef.current = pause;
533
- } catch (error) {
534
- setState((prev) => ({
535
- ...prev,
536
- status: "error",
537
- error: error as Error,
538
- }));
539
-
540
- options.onError?.(error as Error);
541
- }
542
- },
543
- [client, options],
544
- );
292
+ // Wrap manager methods with useCallback
293
+ const upload = useCallback(async (file: File | Blob) => {
294
+ await managerRef.current?.upload(file);
295
+ }, []);
545
296
 
546
297
  const abort = useCallback(() => {
547
- if (abortRef.current) {
548
- abortRef.current();
549
- abortRef.current = null;
550
-
551
- setState((prev) => ({
552
- ...prev,
553
- status: "aborted",
554
- }));
555
-
556
- options.onAbort?.();
557
- }
558
- }, [options]);
298
+ managerRef.current?.abort();
299
+ }, []);
559
300
 
560
301
  const pause = useCallback(() => {
561
- if (pauseRef.current) {
562
- pauseRef.current();
563
- pauseRef.current = null;
564
- }
302
+ managerRef.current?.pause();
565
303
  }, []);
566
304
 
567
305
  const reset = useCallback(() => {
568
- setState(initialState as FlowUploadState<TOutput>);
569
- abortRef.current = null;
570
- jobIdRef.current = null;
306
+ managerRef.current?.reset();
571
307
  }, []);
572
308
 
309
+ // Derive computed values from state (reactive to state changes)
310
+ const isUploading =
311
+ state.status === "uploading" || state.status === "processing";
312
+ const isUploadingFile = state.status === "uploading";
313
+ const isProcessing = state.status === "processing";
314
+
573
315
  return {
574
316
  state,
575
317
  upload,
576
318
  abort,
577
319
  pause,
578
320
  reset,
579
- isUploading: state.status === "uploading" || state.status === "processing",
580
- isUploadingFile: state.status === "uploading",
581
- isProcessing: state.status === "processing",
321
+ isUploading,
322
+ isUploadingFile,
323
+ isProcessing,
582
324
  };
583
325
  }
@@ -1,13 +1,9 @@
1
1
  import type { BrowserUploadInput } from "@uploadista/client-browser";
2
+ import type { UploadMetrics } from "@uploadista/client-core";
2
3
  import type { UploadFile } from "@uploadista/core/types";
3
4
  import { useCallback, useRef, useState } from "react";
4
5
  import { useUploadistaContext } from "../components/uploadista-provider";
5
- import type {
6
- UploadMetrics,
7
- UploadState,
8
- UploadStatus,
9
- UseUploadOptions,
10
- } from "./use-upload";
6
+ import type { UploadState, UploadStatus, UseUploadOptions } from "./use-upload";
11
7
 
12
8
  export interface UploadItem {
13
9
  id: string;