import type { UploadStatusType, ChunkStatusType } from './constants'; import type { QueueManager } from './engine/queue-manager'; export interface UploadUrl { uploadUrl: string; partNum: number; } export interface ChunkState { partNum?: number; status: ChunkStatusType; progress: number; uploadUrl?: string; error?: string; } export interface UploadFile { id: string; file: File; fileName: string; fileSize: number; lastModified: number; status: UploadStatusType; progress: number; uploadId: string; chunks: ChunkState[]; chunkSize: number; relativePath: string; runtime?: unknown; error?: string; errorCode?: string; retryCount?: number; createdAt: number; } export type GetUploadUrlsResult = { success: false; reason?: string; } | { success: true; uploadInfo: { uploadUrls: UploadUrl[]; uploadId: string; }; }; export type CallbackResult = { success: true; } | { success: false; reason?: string; }; /** * Context object passed to handler callbacks with fresh utilities. * This solves the stale closure problem by providing always-current references. * * @example * ```tsx * addFiles(files, { * getHandlers: () => ({ * onUploadSuccess: (file, ctx) => { * // Use ctx instead of closed-over values - always fresh! * console.log('Current files:', ctx.files.length); * ctx.removeFile(file.id); * } * }) * }); * ``` */ export interface HandlerContext { /** Current files in the upload queue (always fresh) */ files: UploadFile[]; /** Remove a file from the queue (always fresh) */ removeFile: (id: string) => Promise; /** Start uploading a pending file (always fresh) */ start: (id: string) => Promise; /** Pause an uploading file (always fresh) */ pause: (id: string) => void; /** Resume a paused file (always fresh) */ resume: (id: string) => Promise; /** Cancel a file upload (always fresh) */ cancel: (id: string) => void; /** Get a specific file by ID (always fresh) */ getFileById: (id: string) => UploadFile | undefined; } /** * Upload lifecycle handler callbacks * These hooks allow consumers to integrate with the upload process at various stages. * * All callbacks that receive a file also receive a `ctx` (HandlerContext) parameter * with fresh references to upload utilities, solving the stale closure problem. */ export interface UploadHandlers { /** Called before requesting upload URLs. Return { success: false } to cancel upload */ onBeforeUpload?: (file: UploadFile, ctx: HandlerContext) => Promise; /** Request upload URLs from server (required) */ onGetUploadUrls: (fileName: string, partCount: number) => Promise; /** Called after URLs received, before chunk uploads begin */ onUploadStart?: (file: UploadFile, ctx: HandlerContext) => void; /** Called when file progress updates (0-100) */ onProgress?: (file: UploadFile, progress: number, ctx: HandlerContext) => void; /** Called when a chunk upload completes (required) */ onPartComplete: (fileName: string, uploadId: string, partNum: number) => Promise; /** Called when upload is paused */ onPause?: (file: UploadFile, ctx: HandlerContext) => void; /** Called when upload is resumed */ onResume?: (file: UploadFile, ctx: HandlerContext) => void; /** Called when entire file upload completes (required) */ onFileComplete: (fileName: string, uploadId: string) => Promise; /** Called when upload is successful */ onUploadSuccess?: (file: UploadFile, ctx: HandlerContext) => void; /** Called when upload is cancelled */ onUploadCancel?: (fileName: string, uploadId: string) => Promise; /** Called when file is deleted */ onDeleteFile?: (file: UploadFile, ctx: HandlerContext) => Promise; /** Called when upload is retried (smart or full retry) */ onRetry?: (file: UploadFile, retryType: 'smart' | 'full', ctx: HandlerContext) => void; /** Called when an error occurs during upload */ onError?: (file: UploadFile, error: Error, ctx: HandlerContext) => void; } export interface UploadConfig { /** Maximum chunk size in bytes */ maxChunkSize?: number; /** Minimum chunk size in bytes */ minChunkSize?: number; /** Number of concurrent chunk uploads per file */ concurrentChunks?: number; /** Number of concurrent file uploads */ concurrentFiles?: number; } type AddFilesHandlerOption = { /** * Static handlers applied to all files in a single addFiles call */ handlers: UploadHandlers; getHandlers?: never; } | { /** * Factory to derive handlers per file (runtime context like folder selection) */ handlers?: never; getHandlers: (file: File) => UploadHandlers; }; export type AddFilesOptions = AddFilesHandlerOption; export interface UploadState { files: UploadFile[]; } export type UploadAction = { type: 'ADD_FILES'; files: UploadFile[]; } | { type: 'UPDATE_FILE'; id: string; updates: Partial; } | { type: 'UPDATE_CHUNK'; fileId: string; partNum: number; updates: Partial; } | { type: 'REMOVE_FILE'; id: string; }; export interface UploadContextValue { /** Upload state (files list, processing status) */ state: UploadState; /** Dispatch function for state updates */ dispatch: React.Dispatch; /** Global upload configuration (chunk sizes, concurrency) */ config: Required; /** Shared QueueManager ref - singleton across all useUploadEngine consumers */ queueManagerRef: React.MutableRefObject; /** Get or create the QueueManager singleton instance */ getQueueManager: () => QueueManager; } export {};