@better-s3/react 1.0.0 → 2.0.0

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/README.md CHANGED
@@ -1,13 +1,21 @@
1
1
  # @better-s3/react
2
2
 
3
- Headless React hooks for S3 file uploads, downloads, and deletes — full state management, progress tracking, and cancellation. Bring your own UI.
3
+ Headless React hooks for S3 file uploads, downloads, and deletes — state management, progress tracking, and cancellation built in.
4
4
 
5
5
  > **Want pre-built components?** Use [`@better-s3/ui`](../better-s3-ui) for ready-to-use shadcn/ui components.
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- pnpm add @better-s3/react @better-s3/core
10
+ pnpm add @better-s3/react @better-s3/server
11
+ ```
12
+
13
+ ## Setup
14
+
15
+ ```ts
16
+ import { createPresignApi } from "@better-s3/server";
17
+
18
+ const presignApi = createPresignApi("/api/s3");
11
19
  ```
12
20
 
13
21
  ## Hooks
@@ -15,22 +23,14 @@ pnpm add @better-s3/react @better-s3/core
15
23
  ### `useUpload` — Single file upload
16
24
 
17
25
  ```tsx
18
- import { useUpload } from "@better-s3/react";
19
- import { createPresignApi } from "@better-s3/core";
20
-
21
- const presignApi = createPresignApi({ basePath: "/api/s3" });
22
-
23
- function MyComponent() {
24
- const { phase, progress, error, upload, cancel, reset } = useUpload({
25
- presignApi,
26
- objectKey: (file) => `uploads/${file.name}`,
27
- accept: ["image/*"],
28
- maxFileSize: 10 * 1024 * 1024, // 10 MB
29
- onSuccess: (result) => console.log("Uploaded:", result.url),
30
- });
26
+ const { phase, progress, error, upload, cancel, reset } = useUpload({
27
+ presignApi,
28
+ accept: ["image/*"],
29
+ maxFileSize: 10 * 1024 * 1024,
30
+ onSuccess: (_file, result) => console.log("Uploaded:", result.key),
31
+ });
31
32
 
32
- return <button onClick={() => upload(file)}>Upload</button>;
33
- }
33
+ await upload(file, `uploads/${file.name}`, { metadata: { source: "web" } });
34
34
  ```
35
35
 
36
36
  **Phases:** `idle → validating → presigning → uploading → finalizing → success | error`
@@ -38,84 +38,50 @@ function MyComponent() {
38
38
  ### `useUploadControls` — Upload with file picker & drag-drop
39
39
 
40
40
  ```tsx
41
- import { useUploadControls } from "@better-s3/react";
42
-
43
- function Uploader() {
44
- const { phase, progress, openFilePicker, inputProps, dropHandlers } =
45
- useUploadControls({
46
- presignApi,
47
- objectKey: (file) => `uploads/${file.name}`,
48
- });
49
-
50
- return (
51
- <div {...dropHandlers}>
52
- <input {...inputProps} />
53
- <button onClick={openFilePicker}>
54
- {phase === "uploading" ? `${progress}%` : "Choose file"}
55
- </button>
56
- </div>
57
- );
58
- }
41
+ const { phase, progress, openFilePicker, inputProps, dropHandlers } =
42
+ useUploadControls({
43
+ presignApi,
44
+ objectKey: (file) => `uploads/${file.name}`,
45
+ });
59
46
  ```
60
47
 
61
- ### `useMultiUpload` — Batch file upload
48
+ ### `useMultiUpload` — Batch upload
62
49
 
63
50
  ```tsx
64
- import { useMultiUpload } from "@better-s3/react";
65
-
66
51
  const { phase, files, totalProgress, upload, cancel } = useMultiUpload({
67
52
  presignApi,
68
- objectKey: (file) => `uploads/${file.name}`,
69
53
  maxFiles: 10,
70
54
  concurrentFiles: 3,
71
55
  });
72
56
 
73
- // files: Array<{ id, name, size, status, progress, error }>
57
+ await upload(selectedFiles, (file) => `uploads/${file.name}`);
74
58
  ```
75
59
 
76
60
  ### `useMultiUploadControls` — Batch upload with file picker
77
61
 
78
- Same convenience layer as `useUploadControls`, but for multiple files.
62
+ Same as `useUploadControls` but for multiple files.
79
63
 
80
64
  ### `useDownload` — File download
81
65
 
82
66
  ```tsx
83
- import { useDownload } from "@better-s3/react";
84
-
85
67
  const { phase, progress, download, cancel } = useDownload({
86
68
  presignApi,
87
- mode: "fetch", // "native" for browser download, "fetch" for streaming
69
+ mode: "fetch", // or "native"
88
70
  });
89
71
 
90
- // Trigger download
91
- download({ key: "uploads/photo.jpg", fileName: "photo.jpg" });
72
+ download("uploads/photo.jpg", "photo.jpg");
92
73
  ```
93
74
 
94
- **Modes:**
95
-
96
- - `native` — opens presigned URL in new window (browser handles download)
97
- - `fetch` — streams file with progress tracking
98
-
99
75
  ### `useDelete` — File deletion with confirmation
100
76
 
101
77
  ```tsx
102
- import { useDelete } from "@better-s3/react";
103
-
104
78
  const { phase, pendingKey, requestDelete, confirmDelete, cancelDelete } =
105
79
  useDelete({ presignApi });
106
80
 
107
- // Two-step flow:
108
81
  requestDelete("uploads/photo.jpg"); // phase → "confirming"
109
- confirmDelete(); // phase → "deleting" → "success"
82
+ confirmDelete(); // phase → "deleting" → "success"
110
83
  ```
111
84
 
112
- ## All hooks return
113
-
114
- - `phase` — current state of the operation
115
- - `error` — error message if failed
116
- - `reset()` — return to idle state
117
- - `cancel()` — abort in-flight operation
118
-
119
85
  ## License
120
86
 
121
87
  MIT
@@ -0,0 +1 @@
1
+ export declare function formatFileSize(bytes: number): string;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ export * from "./types";
2
+ export { formatFileSize } from "./helpers";
3
+ export { uploadFile, uploadFiles, type UploadEngineCallbacks, type FileItem, type FileItemStatus, type MultiUploadCallbacks, } from "./upload";
4
+ export { createPresignApi, validateFile, type PresignApi, type PresignResponse, type MultipartInitResponse, type MultipartPartResponse, } from "@better-s3/server";
1
5
  export { useUpload, type UseUploadOptions, type UseUploadState, type UseUploadReturn, } from "./use-upload";
2
6
  export { useMultiUpload, type UseMultiUploadOptions, type UseMultiUploadState, type UseMultiUploadReturn, } from "./use-multi-upload";
3
7
  export { useUploadControls, type UseUploadControlsOptions, type UseUploadControlsReturn, } from "./use-upload-controls";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import {useState,useRef,useCallback}from'react';import {validateFile,uploadFile,uploadFiles}from'@better-s3/core';var B={loaded:0,total:0,percent:0},R={phase:"idle",progress:B,error:null,result:null,fileName:null,fileSize:null};function x(g){let[h,t]=useState(R),r=useRef(g);r.current=g;let a=useRef(null),m=useCallback(async(o,f)=>{t({...R,phase:"validating",fileName:o.name,fileSize:o.size});let l=r.current,e=validateFile(o,{accept:l.accept,maxFileSize:l.maxFileSize});if(e){t(p=>({...p,phase:"error",error:e})),l.onError?.(o,new Error(e),"validating");return}if(l.beforeUpload&&!await l.beforeUpload(o)){t(u=>({...u,phase:"error",error:"Upload blocked by beforeUpload hook"})),l.onError?.(o,new Error("blocked"),"validating");return}t(p=>({...p,phase:"uploading"})),l.onUploadStart?.(o,f);let U=new AbortController;a.current=U;try{let p=await uploadFile(l.presignApi,o,f,{multipart:l.multipart,multipartThreshold:l.multipartThreshold,concurrentParts:l.concurrentParts},{onProgress:u=>{t(b=>({...b,progress:u})),l.onProgress?.(o,u);}},U.signal);t(u=>({...u,phase:"success",result:p,progress:{loaded:o.size,total:o.size,percent:100}})),await l.onSuccess?.(o,p);}catch(p){if(p.name==="AbortError"){l.onCancel?.(o),t(R);return}let u=p instanceof Error?p.message:"Upload failed";t(b=>({...b,phase:"error",error:u})),l.onError?.(o,p,"uploading");}finally{a.current=null;}},[]),w=useCallback(()=>{a.current?.abort(),t(R);},[]),D=useCallback(()=>{a.current?.abort(),t(R);},[]);return {...h,upload:m,cancel:w,reset:D}}var W={loaded:0,total:0,percent:0},M={phase:"idle",files:[],totalProgress:W,error:null},X=0;function Y(){return `file-${++X}`}function I(g){let[h,t]=useState(M),r=useRef(g);r.current=g;let a=useRef(null),m=useRef(new Map),w=useCallback(async(f,l)=>{let e=r.current,U=[],p=[],u=new Map;if(t(n=>({...n,phase:"validating",error:null})),e.maxFiles&&f.length>e.maxFiles){let n=`Too many files. Maximum is ${e.maxFiles}.`;t(c=>({...c,phase:"error",error:n})),e.onError?.(new Error(n));return}for(let n of f){let c=validateFile(n,{accept:e.accept,maxFileSize:e.maxFileSize});if(c){let S=`${n.name}: ${c}`;t(s=>({...s,phase:"error",error:S})),e.onError?.(new Error(S));return}}if(e.beforeUpload&&!await e.beforeUpload(f)){t(c=>({...c,phase:"error",error:"Upload blocked by beforeUpload hook"})),e.onError?.(new Error("blocked"));return}for(let n of f){let c=Y(),S=l(n);U.push({id:c,file:n,objectKey:S}),u.set(c,n),p.push({id:c,fileName:n.name,fileSize:n.size,status:"pending",progress:{loaded:0,total:n.size,percent:0},error:null});}m.current=u,t({phase:"uploading",files:p,totalProgress:{loaded:0,total:f.reduce((n,c)=>n+c.size,0),percent:0},error:null}),e.onUploadStart?.(f);let b=new AbortController;a.current=b;try{let n=await uploadFiles(e.presignApi,U,{multipart:e.multipart,multipartThreshold:e.multipartThreshold,concurrentParts:e.concurrentParts,concurrentFiles:e.concurrentFiles},{onFileProgress:(s,d)=>{t(P=>({...P,files:P.files.map(i=>i.id===s?{...i,status:"uploading",progress:d}:i)}));let y=u.get(s);y&&e.onFileProgress?.(y,d);},onFileSuccess:(s,d)=>{t(P=>({...P,files:P.files.map(i=>i.id===s?{...i,status:"success",progress:{loaded:i.fileSize,total:i.fileSize,percent:100}}:i)}));let y=u.get(s);y&&e.onFileSuccess?.(y,d);},onFileError:(s,d)=>{t(P=>({...P,files:P.files.map(i=>i.id===s?{...i,status:"error",error:d}:i)}));let y=u.get(s);y&&e.onFileError?.(y,d);},onTotalProgress:s=>{t(d=>({...d,totalProgress:s})),e.onProgress?.(s);}},b.signal),c=n.some(s=>s.status==="error"),S=n.filter(s=>s.result!==null).map(s=>s.result);t(s=>({...s,phase:c?"error":"success",error:c?`${n.filter(d=>d.status==="error").length} file(s) failed`:null,totalProgress:c?s.totalProgress:{loaded:s.totalProgress.total,total:s.totalProgress.total,percent:100}})),c||await e.onSuccess?.(S);}catch(n){if(n.name==="AbortError"){e.onCancel?.(),t(M);return}let c=n instanceof Error?n.message:"Upload failed";t(S=>({...S,phase:"error",error:c})),e.onError?.(n);}finally{a.current=null;}},[]),D=useCallback(()=>{a.current?.abort(),t(M);},[]),o=useCallback(()=>{a.current?.abort(),t(M);},[]);return {...h,upload:w,cancel:D,reset:o}}function te(g){let{objectKey:h,...t}=g,r=x(t),a=useRef(null),[m,w]=useState(null),D=e=>typeof h=="function"?h(e):h,o=async e=>{let U=e?.[0];U&&(w({name:U.name,size:U.size}),await r.upload(U,D(U)));},f=()=>a.current?.click(),l=r.phase==="uploading";return {phase:r.phase,progress:r.progress,error:r.error,fileInfo:m,isUploading:l,handleFiles:o,openFilePicker:f,cancel:r.cancel,reset:()=>{r.reset(),w(null);},inputProps:{ref:a,type:"file",accept:t.accept?.join(","),hidden:true,onChange:e=>{o(e.target.files),e.target.value="";}},dropHandlers:{onDragOver:e=>{e.preventDefault(),e.stopPropagation();},onDrop:e=>{e.preventDefault(),e.stopPropagation(),l||o(e.dataTransfer.files);}}}}function re(g){let{objectKey:h,...t}=g,r=I(t),a=useRef(null),m=async o=>{o?.length&&await r.upload(Array.from(o),h);},w=()=>a.current?.click(),D=r.phase==="uploading";return {phase:r.phase,files:r.files,totalProgress:r.totalProgress,error:r.error,isUploading:D,handleFiles:m,openFilePicker:w,cancel:r.cancel,reset:r.reset,inputProps:{ref:a,type:"file",multiple:true,accept:t.accept?.join(","),hidden:true,onChange:o=>{m(o.target.files),o.target.value="";}},dropHandlers:{onDragOver:o=>{o.preventDefault(),o.stopPropagation();},onDrop:o=>{o.preventDefault(),o.stopPropagation(),D||m(o.dataTransfer.files);}}}}var N={loaded:0,total:0,percent:0},v={phase:"idle",progress:N,error:null,fileName:null,fileSize:null};function ne(g){let[h,t]=useState(v),r=useRef(g);r.current=g;let a=useRef(null),m=useCallback(async(o,f)=>{let l=f??o.split("/").pop()??o,e=r.current,U=e.mode??"native";if(e.beforeDownload&&!await e.beforeDownload(o)){t(u=>({...u,phase:"error",error:"Download blocked by beforeDownload hook"})),e.onError?.(o,new Error("blocked"),"presigning");return}t({phase:"presigning",progress:N,error:null,fileName:l,fileSize:null});try{let{url:p}=await e.presignApi.download(o,l);if(U==="native"){let i=document.createElement("a");i.href=p,i.download=l,i.click(),t(E=>({...E,phase:"success"})),await e.onSuccess?.(o),t(v);return}t(i=>({...i,phase:"downloading"})),e.onDownloadStart?.(o);let u=new AbortController;a.current=u;let b=await fetch(p,{signal:u.signal});if(!b.ok)throw new Error(`Download failed: ${b.statusText}`);let n=Number(b.headers.get("content-length")||0);t(i=>({...i,fileSize:n||null}));let c=b.body?.getReader();if(!c)throw new Error("ReadableStream not supported");let S=[],s=0;for(;;){let{done:i,value:E}=await c.read();if(i)break;S.push(E),s+=E.byteLength;let K=n>0?Math.round(s/n*100):0,z={loaded:s,total:n,percent:K};t(_=>({..._,progress:z})),e.onProgress?.(o,z);}let d=new Blob(S),y=URL.createObjectURL(d),P=document.createElement("a");P.href=y,P.download=l,P.click(),URL.revokeObjectURL(y),t(i=>({...i,phase:"success",fileSize:d.size,progress:{loaded:d.size,total:d.size,percent:100}})),await e.onSuccess?.(o);}catch(p){if(p.name==="AbortError"){e.onCancel?.(o),t(v);return}let u=p instanceof Error?p.message:"Download failed";t(b=>({...b,phase:"error",error:u})),e.onError?.(o,p,"downloading");}finally{a.current=null;}},[]),w=useCallback(()=>{a.current?.abort(),t(v);},[]),D=useCallback(()=>{a.current?.abort(),t(v);},[]);return {...h,download:m,cancel:w,reset:D}}var k={phase:"idle",error:null};function ae(g){let[h,t]=useState(k),[r,a]=useState(null),m=useRef(g);m.current=g;let w=useCallback(l=>{a(l),t({phase:"confirming",error:null});},[]),D=useCallback(async()=>{if(!r)return;let l=m.current;if(l.beforeDelete&&!await l.beforeDelete(r)){t({phase:"error",error:"Delete blocked by beforeDelete hook"}),l.onError?.(r,new Error("blocked"),"confirming"),a(null);return}t({phase:"deleting",error:null}),l.onDeleteStart?.(r);try{await l.presignApi.delete(r),t({phase:"success",error:null}),await l.onSuccess?.(r),a(null);}catch(e){let U=e instanceof Error?e.message:"Delete failed";t({phase:"error",error:U}),l.onError?.(r,e,"deleting");}},[r]),o=useCallback(()=>{a(null),t(k);},[]),f=useCallback(()=>{a(null),t(k);},[]);return {...h,pendingKey:r,requestDelete:w,confirmDelete:D,cancelDelete:o,reset:f}}export{ae as useDelete,ne as useDownload,I as useMultiUpload,re as useMultiUploadControls,x as useUpload,te as useUploadControls};//# sourceMappingURL=index.js.map
1
+ import {validateFile}from'@better-s3/server';export{createPresignApi,validateFile}from'@better-s3/server';import {useState,useRef,useCallback}from'react';function Z(i){if(i===0)return "0 B";let p=["B","KB","MB","GB","TB"],e=Math.floor(Math.log(i)/Math.log(1024));return `${(i/Math.pow(1024,e)).toFixed(e===0?0:1)} ${p[e]}`}async function M(i,p,e){let r;for(let s=0;s<=p;s++)try{return await i()}catch(u){if(u.name==="AbortError")throw u;if(r=u,s<p){let l=1e3*2**s;if(await new Promise(c=>setTimeout(c,l)),e?.aborted)throw new DOMException("Upload aborted","AbortError")}}throw r}function K(i,p,e,r){return new Promise((s,u)=>{let l=new XMLHttpRequest,c=()=>{l.abort(),u(new DOMException("Upload aborted","AbortError"));};r?.addEventListener("abort",c,{once:true}),l.upload.addEventListener("progress",t=>{t.lengthComputable&&e?.({loaded:t.loaded,total:t.total,percent:Math.round(t.loaded/t.total*100)});}),l.addEventListener("load",()=>{if(r?.removeEventListener("abort",c),l.status>=200&&l.status<300){e?.({loaded:i.size,total:i.size,percent:100});let t=l.getResponseHeader("ETag")?.replace(/"/g,"");s(t??void 0);}else u(new Error(`Upload failed: ${l.status} ${l.statusText}`));}),l.addEventListener("error",()=>{r?.removeEventListener("abort",c),u(new Error("Upload failed: network error"));}),l.addEventListener("abort",()=>{r?.removeEventListener("abort",c),u(new DOMException("Upload aborted","AbortError"));}),l.open("PUT",p),l.setRequestHeader("Content-Type",i.type||"application/octet-stream"),l.send(i);})}function q(i,p,e,r,s,u){return new Promise((l,c)=>{let t=new XMLHttpRequest,b=()=>{t.abort(),c(new DOMException("Upload aborted","AbortError"));};u?.addEventListener("abort",b,{once:true}),t.upload.addEventListener("progress",U=>{U.lengthComputable&&(e.bytes=U.loaded,s());}),t.addEventListener("load",()=>{if(u?.removeEventListener("abort",b),t.status>=200&&t.status<300){e.bytes=i.size,s();let U=t.getResponseHeader("ETag")??"";l(U);}else c(new Error(`Part upload failed: ${t.status}`));}),t.addEventListener("error",()=>{u?.removeEventListener("abort",b),c(new Error("Part upload failed: network error"));}),t.addEventListener("abort",()=>{u?.removeEventListener("abort",b),c(new DOMException("Upload aborted","AbortError"));}),t.open("PUT",p),t.send(i);})}async function $(i,p,e,r,s,u,l,c){let t=c?.contentType??p.type,{uploadId:b,key:U}=await i.multipart.init({key:e,contentType:t,metadata:c?.metadata,bucket:c?.bucket}),o=Math.ceil(p.size/r),g=[],n=Array.from({length:o},()=>({bytes:0})),m=()=>{let d=n.reduce((a,y)=>a+y.bytes,0);u?.({loaded:d,total:p.size,percent:Math.round(d/p.size*100)});};try{for(let d=0;d<o;d+=s){if(l?.aborted)throw new DOMException("Upload aborted","AbortError");let a=Math.min(d+s,o),y=[];for(let f=d;f<a;f++){let P=f*r,R=Math.min(P+r,p.size),E=p.slice(P,R),h=f+1;y.push(M(async()=>{let{presignedUrl:v}=await i.multipart.signPart({key:U,uploadId:b,partNumber:h,bucket:c?.bucket});n[f].bytes=0;let k=await q(E,v,n[f],p.size,m,l);return {partNumber:h,eTag:k.replace(/"/g,"")}},3,l));}let w=await Promise.all(y);g.push(...w);}g.sort((d,a)=>d.partNumber-a.partNumber),await i.multipart.complete({key:U,uploadId:b,parts:g,bucket:c?.bucket}),u?.({loaded:p.size,total:p.size,percent:100});}catch(d){throw i.multipart.abort({key:U,uploadId:b,bucket:c?.bucket}).catch(()=>{}),d}}async function S(i,p,e,r={},s={},u,l){let c=r.multipartThreshold??52428800,t=r.multipart===true&&p.size>=c,b=r.concurrentParts??3,U=l?.contentType??p.type,o;return t?await $(i,p,e,10485760,b,s.onProgress,u,l):(o=await M(async()=>{let g=await i.upload({key:e,contentType:U,metadata:l?.metadata,bucket:l?.bucket});return K(p,g.url,s.onProgress,u)},3,u),await i.confirm({key:e,bucket:l?.bucket})),{key:e,eTag:o}}async function A(i,p,e={},r={},s,u){let l=p.map(g=>({...g,status:"pending",progress:{loaded:0,total:g.file.size,percent:0},result:null,error:null})),c=()=>{let g=l.reduce((m,d)=>m+d.progress.loaded,0),n=l.reduce((m,d)=>m+d.progress.total,0);r.onTotalProgress?.({loaded:g,total:n,percent:n>0?Math.round(g/n*100):0});},t=0,b=async()=>{for(;t<l.length;){if(s?.aborted)return;let g=t++,n=l[g];n.status="uploading";try{let m=await S(i,n.file,n.objectKey,e,{onProgress:d=>{n.progress=d,r.onFileProgress?.(n.id,d),c();}},s,u?.(n.file));n.status="success",n.result=m,n.progress={loaded:n.file.size,total:n.file.size,percent:100},r.onFileSuccess?.(n.id,m),c();}catch(m){if(m.name==="AbortError"){n.status="error",n.error="Upload cancelled";return}let d=m instanceof Error?m.message:"Upload failed";n.status="error",n.error=d,r.onFileError?.(n.id,d),c();}}},U=e.concurrentFiles??2,o=Array.from({length:Math.min(U,p.length)},()=>b());return await Promise.all(o),l}var W={loaded:0,total:0,percent:0},F={phase:"idle",progress:W,error:null,result:null,fileName:null,fileSize:null};function O(i){let[p,e]=useState(F),r=useRef(i);r.current=i;let s=useRef(null),u=useCallback(async(t,b,U)=>{e({...F,phase:"validating",fileName:t.name,fileSize:t.size});let o=r.current,g=validateFile(t,{accept:o.accept,maxFileSize:o.maxFileSize});if(g){e(m=>({...m,phase:"error",error:g})),o.onError?.(t,new Error(g),"validating");return}if(o.beforeUpload&&!await o.beforeUpload(t)){e(d=>({...d,phase:"error",error:"Upload blocked by beforeUpload hook"})),o.onError?.(t,new Error("blocked"),"validating");return}e(m=>({...m,phase:"uploading"})),o.onUploadStart?.(t,b);let n=new AbortController;s.current=n;try{let m=await S(o.presignApi,t,b,{multipart:o.multipart,multipartThreshold:o.multipartThreshold,concurrentParts:o.concurrentParts},{onProgress:d=>{e(a=>({...a,progress:d})),o.onProgress?.(t,d);}},n.signal,U);e(d=>({...d,phase:"success",result:m,progress:{loaded:t.size,total:t.size,percent:100}})),await o.onSuccess?.(t,m);}catch(m){if(m.name==="AbortError"){o.onCancel?.(t),e(F);return}let d=m instanceof Error?m.message:"Upload failed";e(a=>({...a,phase:"error",error:d})),o.onError?.(t,m,"uploading");}finally{s.current=null;}},[]),l=useCallback(()=>{s.current?.abort(),e(F);},[]),c=useCallback(()=>{s.current?.abort(),e(F);},[]);return {...p,upload:u,cancel:l,reset:c}}var oe={loaded:0,total:0,percent:0},T={phase:"idle",files:[],totalProgress:oe,error:null},re=0;function se(){return `file-${++re}`}function z(i){let[p,e]=useState(T),r=useRef(i);r.current=i;let s=useRef(null),u=useRef(new Map),l=useCallback(async(b,U)=>{let o=r.current,g=[],n=[],m=new Map;if(e(a=>({...a,phase:"validating",error:null})),o.maxFiles&&b.length>o.maxFiles){let a=`Too many files. Maximum is ${o.maxFiles}.`;e(y=>({...y,phase:"error",error:a})),o.onError?.(new Error(a));return}for(let a of b){let y=validateFile(a,{accept:o.accept,maxFileSize:o.maxFileSize});if(y){let w=`${a.name}: ${y}`;e(f=>({...f,phase:"error",error:w})),o.onError?.(new Error(w));return}}if(o.beforeUpload&&!await o.beforeUpload(b)){e(y=>({...y,phase:"error",error:"Upload blocked by beforeUpload hook"})),o.onError?.(new Error("blocked"));return}for(let a of b){let y=se(),w=U(a);g.push({id:y,file:a,objectKey:w}),m.set(y,a),n.push({id:y,fileName:a.name,fileSize:a.size,status:"pending",progress:{loaded:0,total:a.size,percent:0},error:null});}u.current=m,e({phase:"uploading",files:n,totalProgress:{loaded:0,total:b.reduce((a,y)=>a+y.size,0),percent:0},error:null}),o.onUploadStart?.(b);let d=new AbortController;s.current=d;try{let a=await A(o.presignApi,g,{multipart:o.multipart,multipartThreshold:o.multipartThreshold,concurrentParts:o.concurrentParts,concurrentFiles:o.concurrentFiles},{onFileProgress:(f,P)=>{e(E=>({...E,files:E.files.map(h=>h.id===f?{...h,status:"uploading",progress:P}:h)}));let R=m.get(f);R&&o.onFileProgress?.(R,P);},onFileSuccess:(f,P)=>{e(E=>({...E,files:E.files.map(h=>h.id===f?{...h,status:"success",progress:{loaded:h.fileSize,total:h.fileSize,percent:100}}:h)}));let R=m.get(f);R&&o.onFileSuccess?.(R,P);},onFileError:(f,P)=>{e(E=>({...E,files:E.files.map(h=>h.id===f?{...h,status:"error",error:P}:h)}));let R=m.get(f);R&&o.onFileError?.(R,P);},onTotalProgress:f=>{e(P=>({...P,totalProgress:f})),o.onProgress?.(f);}},d.signal,f=>{let P=o.getUploadOptions?.(f);return o.uploadOptions?{...o.uploadOptions,...P}:P??{}}),y=a.some(f=>f.status==="error"),w=a.filter(f=>f.result!==null).map(f=>f.result);e(f=>({...f,phase:y?"error":"success",error:y?`${a.filter(P=>P.status==="error").length} file(s) failed`:null,totalProgress:y?f.totalProgress:{loaded:f.totalProgress.total,total:f.totalProgress.total,percent:100}})),y||await o.onSuccess?.(w);}catch(a){if(a.name==="AbortError"){o.onCancel?.(),e(T);return}let y=a instanceof Error?a.message:"Upload failed";e(w=>({...w,phase:"error",error:y})),o.onError?.(a);}finally{s.current=null;}},[]),c=useCallback(()=>{s.current?.abort(),e(T);},[]),t=useCallback(()=>{s.current?.abort(),e(T);},[]);return {...p,upload:l,cancel:c,reset:t}}function ae(i){let{objectKey:p,getUploadOptions:e,...r}=i,s=O(r),u=useRef(null),[l,c]=useState(null),t=g=>typeof p=="function"?p(g):p,b=async g=>{let n=g?.[0];n&&(c({name:n.name,size:n.size}),await s.upload(n,t(n),e?.(n)));},U=()=>u.current?.click(),o=s.phase==="uploading";return {phase:s.phase,progress:s.progress,error:s.error,fileInfo:l,isUploading:o,handleFiles:b,openFilePicker:U,cancel:s.cancel,reset:()=>{s.reset(),c(null);},inputProps:{ref:u,type:"file",accept:r.accept?.join(","),hidden:true,onChange:g=>{b(g.target.files),g.target.value="";}},dropHandlers:{onDragOver:g=>{g.preventDefault(),g.stopPropagation();},onDrop:g=>{g.preventDefault(),g.stopPropagation(),o||b(g.dataTransfer.files);}}}}function pe(i){let{objectKey:p,...e}=i,r=z(e),s=useRef(null),u=async t=>{t?.length&&await r.upload(Array.from(t),p);},l=()=>s.current?.click(),c=r.phase==="uploading";return {phase:r.phase,files:r.files,totalProgress:r.totalProgress,error:r.error,isUploading:c,handleFiles:u,openFilePicker:l,cancel:r.cancel,reset:r.reset,inputProps:{ref:s,type:"file",multiple:true,accept:e.accept?.join(","),hidden:true,onChange:t=>{u(t.target.files),t.target.value="";}},dropHandlers:{onDragOver:t=>{t.preventDefault(),t.stopPropagation();},onDrop:t=>{t.preventDefault(),t.stopPropagation(),c||u(t.dataTransfer.files);}}}}var G={loaded:0,total:0,percent:0},D={phase:"idle",progress:G,error:null,fileName:null,fileSize:null};function ue(i){let[p,e]=useState(D),r=useRef(i);r.current=i;let s=useRef(null),u=useCallback(async(t,b)=>{let U=b??t.split("/").pop()??t,o=r.current,g=o.mode??"native";if(o.beforeDownload&&!await o.beforeDownload(t)){e(m=>({...m,phase:"error",error:"Download blocked by beforeDownload hook"})),o.onError?.(t,new Error("blocked"),"presigning");return}e({phase:"presigning",progress:G,error:null,fileName:U,fileSize:null});try{let{url:n}=await o.presignApi.download(t,{fileName:U,bucket:o.bucket});if(g==="native"){let h=document.createElement("a");h.href=n,h.download=U,h.click(),e(v=>({...v,phase:"success"})),await o.onSuccess?.(t),e(D);return}e(h=>({...h,phase:"downloading"})),o.onDownloadStart?.(t);let m=new AbortController;s.current=m;let d=await fetch(n,{signal:m.signal});if(!d.ok)throw new Error(`Download failed: ${d.statusText}`);let a=Number(d.headers.get("content-length")||0);e(h=>({...h,fileSize:a||null}));let y=d.body?.getReader();if(!y)throw new Error("ReadableStream not supported");let w=[],f=0;for(;;){let{done:h,value:v}=await y.read();if(h)break;w.push(v),f+=v.byteLength;let k=a>0?Math.round(f/a*100):0,_={loaded:f,total:a,percent:k};e(Y=>({...Y,progress:_})),o.onProgress?.(t,_);}let P=new Blob(w),R=URL.createObjectURL(P),E=document.createElement("a");E.href=R,E.download=U,E.click(),URL.revokeObjectURL(R),e(h=>({...h,phase:"success",fileSize:P.size,progress:{loaded:P.size,total:P.size,percent:100}})),await o.onSuccess?.(t);}catch(n){if(n.name==="AbortError"){o.onCancel?.(t),e(D);return}let m=n instanceof Error?n.message:"Download failed";e(d=>({...d,phase:"error",error:m})),o.onError?.(t,n,"downloading");}finally{s.current=null;}},[]),l=useCallback(()=>{s.current?.abort(),e(D);},[]),c=useCallback(()=>{s.current?.abort(),e(D);},[]);return {...p,download:u,cancel:l,reset:c}}var N={phase:"idle",error:null};function ge(i){let[p,e]=useState(N),[r,s]=useState(null),u=useRef(i);u.current=i;let l=useCallback(U=>{s(U),e({phase:"confirming",error:null});},[]),c=useCallback(async()=>{if(!r)return;let U=u.current;if(U.beforeDelete&&!await U.beforeDelete(r)){e({phase:"error",error:"Delete blocked by beforeDelete hook"}),U.onError?.(r,new Error("blocked"),"confirming"),s(null);return}e({phase:"deleting",error:null}),U.onDeleteStart?.(r);try{await U.presignApi.delete(r,{bucket:U.bucket}),e({phase:"success",error:null}),await U.onSuccess?.(r),s(null);}catch(o){let g=o instanceof Error?o.message:"Delete failed";e({phase:"error",error:g}),U.onError?.(r,o,"deleting");}},[r]),t=useCallback(()=>{s(null),e(N);},[]),b=useCallback(()=>{s(null),e(N);},[]);return {...p,pendingKey:r,requestDelete:l,confirmDelete:c,cancelDelete:t,reset:b}}export{Z as formatFileSize,S as uploadFile,A as uploadFiles,ge as useDelete,ue as useDownload,z as useMultiUpload,pe as useMultiUploadControls,O as useUpload,ae as useUploadControls};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/use-upload.ts","../src/use-multi-upload.ts","../src/use-upload-controls.ts","../src/use-multi-upload-controls.ts","../src/use-download.ts","../src/use-delete.ts"],"names":["INITIAL_PROGRESS","INITIAL_STATE","useUpload","options","state","setState","useState","optionsRef","useRef","abortRef","upload","useCallback","file","objectKey","opts","validationError","validateFile","s","controller","result","uploadFile","progress","err","message","cancel","reset","nextId","generateId","useMultiUpload","fileMapRef","files","resolveKey","items","fileStates","fileMap","msg","id","f","results","uploadFiles","error","hasErrors","r","successResults","useUploadControls","hookOptions","ctx","inputRef","fileInfo","setFileInfo","handleFiles","openFilePicker","isUploading","useMultiUploadControls","e","useDownload","download","key","downloadName","name","mode","url","anchor","res","contentLength","reader","chunks","loaded","done","value","percent","blob","blobUrl","useDelete","pendingKey","setPendingKey","requestDelete","confirmDelete","cancelDelete"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type {\n PresignApi,\n UploadConfig,\n UploadHooks,\n UploadPhase,\n UploadProgress,\n UploadResult,\n} from \"@better-s3/core\";\nimport { uploadFile, validateFile } from \"@better-s3/core\";\n\nexport type UseUploadOptions = UploadConfig &\n UploadHooks & {\n presignApi: PresignApi;\n };\n\nexport type UseUploadState = {\n phase: UploadPhase;\n progress: UploadProgress;\n error: string | null;\n result: UploadResult | null;\n fileName: string | null;\n fileSize: number | null;\n};\n\nexport type UseUploadReturn = UseUploadState & {\n upload: (file: File, objectKey: string) => Promise<void>;\n cancel: () => void;\n reset: () => void;\n};\n\nconst INITIAL_PROGRESS: UploadProgress = { loaded: 0, total: 0, percent: 0 };\n\nconst INITIAL_STATE: UseUploadState = {\n phase: \"idle\",\n progress: INITIAL_PROGRESS,\n error: null,\n result: null,\n fileName: null,\n fileSize: null,\n};\n\nexport function useUpload(options: UseUploadOptions): UseUploadReturn {\n const [state, setState] = useState<UseUploadState>(INITIAL_STATE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const abortRef = useRef<AbortController | null>(null);\n\n const upload = useCallback(async (file: File, objectKey: string) => {\n setState({\n ...INITIAL_STATE,\n phase: \"validating\",\n fileName: file.name,\n fileSize: file.size,\n });\n const opts = optionsRef.current;\n\n const validationError = validateFile(file, {\n accept: opts.accept,\n maxFileSize: opts.maxFileSize,\n });\n if (validationError) {\n setState((s) => ({ ...s, phase: \"error\", error: validationError }));\n opts.onError?.(file, new Error(validationError), \"validating\");\n return;\n }\n\n if (opts.beforeUpload) {\n const allowed = await opts.beforeUpload(file);\n if (!allowed) {\n setState((s) => ({\n ...s,\n phase: \"error\",\n error: \"Upload blocked by beforeUpload hook\",\n }));\n opts.onError?.(file, new Error(\"blocked\"), \"validating\");\n return;\n }\n }\n\n setState((s) => ({ ...s, phase: \"uploading\" }));\n opts.onUploadStart?.(file, objectKey);\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n const result = await uploadFile(\n opts.presignApi,\n file,\n objectKey,\n {\n multipart: opts.multipart,\n multipartThreshold: opts.multipartThreshold,\n concurrentParts: opts.concurrentParts,\n },\n {\n onProgress: (progress) => {\n setState((s) => ({ ...s, progress }));\n opts.onProgress?.(file, progress);\n },\n },\n controller.signal,\n );\n\n setState((s) => ({\n ...s,\n phase: \"success\",\n result,\n progress: { loaded: file.size, total: file.size, percent: 100 },\n }));\n await opts.onSuccess?.(file, result);\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n opts.onCancel?.(file);\n setState(INITIAL_STATE);\n return;\n }\n const message = err instanceof Error ? err.message : \"Upload failed\";\n setState((s) => ({ ...s, phase: \"error\", error: message }));\n opts.onError?.(file, err, \"uploading\");\n } finally {\n abortRef.current = null;\n }\n }, []);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n const reset = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, upload, cancel, reset };\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type {\n PresignApi,\n UploadConfig,\n UploadProgress,\n UploadResult,\n MultiUploadPhase,\n MultiUploadFileState,\n MultiUploadHooks,\n} from \"@better-s3/core\";\nimport { uploadFiles, validateFile } from \"@better-s3/core\";\n\nexport type UseMultiUploadOptions = UploadConfig &\n MultiUploadHooks & {\n presignApi: PresignApi;\n };\n\nexport type UseMultiUploadState = {\n phase: MultiUploadPhase;\n files: MultiUploadFileState[];\n totalProgress: UploadProgress;\n error: string | null;\n};\n\nexport type UseMultiUploadReturn = UseMultiUploadState & {\n upload: (files: File[], resolveKey: (file: File) => string) => Promise<void>;\n cancel: () => void;\n reset: () => void;\n};\n\nconst INITIAL_PROGRESS: UploadProgress = { loaded: 0, total: 0, percent: 0 };\n\nconst INITIAL_STATE: UseMultiUploadState = {\n phase: \"idle\",\n files: [],\n totalProgress: INITIAL_PROGRESS,\n error: null,\n};\n\nlet nextId = 0;\nfunction generateId() {\n return `file-${++nextId}`;\n}\n\nexport function useMultiUpload(\n options: UseMultiUploadOptions,\n): UseMultiUploadReturn {\n const [state, setState] = useState<UseMultiUploadState>(INITIAL_STATE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const abortRef = useRef<AbortController | null>(null);\n const fileMapRef = useRef<Map<string, File>>(new Map());\n\n const upload = useCallback(\n async (files: File[], resolveKey: (file: File) => string) => {\n const opts = optionsRef.current;\n\n const items: Array<{\n id: string;\n file: File;\n objectKey: string;\n }> = [];\n const fileStates: MultiUploadFileState[] = [];\n const fileMap = new Map<string, File>();\n\n setState((s) => ({ ...s, phase: \"validating\", error: null }));\n\n if (opts.maxFiles && files.length > opts.maxFiles) {\n const msg = `Too many files. Maximum is ${opts.maxFiles}.`;\n setState((s) => ({ ...s, phase: \"error\", error: msg }));\n opts.onError?.(new Error(msg));\n return;\n }\n\n for (const file of files) {\n const validationError = validateFile(file, {\n accept: opts.accept,\n maxFileSize: opts.maxFileSize,\n });\n if (validationError) {\n const msg = `${file.name}: ${validationError}`;\n setState((s) => ({ ...s, phase: \"error\", error: msg }));\n opts.onError?.(new Error(msg));\n return;\n }\n }\n\n if (opts.beforeUpload) {\n const allowed = await opts.beforeUpload(files);\n if (!allowed) {\n setState((s) => ({\n ...s,\n phase: \"error\",\n error: \"Upload blocked by beforeUpload hook\",\n }));\n opts.onError?.(new Error(\"blocked\"));\n return;\n }\n }\n\n for (const file of files) {\n const id = generateId();\n const objectKey = resolveKey(file);\n items.push({ id, file, objectKey });\n fileMap.set(id, file);\n fileStates.push({\n id,\n fileName: file.name,\n fileSize: file.size,\n status: \"pending\",\n progress: { loaded: 0, total: file.size, percent: 0 },\n error: null,\n });\n }\n\n fileMapRef.current = fileMap;\n\n setState({\n phase: \"uploading\",\n files: fileStates,\n totalProgress: {\n loaded: 0,\n total: files.reduce((s, f) => s + f.size, 0),\n percent: 0,\n },\n error: null,\n });\n\n opts.onUploadStart?.(files);\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n const results = await uploadFiles(\n opts.presignApi,\n items,\n {\n multipart: opts.multipart,\n multipartThreshold: opts.multipartThreshold,\n concurrentParts: opts.concurrentParts,\n concurrentFiles: opts.concurrentFiles,\n },\n {\n onFileProgress: (id, progress) => {\n setState((s) => ({\n ...s,\n files: s.files.map((f) =>\n f.id === id ? { ...f, status: \"uploading\", progress } : f,\n ),\n }));\n const file = fileMap.get(id);\n if (file) opts.onFileProgress?.(file, progress);\n },\n onFileSuccess: (id, result) => {\n setState((s) => ({\n ...s,\n files: s.files.map((f) =>\n f.id === id\n ? {\n ...f,\n status: \"success\",\n progress: {\n loaded: f.fileSize,\n total: f.fileSize,\n percent: 100,\n },\n }\n : f,\n ),\n }));\n const file = fileMap.get(id);\n if (file) opts.onFileSuccess?.(file, result);\n },\n onFileError: (id, error) => {\n setState((s) => ({\n ...s,\n files: s.files.map((f) =>\n f.id === id ? { ...f, status: \"error\", error } : f,\n ),\n }));\n const file = fileMap.get(id);\n if (file) opts.onFileError?.(file, error);\n },\n onTotalProgress: (progress) => {\n setState((s) => ({ ...s, totalProgress: progress }));\n opts.onProgress?.(progress);\n },\n },\n controller.signal,\n );\n\n const hasErrors = results.some((r) => r.status === \"error\");\n const successResults = results\n .filter((r) => r.result !== null)\n .map((r) => r.result!);\n\n setState((s) => ({\n ...s,\n phase: hasErrors ? \"error\" : \"success\",\n error: hasErrors\n ? `${results.filter((r) => r.status === \"error\").length} file(s) failed`\n : null,\n totalProgress: hasErrors\n ? s.totalProgress\n : {\n loaded: s.totalProgress.total,\n total: s.totalProgress.total,\n percent: 100,\n },\n }));\n\n if (!hasErrors) {\n await opts.onSuccess?.(successResults);\n }\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n opts.onCancel?.();\n setState(INITIAL_STATE);\n return;\n }\n const message = err instanceof Error ? err.message : \"Upload failed\";\n setState((s) => ({ ...s, phase: \"error\", error: message }));\n opts.onError?.(err);\n } finally {\n abortRef.current = null;\n }\n },\n [],\n );\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n const reset = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, upload, cancel, reset };\n}\n","\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport type { UploadPhase, UploadProgress } from \"@better-s3/core\";\nimport { useUpload, type UseUploadOptions } from \"./use-upload\";\n\nexport type UseUploadControlsOptions = UseUploadOptions & {\n objectKey: string | ((file: File) => string);\n};\n\nexport type UseUploadControlsReturn = {\n /** Current upload phase */\n phase: UploadPhase;\n /** Upload progress (loaded, total, percent) */\n progress: UploadProgress;\n /** Error message if upload failed */\n error: string | null;\n /** Info about the selected file */\n fileInfo: { name: string; size: number } | null;\n /** Whether an upload is currently in progress */\n isUploading: boolean;\n /** Trigger upload from a FileList (e.g. from a file input or drop event) */\n handleFiles: (files: FileList | null) => void;\n /** Open the native file picker dialog */\n openFilePicker: () => void;\n /** Cancel the current upload */\n cancel: () => void;\n /** Reset to idle state */\n reset: () => void;\n /** Spread on a hidden `<input>` element */\n inputProps: {\n ref: React.RefObject<HTMLInputElement | null>;\n type: \"file\";\n accept?: string;\n hidden: true;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n };\n /** Spread on a container to enable drag-and-drop */\n dropHandlers: {\n onDragOver: (e: React.DragEvent) => void;\n onDrop: (e: React.DragEvent) => void;\n };\n};\n\nexport function useUploadControls(\n options: UseUploadControlsOptions,\n): UseUploadControlsReturn {\n const { objectKey, ...hookOptions } = options;\n const ctx = useUpload(hookOptions);\n const inputRef = useRef<HTMLInputElement>(null);\n const [fileInfo, setFileInfo] = useState<{\n name: string;\n size: number;\n } | null>(null);\n\n const resolveKey = (file: File): string =>\n typeof objectKey === \"function\" ? objectKey(file) : objectKey;\n\n const handleFiles = async (files: FileList | null) => {\n const file = files?.[0];\n if (!file) return;\n setFileInfo({ name: file.name, size: file.size });\n await ctx.upload(file, resolveKey(file));\n };\n\n const openFilePicker = () => inputRef.current?.click();\n\n const isUploading = ctx.phase === \"uploading\";\n\n return {\n phase: ctx.phase,\n progress: ctx.progress,\n error: ctx.error,\n fileInfo,\n isUploading,\n handleFiles,\n openFilePicker,\n cancel: ctx.cancel,\n reset: () => {\n ctx.reset();\n setFileInfo(null);\n },\n inputProps: {\n ref: inputRef,\n type: \"file\",\n accept: hookOptions.accept?.join(\",\"),\n hidden: true,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => {\n handleFiles(e.target.files);\n e.target.value = \"\";\n },\n },\n dropHandlers: {\n onDragOver: (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n },\n onDrop: (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (!isUploading) handleFiles(e.dataTransfer.files);\n },\n },\n };\n}\n","\"use client\";\n\nimport { useRef } from \"react\";\nimport type {\n UploadProgress,\n MultiUploadPhase,\n MultiUploadFileState,\n} from \"@better-s3/core\";\nimport { useMultiUpload, type UseMultiUploadOptions } from \"./use-multi-upload\";\n\nexport type UseMultiUploadControlsOptions = UseMultiUploadOptions & {\n objectKey: (file: File) => string;\n};\n\nexport type UseMultiUploadControlsReturn = {\n /** Current upload phase */\n phase: MultiUploadPhase;\n /** Per-file upload state */\n files: MultiUploadFileState[];\n /** Aggregated progress across all files */\n totalProgress: UploadProgress;\n /** Error message if upload failed */\n error: string | null;\n /** Whether an upload is currently in progress */\n isUploading: boolean;\n /** Trigger upload from a FileList (e.g. from a file input or drop event) */\n handleFiles: (files: FileList | null) => void;\n /** Open the native file picker dialog */\n openFilePicker: () => void;\n /** Cancel all uploads */\n cancel: () => void;\n /** Reset to idle state */\n reset: () => void;\n /** Spread on a hidden `<input>` element */\n inputProps: {\n ref: React.RefObject<HTMLInputElement | null>;\n type: \"file\";\n multiple: true;\n accept?: string;\n hidden: true;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n };\n /** Spread on a container to enable drag-and-drop */\n dropHandlers: {\n onDragOver: (e: React.DragEvent) => void;\n onDrop: (e: React.DragEvent) => void;\n };\n};\n\nexport function useMultiUploadControls(\n options: UseMultiUploadControlsOptions,\n): UseMultiUploadControlsReturn {\n const { objectKey, ...hookOptions } = options;\n const ctx = useMultiUpload(hookOptions);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleFiles = async (files: FileList | null) => {\n if (!files?.length) return;\n await ctx.upload(Array.from(files), objectKey);\n };\n\n const openFilePicker = () => inputRef.current?.click();\n\n const isUploading = ctx.phase === \"uploading\";\n\n return {\n phase: ctx.phase,\n files: ctx.files,\n totalProgress: ctx.totalProgress,\n error: ctx.error,\n isUploading,\n handleFiles,\n openFilePicker,\n cancel: ctx.cancel,\n reset: ctx.reset,\n inputProps: {\n ref: inputRef,\n type: \"file\",\n multiple: true,\n accept: hookOptions.accept?.join(\",\"),\n hidden: true,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => {\n handleFiles(e.target.files);\n e.target.value = \"\";\n },\n },\n dropHandlers: {\n onDragOver: (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n },\n onDrop: (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (!isUploading) handleFiles(e.dataTransfer.files);\n },\n },\n };\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type {\n PresignApi,\n DownloadPhase,\n DownloadProgress,\n DownloadHooks,\n} from \"@better-s3/core\";\n\nexport type UseDownloadOptions = DownloadHooks & {\n presignApi: PresignApi;\n /**\n * `\"native\"` — browser handles download natively via presigned URL (default)\n * `\"fetch\"` — streams via fetch, enables in-app progress tracking\n */\n mode?: \"native\" | \"fetch\";\n};\n\nexport type UseDownloadState = {\n phase: DownloadPhase;\n progress: DownloadProgress;\n error: string | null;\n fileName: string | null;\n fileSize: number | null;\n};\n\nexport type UseDownloadReturn = UseDownloadState & {\n download: (key: string, downloadName?: string) => Promise<void>;\n cancel: () => void;\n reset: () => void;\n};\n\nconst INITIAL_PROGRESS: DownloadProgress = { loaded: 0, total: 0, percent: 0 };\n\nconst INITIAL_STATE: UseDownloadState = {\n phase: \"idle\",\n progress: INITIAL_PROGRESS,\n error: null,\n fileName: null,\n fileSize: null,\n};\n\nexport function useDownload(options: UseDownloadOptions): UseDownloadReturn {\n const [state, setState] = useState<UseDownloadState>(INITIAL_STATE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const abortRef = useRef<AbortController | null>(null);\n\n const download = useCallback(async (key: string, downloadName?: string) => {\n const name = downloadName ?? key.split(\"/\").pop() ?? key;\n const opts = optionsRef.current;\n const mode = opts.mode ?? \"native\";\n\n if (opts.beforeDownload) {\n const allowed = await opts.beforeDownload(key);\n if (!allowed) {\n setState((s) => ({\n ...s,\n phase: \"error\",\n error: \"Download blocked by beforeDownload hook\",\n }));\n opts.onError?.(key, new Error(\"blocked\"), \"presigning\");\n return;\n }\n }\n\n setState({\n phase: \"presigning\",\n progress: INITIAL_PROGRESS,\n error: null,\n fileName: name,\n fileSize: null,\n });\n\n try {\n const { url } = await opts.presignApi.download(key, name);\n\n if (mode === \"native\") {\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = name;\n anchor.click();\n setState((s) => ({ ...s, phase: \"success\" }));\n await opts.onSuccess?.(key);\n setState(INITIAL_STATE);\n return;\n }\n\n // ── Fetch mode ───────────────────────────────────────────────\n setState((s) => ({ ...s, phase: \"downloading\" }));\n opts.onDownloadStart?.(key);\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n const res = await fetch(url, { signal: controller.signal });\n if (!res.ok) throw new Error(`Download failed: ${res.statusText}`);\n\n const contentLength = Number(res.headers.get(\"content-length\") || 0);\n setState((s) => ({ ...s, fileSize: contentLength || null }));\n\n const reader = res.body?.getReader();\n if (!reader) throw new Error(\"ReadableStream not supported\");\n\n const chunks: BlobPart[] = [];\n let loaded = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n loaded += value.byteLength;\n const percent =\n contentLength > 0 ? Math.round((loaded / contentLength) * 100) : 0;\n const progress: DownloadProgress = {\n loaded,\n total: contentLength,\n percent,\n };\n setState((s) => ({ ...s, progress }));\n opts.onProgress?.(key, progress);\n }\n\n const blob = new Blob(chunks);\n const blobUrl = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = blobUrl;\n anchor.download = name;\n anchor.click();\n URL.revokeObjectURL(blobUrl);\n\n setState((s) => ({\n ...s,\n phase: \"success\",\n fileSize: blob.size,\n progress: { loaded: blob.size, total: blob.size, percent: 100 },\n }));\n await opts.onSuccess?.(key);\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n opts.onCancel?.(key);\n setState(INITIAL_STATE);\n return;\n }\n const message = err instanceof Error ? err.message : \"Download failed\";\n setState((s) => ({ ...s, phase: \"error\", error: message }));\n opts.onError?.(key, err, \"downloading\");\n } finally {\n abortRef.current = null;\n }\n }, []);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n const reset = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, download, cancel, reset };\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { PresignApi, DeletePhase, DeleteHooks } from \"@better-s3/core\";\n\nexport type UseDeleteOptions = DeleteHooks & {\n presignApi: PresignApi;\n};\n\nexport type UseDeleteState = {\n phase: DeletePhase;\n error: string | null;\n};\n\nexport type UseDeleteReturn = UseDeleteState & {\n requestDelete: (key: string) => void;\n confirmDelete: () => Promise<void>;\n cancelDelete: () => void;\n reset: () => void;\n pendingKey: string | null;\n};\n\nconst INITIAL_STATE: UseDeleteState = {\n phase: \"idle\",\n error: null,\n};\n\nexport function useDelete(options: UseDeleteOptions): UseDeleteReturn {\n const [state, setState] = useState<UseDeleteState>(INITIAL_STATE);\n const [pendingKey, setPendingKey] = useState<string | null>(null);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const requestDelete = useCallback((key: string) => {\n setPendingKey(key);\n setState({ phase: \"confirming\", error: null });\n }, []);\n\n const confirmDelete = useCallback(async () => {\n if (!pendingKey) return;\n const opts = optionsRef.current;\n\n if (opts.beforeDelete) {\n const allowed = await opts.beforeDelete(pendingKey);\n if (!allowed) {\n setState({\n phase: \"error\",\n error: \"Delete blocked by beforeDelete hook\",\n });\n opts.onError?.(pendingKey, new Error(\"blocked\"), \"confirming\");\n setPendingKey(null);\n return;\n }\n }\n\n setState({ phase: \"deleting\", error: null });\n opts.onDeleteStart?.(pendingKey);\n\n try {\n await opts.presignApi.delete(pendingKey);\n\n setState({ phase: \"success\", error: null });\n await opts.onSuccess?.(pendingKey);\n setPendingKey(null);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Delete failed\";\n setState({ phase: \"error\", error: message });\n opts.onError?.(pendingKey, err, \"deleting\");\n }\n }, [pendingKey]);\n\n const cancelDelete = useCallback(() => {\n setPendingKey(null);\n setState(INITIAL_STATE);\n }, []);\n\n const reset = useCallback(() => {\n setPendingKey(null);\n setState(INITIAL_STATE);\n }, []);\n\n return {\n ...state,\n pendingKey,\n requestDelete,\n confirmDelete,\n cancelDelete,\n reset,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/helpers.ts","../src/upload/retry.ts","../src/upload/simple.ts","../src/upload/part.ts","../src/upload/multipart.ts","../src/upload/upload-file.ts","../src/upload/upload-files.ts","../src/use-upload.ts","../src/use-multi-upload.ts","../src/use-upload-controls.ts","../src/use-multi-upload-controls.ts","../src/use-download.ts","../src/use-delete.ts"],"names":["formatFileSize","bytes","units","i","withRetry","fn","retries","signal","lastError","attempt","err","delay","r","uploadSimple","file","presignedUrl","onProgress","resolve","reject","xhr","onAbort","e","eTag","uploadPart","blob","partLoaded","totalSize","reportProgress","uploadMultipart","presignApi","objectKey","partSize","concurrentParts","requestOptions","contentType","uploadId","key","totalParts","parts","partProgress","loaded","sum","p","batchStart","batchEnd","batch","start","end","partNumber","batchResults","a","b","uploadFile","config","callbacks","threshold","useMultipart","presign","uploadFiles","items","getRequestOptions","results","item","reportTotalProgress","total","nextIndex","processNext","idx","result","progress","message","concurrentFiles","workers","INITIAL_PROGRESS","INITIAL_STATE","useUpload","options","state","setState","useState","optionsRef","useRef","abortRef","upload","useCallback","opts","validationError","validateFile","s","controller","cancel","reset","nextId","generateId","useMultiUpload","fileMapRef","files","resolveKey","fileStates","fileMap","msg","id","f","error","perFile","hasErrors","successResults","useUploadControls","getUploadOptions","hookOptions","ctx","inputRef","fileInfo","setFileInfo","handleFiles","openFilePicker","isUploading","useMultiUploadControls","useDownload","download","downloadName","name","mode","url","anchor","res","contentLength","reader","chunks","done","value","percent","blobUrl","useDelete","pendingKey","setPendingKey","requestDelete","confirmDelete","cancelDelete"],"mappings":"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","file":"index.js","sourcesContent":["export function formatFileSize(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n const size = bytes / Math.pow(1024, i);\n return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;\n}\n","import { RETRY_BASE_DELAY } from \"./constants\";\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n retries: number,\n signal?: AbortSignal,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if ((err as Error).name === \"AbortError\") throw err;\n lastError = err;\n if (attempt < retries) {\n const delay = RETRY_BASE_DELAY * 2 ** attempt;\n await new Promise((r) => setTimeout(r, delay));\n if (signal?.aborted)\n throw new DOMException(\"Upload aborted\", \"AbortError\");\n }\n }\n }\n throw lastError;\n}\n","import type { UploadProgress } from \"../types\";\n\nexport function uploadSimple(\n file: File,\n presignedUrl: string,\n onProgress?: (progress: UploadProgress) => void,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n const onAbort = () => {\n xhr.abort();\n reject(new DOMException(\"Upload aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable) {\n onProgress?.({\n loaded: e.loaded,\n total: e.total,\n percent: Math.round((e.loaded / e.total) * 100),\n });\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n signal?.removeEventListener(\"abort\", onAbort);\n if (xhr.status >= 200 && xhr.status < 300) {\n onProgress?.({ loaded: file.size, total: file.size, percent: 100 });\n const eTag = xhr.getResponseHeader(\"ETag\")?.replace(/\"/g, \"\");\n resolve(eTag ?? undefined);\n } else {\n reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n signal?.removeEventListener(\"abort\", onAbort);\n reject(new Error(\"Upload failed: network error\"));\n });\n\n xhr.addEventListener(\"abort\", () => {\n signal?.removeEventListener(\"abort\", onAbort);\n reject(new DOMException(\"Upload aborted\", \"AbortError\"));\n });\n\n xhr.open(\"PUT\", presignedUrl);\n xhr.setRequestHeader(\n \"Content-Type\",\n file.type || \"application/octet-stream\",\n );\n xhr.send(file);\n });\n}\n","export function uploadPart(\n blob: Blob,\n presignedUrl: string,\n partLoaded: { bytes: number },\n totalSize: number,\n reportProgress: () => void,\n signal?: AbortSignal,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n const onAbort = () => {\n xhr.abort();\n reject(new DOMException(\"Upload aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable) {\n partLoaded.bytes = e.loaded;\n reportProgress();\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n signal?.removeEventListener(\"abort\", onAbort);\n if (xhr.status >= 200 && xhr.status < 300) {\n partLoaded.bytes = blob.size;\n reportProgress();\n const eTag = xhr.getResponseHeader(\"ETag\") ?? \"\";\n resolve(eTag);\n } else {\n reject(new Error(`Part upload failed: ${xhr.status}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n signal?.removeEventListener(\"abort\", onAbort);\n reject(new Error(\"Part upload failed: network error\"));\n });\n\n xhr.addEventListener(\"abort\", () => {\n signal?.removeEventListener(\"abort\", onAbort);\n reject(new DOMException(\"Upload aborted\", \"AbortError\"));\n });\n\n xhr.open(\"PUT\", presignedUrl);\n xhr.send(blob);\n });\n}\n","import type { UploadProgress, UploadRequestOptions } from \"../types\";\nimport type { PresignApi } from \"@better-s3/server\";\nimport { MAX_RETRIES } from \"./constants\";\nimport { withRetry } from \"./retry\";\nimport { uploadPart } from \"./part\";\n\nexport async function uploadMultipart(\n presignApi: PresignApi,\n file: File,\n objectKey: string,\n partSize: number,\n concurrentParts: number,\n onProgress?: (progress: UploadProgress) => void,\n signal?: AbortSignal,\n requestOptions?: UploadRequestOptions,\n): Promise<void> {\n const contentType = requestOptions?.contentType ?? file.type;\n const { uploadId, key } = await presignApi.multipart.init({\n key: objectKey,\n contentType,\n metadata: requestOptions?.metadata,\n bucket: requestOptions?.bucket,\n });\n\n const totalParts = Math.ceil(file.size / partSize);\n const parts: Array<{ partNumber: number; eTag: string }> = [];\n\n const partProgress: Array<{ bytes: number }> = Array.from(\n { length: totalParts },\n () => ({ bytes: 0 }),\n );\n\n const reportProgress = () => {\n const loaded = partProgress.reduce((sum, p) => sum + p.bytes, 0);\n onProgress?.({\n loaded,\n total: file.size,\n percent: Math.round((loaded / file.size) * 100),\n });\n };\n\n try {\n for (\n let batchStart = 0;\n batchStart < totalParts;\n batchStart += concurrentParts\n ) {\n if (signal?.aborted) {\n throw new DOMException(\"Upload aborted\", \"AbortError\");\n }\n\n const batchEnd = Math.min(batchStart + concurrentParts, totalParts);\n const batch: Array<Promise<{ partNumber: number; eTag: string }>> = [];\n\n for (let i = batchStart; i < batchEnd; i++) {\n const start = i * partSize;\n const end = Math.min(start + partSize, file.size);\n const blob = file.slice(start, end);\n const partNumber = i + 1;\n\n batch.push(\n withRetry(\n async () => {\n const { presignedUrl } = await presignApi.multipart.signPart({\n key,\n uploadId,\n partNumber,\n bucket: requestOptions?.bucket,\n });\n\n partProgress[i].bytes = 0;\n\n const eTag = await uploadPart(\n blob,\n presignedUrl,\n partProgress[i],\n file.size,\n reportProgress,\n signal,\n );\n\n return { partNumber, eTag: eTag.replace(/\"/g, \"\") };\n },\n MAX_RETRIES,\n signal,\n ),\n );\n }\n\n const batchResults = await Promise.all(batch);\n parts.push(...batchResults);\n }\n\n parts.sort((a, b) => a.partNumber - b.partNumber);\n\n await presignApi.multipart.complete({\n key,\n uploadId,\n parts,\n bucket: requestOptions?.bucket,\n });\n onProgress?.({ loaded: file.size, total: file.size, percent: 100 });\n } catch (err) {\n presignApi.multipart\n .abort({ key, uploadId, bucket: requestOptions?.bucket })\n .catch(() => {});\n throw err;\n }\n}\n","import type {\n UploadConfig,\n UploadProgress,\n UploadResult,\n UploadRequestOptions,\n} from \"../types\";\nimport type { PresignApi } from \"@better-s3/server\";\nimport {\n DEFAULT_MULTIPART_THRESHOLD,\n DEFAULT_CONCURRENT_PARTS,\n DEFAULT_PART_SIZE,\n MAX_RETRIES,\n} from \"./constants\";\nimport { withRetry } from \"./retry\";\nimport { uploadSimple } from \"./simple\";\nimport { uploadMultipart } from \"./multipart\";\n\nexport type UploadEngineCallbacks = {\n onProgress?: (progress: UploadProgress) => void;\n};\n\nexport async function uploadFile(\n presignApi: PresignApi,\n file: File,\n objectKey: string,\n config: UploadConfig = {},\n callbacks: UploadEngineCallbacks = {},\n signal?: AbortSignal,\n requestOptions?: UploadRequestOptions,\n): Promise<UploadResult> {\n const threshold = config.multipartThreshold ?? DEFAULT_MULTIPART_THRESHOLD;\n const useMultipart = config.multipart === true && file.size >= threshold;\n const concurrentParts = config.concurrentParts ?? DEFAULT_CONCURRENT_PARTS;\n const contentType = requestOptions?.contentType ?? file.type;\n\n let eTag: string | undefined;\n\n if (useMultipart) {\n await uploadMultipart(\n presignApi,\n file,\n objectKey,\n DEFAULT_PART_SIZE,\n concurrentParts,\n callbacks.onProgress,\n signal,\n requestOptions,\n );\n } else {\n eTag = await withRetry(\n async () => {\n const presign = await presignApi.upload({\n key: objectKey,\n contentType,\n metadata: requestOptions?.metadata,\n bucket: requestOptions?.bucket,\n });\n return uploadSimple(file, presign.url, callbacks.onProgress, signal);\n },\n MAX_RETRIES,\n signal,\n );\n\n await presignApi.confirm({\n key: objectKey,\n bucket: requestOptions?.bucket,\n });\n }\n\n return { key: objectKey, eTag };\n}\n","import type {\n UploadConfig,\n UploadProgress,\n UploadResult,\n UploadRequestOptions,\n} from \"../types\";\nimport type { PresignApi } from \"@better-s3/server\";\nimport { DEFAULT_CONCURRENT_FILES } from \"./constants\";\nimport { uploadFile } from \"./upload-file\";\n\nexport type FileItemStatus = \"pending\" | \"uploading\" | \"success\" | \"error\";\n\nexport type FileItem = {\n id: string;\n file: File;\n objectKey: string;\n status: FileItemStatus;\n progress: UploadProgress;\n result: UploadResult | null;\n error: string | null;\n};\n\nexport type MultiUploadCallbacks = {\n onFileProgress?: (id: string, progress: UploadProgress) => void;\n onFileSuccess?: (id: string, result: UploadResult) => void;\n onFileError?: (id: string, error: string) => void;\n onTotalProgress?: (progress: UploadProgress) => void;\n};\n\nexport async function uploadFiles(\n presignApi: PresignApi,\n items: Array<{ id: string; file: File; objectKey: string }>,\n config: UploadConfig = {},\n callbacks: MultiUploadCallbacks = {},\n signal?: AbortSignal,\n getRequestOptions?: (file: File) => UploadRequestOptions,\n): Promise<FileItem[]> {\n const results: FileItem[] = items.map((item) => ({\n ...item,\n status: \"pending\" as FileItemStatus,\n progress: { loaded: 0, total: item.file.size, percent: 0 },\n result: null,\n error: null,\n }));\n\n const reportTotalProgress = () => {\n const loaded = results.reduce((sum, r) => sum + r.progress.loaded, 0);\n const total = results.reduce((sum, r) => sum + r.progress.total, 0);\n callbacks.onTotalProgress?.({\n loaded,\n total,\n percent: total > 0 ? Math.round((loaded / total) * 100) : 0,\n });\n };\n\n let nextIndex = 0;\n\n const processNext = async (): Promise<void> => {\n while (nextIndex < results.length) {\n if (signal?.aborted) return;\n const idx = nextIndex++;\n const item = results[idx];\n\n item.status = \"uploading\";\n\n try {\n const result = await uploadFile(\n presignApi,\n item.file,\n item.objectKey,\n config,\n {\n onProgress: (progress) => {\n item.progress = progress;\n callbacks.onFileProgress?.(item.id, progress);\n reportTotalProgress();\n },\n },\n signal,\n getRequestOptions?.(item.file),\n );\n item.status = \"success\";\n item.result = result;\n item.progress = {\n loaded: item.file.size,\n total: item.file.size,\n percent: 100,\n };\n callbacks.onFileSuccess?.(item.id, result);\n reportTotalProgress();\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n item.status = \"error\";\n item.error = \"Upload cancelled\";\n return;\n }\n const message = err instanceof Error ? err.message : \"Upload failed\";\n item.status = \"error\";\n item.error = message;\n callbacks.onFileError?.(item.id, message);\n reportTotalProgress();\n }\n }\n };\n\n const concurrentFiles = config.concurrentFiles ?? DEFAULT_CONCURRENT_FILES;\n const workers = Array.from(\n { length: Math.min(concurrentFiles, items.length) },\n () => processNext(),\n );\n await Promise.all(workers);\n\n return results;\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { PresignApi } from \"@better-s3/server\";\nimport { validateFile } from \"@better-s3/server\";\nimport type {\n UploadConfig,\n UploadHooks,\n UploadPhase,\n UploadProgress,\n UploadResult,\n UploadRequestOptions,\n} from \"./types\";\nimport { uploadFile } from \"./upload\";\n\nexport type UseUploadOptions = UploadConfig &\n UploadHooks & {\n presignApi: PresignApi;\n };\n\nexport type UseUploadState = {\n phase: UploadPhase;\n progress: UploadProgress;\n error: string | null;\n result: UploadResult | null;\n fileName: string | null;\n fileSize: number | null;\n};\n\nexport type UseUploadReturn = UseUploadState & {\n upload: (\n file: File,\n objectKey: string,\n requestOptions?: UploadRequestOptions,\n ) => Promise<void>;\n cancel: () => void;\n reset: () => void;\n};\n\nconst INITIAL_PROGRESS: UploadProgress = { loaded: 0, total: 0, percent: 0 };\n\nconst INITIAL_STATE: UseUploadState = {\n phase: \"idle\",\n progress: INITIAL_PROGRESS,\n error: null,\n result: null,\n fileName: null,\n fileSize: null,\n};\n\nexport function useUpload(options: UseUploadOptions): UseUploadReturn {\n const [state, setState] = useState<UseUploadState>(INITIAL_STATE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const abortRef = useRef<AbortController | null>(null);\n\n const upload = useCallback(\n async (\n file: File,\n objectKey: string,\n requestOptions?: UploadRequestOptions,\n ) => {\n setState({\n ...INITIAL_STATE,\n phase: \"validating\",\n fileName: file.name,\n fileSize: file.size,\n });\n const opts = optionsRef.current;\n\n const validationError = validateFile(file, {\n accept: opts.accept,\n maxFileSize: opts.maxFileSize,\n });\n if (validationError) {\n setState((s) => ({ ...s, phase: \"error\", error: validationError }));\n opts.onError?.(file, new Error(validationError), \"validating\");\n return;\n }\n\n if (opts.beforeUpload) {\n const allowed = await opts.beforeUpload(file);\n if (!allowed) {\n setState((s) => ({\n ...s,\n phase: \"error\",\n error: \"Upload blocked by beforeUpload hook\",\n }));\n opts.onError?.(file, new Error(\"blocked\"), \"validating\");\n return;\n }\n }\n\n setState((s) => ({ ...s, phase: \"uploading\" }));\n opts.onUploadStart?.(file, objectKey);\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n const result = await uploadFile(\n opts.presignApi,\n file,\n objectKey,\n {\n multipart: opts.multipart,\n multipartThreshold: opts.multipartThreshold,\n concurrentParts: opts.concurrentParts,\n },\n {\n onProgress: (progress) => {\n setState((s) => ({ ...s, progress }));\n opts.onProgress?.(file, progress);\n },\n },\n controller.signal,\n requestOptions,\n );\n\n setState((s) => ({\n ...s,\n phase: \"success\",\n result,\n progress: { loaded: file.size, total: file.size, percent: 100 },\n }));\n await opts.onSuccess?.(file, result);\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n opts.onCancel?.(file);\n setState(INITIAL_STATE);\n return;\n }\n const message = err instanceof Error ? err.message : \"Upload failed\";\n setState((s) => ({ ...s, phase: \"error\", error: message }));\n opts.onError?.(file, err, \"uploading\");\n } finally {\n abortRef.current = null;\n }\n },\n [],\n );\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n const reset = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, upload, cancel, reset };\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { PresignApi } from \"@better-s3/server\";\nimport { validateFile } from \"@better-s3/server\";\nimport type {\n UploadConfig,\n UploadProgress,\n UploadResult,\n UploadRequestOptions,\n MultiUploadPhase,\n MultiUploadFileState,\n MultiUploadHooks,\n} from \"./types\";\nimport { uploadFiles } from \"./upload\";\n\nexport type UseMultiUploadOptions = UploadConfig &\n MultiUploadHooks & {\n presignApi: PresignApi;\n /** Static request options applied to all files */\n uploadOptions?: UploadRequestOptions;\n /** Per-file request options (overrides uploadOptions) */\n getUploadOptions?: (file: File) => UploadRequestOptions;\n };\n\nexport type UseMultiUploadState = {\n phase: MultiUploadPhase;\n files: MultiUploadFileState[];\n totalProgress: UploadProgress;\n error: string | null;\n};\n\nexport type UseMultiUploadReturn = UseMultiUploadState & {\n upload: (files: File[], resolveKey: (file: File) => string) => Promise<void>;\n cancel: () => void;\n reset: () => void;\n};\n\nconst INITIAL_PROGRESS: UploadProgress = { loaded: 0, total: 0, percent: 0 };\n\nconst INITIAL_STATE: UseMultiUploadState = {\n phase: \"idle\",\n files: [],\n totalProgress: INITIAL_PROGRESS,\n error: null,\n};\n\nlet nextId = 0;\nfunction generateId() {\n return `file-${++nextId}`;\n}\n\nexport function useMultiUpload(\n options: UseMultiUploadOptions,\n): UseMultiUploadReturn {\n const [state, setState] = useState<UseMultiUploadState>(INITIAL_STATE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const abortRef = useRef<AbortController | null>(null);\n const fileMapRef = useRef<Map<string, File>>(new Map());\n\n const upload = useCallback(\n async (files: File[], resolveKey: (file: File) => string) => {\n const opts = optionsRef.current;\n\n const items: Array<{\n id: string;\n file: File;\n objectKey: string;\n }> = [];\n const fileStates: MultiUploadFileState[] = [];\n const fileMap = new Map<string, File>();\n\n setState((s) => ({ ...s, phase: \"validating\", error: null }));\n\n if (opts.maxFiles && files.length > opts.maxFiles) {\n const msg = `Too many files. Maximum is ${opts.maxFiles}.`;\n setState((s) => ({ ...s, phase: \"error\", error: msg }));\n opts.onError?.(new Error(msg));\n return;\n }\n\n for (const file of files) {\n const validationError = validateFile(file, {\n accept: opts.accept,\n maxFileSize: opts.maxFileSize,\n });\n if (validationError) {\n const msg = `${file.name}: ${validationError}`;\n setState((s) => ({ ...s, phase: \"error\", error: msg }));\n opts.onError?.(new Error(msg));\n return;\n }\n }\n\n if (opts.beforeUpload) {\n const allowed = await opts.beforeUpload(files);\n if (!allowed) {\n setState((s) => ({\n ...s,\n phase: \"error\",\n error: \"Upload blocked by beforeUpload hook\",\n }));\n opts.onError?.(new Error(\"blocked\"));\n return;\n }\n }\n\n for (const file of files) {\n const id = generateId();\n const objectKey = resolveKey(file);\n items.push({ id, file, objectKey });\n fileMap.set(id, file);\n fileStates.push({\n id,\n fileName: file.name,\n fileSize: file.size,\n status: \"pending\",\n progress: { loaded: 0, total: file.size, percent: 0 },\n error: null,\n });\n }\n\n fileMapRef.current = fileMap;\n\n setState({\n phase: \"uploading\",\n files: fileStates,\n totalProgress: {\n loaded: 0,\n total: files.reduce((s, f) => s + f.size, 0),\n percent: 0,\n },\n error: null,\n });\n\n opts.onUploadStart?.(files);\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n const results = await uploadFiles(\n opts.presignApi,\n items,\n {\n multipart: opts.multipart,\n multipartThreshold: opts.multipartThreshold,\n concurrentParts: opts.concurrentParts,\n concurrentFiles: opts.concurrentFiles,\n },\n {\n onFileProgress: (id, progress) => {\n setState((s) => ({\n ...s,\n files: s.files.map((f) =>\n f.id === id ? { ...f, status: \"uploading\", progress } : f,\n ),\n }));\n const file = fileMap.get(id);\n if (file) opts.onFileProgress?.(file, progress);\n },\n onFileSuccess: (id, result) => {\n setState((s) => ({\n ...s,\n files: s.files.map((f) =>\n f.id === id\n ? {\n ...f,\n status: \"success\",\n progress: {\n loaded: f.fileSize,\n total: f.fileSize,\n percent: 100,\n },\n }\n : f,\n ),\n }));\n const file = fileMap.get(id);\n if (file) opts.onFileSuccess?.(file, result);\n },\n onFileError: (id, error) => {\n setState((s) => ({\n ...s,\n files: s.files.map((f) =>\n f.id === id ? { ...f, status: \"error\", error } : f,\n ),\n }));\n const file = fileMap.get(id);\n if (file) opts.onFileError?.(file, error);\n },\n onTotalProgress: (progress) => {\n setState((s) => ({ ...s, totalProgress: progress }));\n opts.onProgress?.(progress);\n },\n },\n controller.signal,\n (file) => {\n const perFile = opts.getUploadOptions?.(file);\n if (!opts.uploadOptions) return perFile ?? {};\n return { ...opts.uploadOptions, ...perFile };\n },\n );\n\n const hasErrors = results.some((r) => r.status === \"error\");\n const successResults = results\n .filter((r) => r.result !== null)\n .map((r) => r.result!);\n\n setState((s) => ({\n ...s,\n phase: hasErrors ? \"error\" : \"success\",\n error: hasErrors\n ? `${results.filter((r) => r.status === \"error\").length} file(s) failed`\n : null,\n totalProgress: hasErrors\n ? s.totalProgress\n : {\n loaded: s.totalProgress.total,\n total: s.totalProgress.total,\n percent: 100,\n },\n }));\n\n if (!hasErrors) {\n await opts.onSuccess?.(successResults);\n }\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n opts.onCancel?.();\n setState(INITIAL_STATE);\n return;\n }\n const message = err instanceof Error ? err.message : \"Upload failed\";\n setState((s) => ({ ...s, phase: \"error\", error: message }));\n opts.onError?.(err);\n } finally {\n abortRef.current = null;\n }\n },\n [],\n );\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n const reset = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, upload, cancel, reset };\n}\n","\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport type {\n UploadPhase,\n UploadProgress,\n UploadRequestOptions,\n} from \"./types\";\nimport { useUpload, type UseUploadOptions } from \"./use-upload\";\n\nexport type UseUploadControlsOptions = UseUploadOptions & {\n objectKey: string | ((file: File) => string);\n /** Per-file request options (metadata, bucket, contentType) */\n getUploadOptions?: (file: File) => UploadRequestOptions;\n};\n\nexport type UseUploadControlsReturn = {\n /** Current upload phase */\n phase: UploadPhase;\n /** Upload progress (loaded, total, percent) */\n progress: UploadProgress;\n /** Error message if upload failed */\n error: string | null;\n /** Info about the selected file */\n fileInfo: { name: string; size: number } | null;\n /** Whether an upload is currently in progress */\n isUploading: boolean;\n /** Trigger upload from a FileList (e.g. from a file input or drop event) */\n handleFiles: (files: FileList | null) => void;\n /** Open the native file picker dialog */\n openFilePicker: () => void;\n /** Cancel the current upload */\n cancel: () => void;\n /** Reset to idle state */\n reset: () => void;\n /** Spread on a hidden `<input>` element */\n inputProps: {\n ref: React.RefObject<HTMLInputElement | null>;\n type: \"file\";\n accept?: string;\n hidden: true;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n };\n /** Spread on a container to enable drag-and-drop */\n dropHandlers: {\n onDragOver: (e: React.DragEvent) => void;\n onDrop: (e: React.DragEvent) => void;\n };\n};\n\nexport function useUploadControls(\n options: UseUploadControlsOptions,\n): UseUploadControlsReturn {\n const { objectKey, getUploadOptions, ...hookOptions } = options;\n const ctx = useUpload(hookOptions);\n const inputRef = useRef<HTMLInputElement>(null);\n const [fileInfo, setFileInfo] = useState<{\n name: string;\n size: number;\n } | null>(null);\n\n const resolveKey = (file: File): string =>\n typeof objectKey === \"function\" ? objectKey(file) : objectKey;\n\n const handleFiles = async (files: FileList | null) => {\n const file = files?.[0];\n if (!file) return;\n setFileInfo({ name: file.name, size: file.size });\n await ctx.upload(file, resolveKey(file), getUploadOptions?.(file));\n };\n\n const openFilePicker = () => inputRef.current?.click();\n\n const isUploading = ctx.phase === \"uploading\";\n\n return {\n phase: ctx.phase,\n progress: ctx.progress,\n error: ctx.error,\n fileInfo,\n isUploading,\n handleFiles,\n openFilePicker,\n cancel: ctx.cancel,\n reset: () => {\n ctx.reset();\n setFileInfo(null);\n },\n inputProps: {\n ref: inputRef,\n type: \"file\",\n accept: hookOptions.accept?.join(\",\"),\n hidden: true,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => {\n handleFiles(e.target.files);\n e.target.value = \"\";\n },\n },\n dropHandlers: {\n onDragOver: (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n },\n onDrop: (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (!isUploading) handleFiles(e.dataTransfer.files);\n },\n },\n };\n}\n","\"use client\";\n\nimport { useRef } from \"react\";\nimport type {\n UploadProgress,\n MultiUploadPhase,\n MultiUploadFileState,\n} from \"./types\";\nimport { useMultiUpload, type UseMultiUploadOptions } from \"./use-multi-upload\";\n\nexport type UseMultiUploadControlsOptions = UseMultiUploadOptions & {\n objectKey: (file: File) => string;\n};\n\nexport type UseMultiUploadControlsReturn = {\n /** Current upload phase */\n phase: MultiUploadPhase;\n /** Per-file upload state */\n files: MultiUploadFileState[];\n /** Aggregated progress across all files */\n totalProgress: UploadProgress;\n /** Error message if upload failed */\n error: string | null;\n /** Whether an upload is currently in progress */\n isUploading: boolean;\n /** Trigger upload from a FileList (e.g. from a file input or drop event) */\n handleFiles: (files: FileList | null) => void;\n /** Open the native file picker dialog */\n openFilePicker: () => void;\n /** Cancel all uploads */\n cancel: () => void;\n /** Reset to idle state */\n reset: () => void;\n /** Spread on a hidden `<input>` element */\n inputProps: {\n ref: React.RefObject<HTMLInputElement | null>;\n type: \"file\";\n multiple: true;\n accept?: string;\n hidden: true;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n };\n /** Spread on a container to enable drag-and-drop */\n dropHandlers: {\n onDragOver: (e: React.DragEvent) => void;\n onDrop: (e: React.DragEvent) => void;\n };\n};\n\nexport function useMultiUploadControls(\n options: UseMultiUploadControlsOptions,\n): UseMultiUploadControlsReturn {\n const { objectKey, ...hookOptions } = options;\n const ctx = useMultiUpload(hookOptions);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleFiles = async (files: FileList | null) => {\n if (!files?.length) return;\n await ctx.upload(Array.from(files), objectKey);\n };\n\n const openFilePicker = () => inputRef.current?.click();\n\n const isUploading = ctx.phase === \"uploading\";\n\n return {\n phase: ctx.phase,\n files: ctx.files,\n totalProgress: ctx.totalProgress,\n error: ctx.error,\n isUploading,\n handleFiles,\n openFilePicker,\n cancel: ctx.cancel,\n reset: ctx.reset,\n inputProps: {\n ref: inputRef,\n type: \"file\",\n multiple: true,\n accept: hookOptions.accept?.join(\",\"),\n hidden: true,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => {\n handleFiles(e.target.files);\n e.target.value = \"\";\n },\n },\n dropHandlers: {\n onDragOver: (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n },\n onDrop: (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (!isUploading) handleFiles(e.dataTransfer.files);\n },\n },\n };\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { PresignApi } from \"@better-s3/server\";\nimport type { DownloadPhase, DownloadProgress, DownloadHooks } from \"./types\";\n\nexport type UseDownloadOptions = DownloadHooks & {\n presignApi: PresignApi;\n /**\n * `\"native\"` — browser handles download natively via presigned URL (default)\n * `\"fetch\"` — streams via fetch, enables in-app progress tracking\n */\n mode?: \"native\" | \"fetch\";\n /** Target bucket (overrides server default) */\n bucket?: string;\n};\n\nexport type UseDownloadState = {\n phase: DownloadPhase;\n progress: DownloadProgress;\n error: string | null;\n fileName: string | null;\n fileSize: number | null;\n};\n\nexport type UseDownloadReturn = UseDownloadState & {\n download: (key: string, downloadName?: string) => Promise<void>;\n cancel: () => void;\n reset: () => void;\n};\n\nconst INITIAL_PROGRESS: DownloadProgress = { loaded: 0, total: 0, percent: 0 };\n\nconst INITIAL_STATE: UseDownloadState = {\n phase: \"idle\",\n progress: INITIAL_PROGRESS,\n error: null,\n fileName: null,\n fileSize: null,\n};\n\nexport function useDownload(options: UseDownloadOptions): UseDownloadReturn {\n const [state, setState] = useState<UseDownloadState>(INITIAL_STATE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const abortRef = useRef<AbortController | null>(null);\n\n const download = useCallback(async (key: string, downloadName?: string) => {\n const name = downloadName ?? key.split(\"/\").pop() ?? key;\n const opts = optionsRef.current;\n const mode = opts.mode ?? \"native\";\n\n if (opts.beforeDownload) {\n const allowed = await opts.beforeDownload(key);\n if (!allowed) {\n setState((s) => ({\n ...s,\n phase: \"error\",\n error: \"Download blocked by beforeDownload hook\",\n }));\n opts.onError?.(key, new Error(\"blocked\"), \"presigning\");\n return;\n }\n }\n\n setState({\n phase: \"presigning\",\n progress: INITIAL_PROGRESS,\n error: null,\n fileName: name,\n fileSize: null,\n });\n\n try {\n const { url } = await opts.presignApi.download(key, {\n fileName: name,\n bucket: opts.bucket,\n });\n\n if (mode === \"native\") {\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = name;\n anchor.click();\n setState((s) => ({ ...s, phase: \"success\" }));\n await opts.onSuccess?.(key);\n setState(INITIAL_STATE);\n return;\n }\n\n // ── Fetch mode ───────────────────────────────────────────────\n setState((s) => ({ ...s, phase: \"downloading\" }));\n opts.onDownloadStart?.(key);\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n const res = await fetch(url, { signal: controller.signal });\n if (!res.ok) throw new Error(`Download failed: ${res.statusText}`);\n\n const contentLength = Number(res.headers.get(\"content-length\") || 0);\n setState((s) => ({ ...s, fileSize: contentLength || null }));\n\n const reader = res.body?.getReader();\n if (!reader) throw new Error(\"ReadableStream not supported\");\n\n const chunks: BlobPart[] = [];\n let loaded = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n loaded += value.byteLength;\n const percent =\n contentLength > 0 ? Math.round((loaded / contentLength) * 100) : 0;\n const progress: DownloadProgress = {\n loaded,\n total: contentLength,\n percent,\n };\n setState((s) => ({ ...s, progress }));\n opts.onProgress?.(key, progress);\n }\n\n const blob = new Blob(chunks);\n const blobUrl = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = blobUrl;\n anchor.download = name;\n anchor.click();\n URL.revokeObjectURL(blobUrl);\n\n setState((s) => ({\n ...s,\n phase: \"success\",\n fileSize: blob.size,\n progress: { loaded: blob.size, total: blob.size, percent: 100 },\n }));\n await opts.onSuccess?.(key);\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n opts.onCancel?.(key);\n setState(INITIAL_STATE);\n return;\n }\n const message = err instanceof Error ? err.message : \"Download failed\";\n setState((s) => ({ ...s, phase: \"error\", error: message }));\n opts.onError?.(key, err, \"downloading\");\n } finally {\n abortRef.current = null;\n }\n }, []);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n const reset = useCallback(() => {\n abortRef.current?.abort();\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, download, cancel, reset };\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { PresignApi } from \"@better-s3/server\";\nimport type { DeletePhase, DeleteHooks } from \"./types\";\n\nexport type UseDeleteOptions = DeleteHooks & {\n presignApi: PresignApi;\n /** Target bucket (overrides server default) */\n bucket?: string;\n};\n\nexport type UseDeleteState = {\n phase: DeletePhase;\n error: string | null;\n};\n\nexport type UseDeleteReturn = UseDeleteState & {\n requestDelete: (key: string) => void;\n confirmDelete: () => Promise<void>;\n cancelDelete: () => void;\n reset: () => void;\n pendingKey: string | null;\n};\n\nconst INITIAL_STATE: UseDeleteState = {\n phase: \"idle\",\n error: null,\n};\n\nexport function useDelete(options: UseDeleteOptions): UseDeleteReturn {\n const [state, setState] = useState<UseDeleteState>(INITIAL_STATE);\n const [pendingKey, setPendingKey] = useState<string | null>(null);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const requestDelete = useCallback((key: string) => {\n setPendingKey(key);\n setState({ phase: \"confirming\", error: null });\n }, []);\n\n const confirmDelete = useCallback(async () => {\n if (!pendingKey) return;\n const opts = optionsRef.current;\n\n if (opts.beforeDelete) {\n const allowed = await opts.beforeDelete(pendingKey);\n if (!allowed) {\n setState({\n phase: \"error\",\n error: \"Delete blocked by beforeDelete hook\",\n });\n opts.onError?.(pendingKey, new Error(\"blocked\"), \"confirming\");\n setPendingKey(null);\n return;\n }\n }\n\n setState({ phase: \"deleting\", error: null });\n opts.onDeleteStart?.(pendingKey);\n\n try {\n await opts.presignApi.delete(pendingKey, { bucket: opts.bucket });\n\n setState({ phase: \"success\", error: null });\n await opts.onSuccess?.(pendingKey);\n setPendingKey(null);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Delete failed\";\n setState({ phase: \"error\", error: message });\n opts.onError?.(pendingKey, err, \"deleting\");\n }\n }, [pendingKey]);\n\n const cancelDelete = useCallback(() => {\n setPendingKey(null);\n setState(INITIAL_STATE);\n }, []);\n\n const reset = useCallback(() => {\n setPendingKey(null);\n setState(INITIAL_STATE);\n }, []);\n\n return {\n ...state,\n pendingKey,\n requestDelete,\n confirmDelete,\n cancelDelete,\n reset,\n };\n}\n"]}
@@ -0,0 +1,7 @@
1
+ export type DeletePhase = "idle" | "confirming" | "deleting" | "success" | "error";
2
+ export type DeleteHooks = {
3
+ beforeDelete?: (key: string) => Promise<boolean> | boolean;
4
+ onDeleteStart?: (key: string) => void;
5
+ onSuccess?: (key: string) => Promise<void> | void;
6
+ onError?: (key: string, error: unknown, phase: DeletePhase) => void;
7
+ };
@@ -0,0 +1,14 @@
1
+ export type DownloadPhase = "idle" | "presigning" | "downloading" | "success" | "error";
2
+ export type DownloadProgress = {
3
+ loaded: number;
4
+ total: number;
5
+ percent: number;
6
+ };
7
+ export type DownloadHooks = {
8
+ beforeDownload?: (key: string) => Promise<boolean> | boolean;
9
+ onDownloadStart?: (key: string) => void;
10
+ onProgress?: (key: string, progress: DownloadProgress) => void;
11
+ onSuccess?: (key: string) => Promise<void> | void;
12
+ onError?: (key: string, error: unknown, phase: DownloadPhase) => void;
13
+ onCancel?: (key: string) => void;
14
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./upload";
2
+ export * from "./multi-upload";
3
+ export * from "./download";
4
+ export * from "./delete";
@@ -0,0 +1,21 @@
1
+ import type { UploadProgress, UploadResult } from "./upload";
2
+ export type MultiUploadPhase = "idle" | "validating" | "uploading" | "success" | "error";
3
+ export type MultiUploadFileState = {
4
+ id: string;
5
+ fileName: string;
6
+ fileSize: number;
7
+ status: "pending" | "uploading" | "success" | "error";
8
+ progress: UploadProgress;
9
+ error: string | null;
10
+ };
11
+ export type MultiUploadHooks = {
12
+ beforeUpload?: (files: File[]) => Promise<boolean> | boolean;
13
+ onUploadStart?: (files: File[]) => void;
14
+ onFileProgress?: (file: File, progress: UploadProgress) => void;
15
+ onFileSuccess?: (file: File, result: UploadResult) => void;
16
+ onFileError?: (file: File, error: string) => void;
17
+ onProgress?: (progress: UploadProgress) => void;
18
+ onSuccess?: (results: UploadResult[]) => Promise<void> | void;
19
+ onError?: (error: unknown) => void;
20
+ onCancel?: () => void;
21
+ };
@@ -0,0 +1,36 @@
1
+ export type UploadResult = {
2
+ key: string;
3
+ url?: string;
4
+ eTag?: string;
5
+ };
6
+ export type UploadProgress = {
7
+ loaded: number;
8
+ total: number;
9
+ percent: number;
10
+ };
11
+ export type UploadPhase = "idle" | "validating" | "presigning" | "uploading" | "finalizing" | "success" | "error";
12
+ export type UploadRequestOptions = {
13
+ /** Custom S3 object metadata (x-amz-meta-*) */
14
+ metadata?: Record<string, string>;
15
+ /** Target bucket (overrides server default) */
16
+ bucket?: string;
17
+ /** Override auto-detected content type */
18
+ contentType?: string;
19
+ };
20
+ export type UploadHooks = {
21
+ beforeUpload?: (file: File) => Promise<boolean> | boolean;
22
+ onUploadStart?: (file: File, key: string) => void;
23
+ onProgress?: (file: File, progress: UploadProgress) => void;
24
+ onSuccess?: (file: File, result: UploadResult) => Promise<void> | void;
25
+ onError?: (file: File | null, error: unknown, phase: UploadPhase) => void;
26
+ onCancel?: (file: File | null) => void;
27
+ };
28
+ export type UploadConfig = {
29
+ multipart?: boolean;
30
+ accept?: string[];
31
+ maxFileSize?: number;
32
+ maxFiles?: number;
33
+ multipartThreshold?: number;
34
+ concurrentParts?: number;
35
+ concurrentFiles?: number;
36
+ };
@@ -0,0 +1,6 @@
1
+ export declare const DEFAULT_MULTIPART_THRESHOLD: number;
2
+ export declare const DEFAULT_PART_SIZE: number;
3
+ export declare const MAX_RETRIES = 3;
4
+ export declare const RETRY_BASE_DELAY = 1000;
5
+ export declare const DEFAULT_CONCURRENT_PARTS = 3;
6
+ export declare const DEFAULT_CONCURRENT_FILES = 2;
@@ -0,0 +1,2 @@
1
+ export { uploadFile, type UploadEngineCallbacks } from "./upload-file";
2
+ export { uploadFiles, type FileItem, type FileItemStatus, type MultiUploadCallbacks, } from "./upload-files";
@@ -0,0 +1,3 @@
1
+ import type { UploadProgress, UploadRequestOptions } from "../types";
2
+ import type { PresignApi } from "@better-s3/server";
3
+ export declare function uploadMultipart(presignApi: PresignApi, file: File, objectKey: string, partSize: number, concurrentParts: number, onProgress?: (progress: UploadProgress) => void, signal?: AbortSignal, requestOptions?: UploadRequestOptions): Promise<void>;
@@ -0,0 +1,3 @@
1
+ export declare function uploadPart(blob: Blob, presignedUrl: string, partLoaded: {
2
+ bytes: number;
3
+ }, totalSize: number, reportProgress: () => void, signal?: AbortSignal): Promise<string>;
@@ -0,0 +1 @@
1
+ export declare function withRetry<T>(fn: () => Promise<T>, retries: number, signal?: AbortSignal): Promise<T>;
@@ -0,0 +1,2 @@
1
+ import type { UploadProgress } from "../types";
2
+ export declare function uploadSimple(file: File, presignedUrl: string, onProgress?: (progress: UploadProgress) => void, signal?: AbortSignal): Promise<string | undefined>;
@@ -0,0 +1,6 @@
1
+ import type { UploadConfig, UploadProgress, UploadResult, UploadRequestOptions } from "../types";
2
+ import type { PresignApi } from "@better-s3/server";
3
+ export type UploadEngineCallbacks = {
4
+ onProgress?: (progress: UploadProgress) => void;
5
+ };
6
+ export declare function uploadFile(presignApi: PresignApi, file: File, objectKey: string, config?: UploadConfig, callbacks?: UploadEngineCallbacks, signal?: AbortSignal, requestOptions?: UploadRequestOptions): Promise<UploadResult>;
@@ -0,0 +1,23 @@
1
+ import type { UploadConfig, UploadProgress, UploadResult, UploadRequestOptions } from "../types";
2
+ import type { PresignApi } from "@better-s3/server";
3
+ export type FileItemStatus = "pending" | "uploading" | "success" | "error";
4
+ export type FileItem = {
5
+ id: string;
6
+ file: File;
7
+ objectKey: string;
8
+ status: FileItemStatus;
9
+ progress: UploadProgress;
10
+ result: UploadResult | null;
11
+ error: string | null;
12
+ };
13
+ export type MultiUploadCallbacks = {
14
+ onFileProgress?: (id: string, progress: UploadProgress) => void;
15
+ onFileSuccess?: (id: string, result: UploadResult) => void;
16
+ onFileError?: (id: string, error: string) => void;
17
+ onTotalProgress?: (progress: UploadProgress) => void;
18
+ };
19
+ export declare function uploadFiles(presignApi: PresignApi, items: Array<{
20
+ id: string;
21
+ file: File;
22
+ objectKey: string;
23
+ }>, config?: UploadConfig, callbacks?: MultiUploadCallbacks, signal?: AbortSignal, getRequestOptions?: (file: File) => UploadRequestOptions): Promise<FileItem[]>;
@@ -1,6 +1,9 @@
1
- import type { PresignApi, DeletePhase, DeleteHooks } from "@better-s3/core";
1
+ import type { PresignApi } from "@better-s3/server";
2
+ import type { DeletePhase, DeleteHooks } from "./types";
2
3
  export type UseDeleteOptions = DeleteHooks & {
3
4
  presignApi: PresignApi;
5
+ /** Target bucket (overrides server default) */
6
+ bucket?: string;
4
7
  };
5
8
  export type UseDeleteState = {
6
9
  phase: DeletePhase;
@@ -1,4 +1,5 @@
1
- import type { PresignApi, DownloadPhase, DownloadProgress, DownloadHooks } from "@better-s3/core";
1
+ import type { PresignApi } from "@better-s3/server";
2
+ import type { DownloadPhase, DownloadProgress, DownloadHooks } from "./types";
2
3
  export type UseDownloadOptions = DownloadHooks & {
3
4
  presignApi: PresignApi;
4
5
  /**
@@ -6,6 +7,8 @@ export type UseDownloadOptions = DownloadHooks & {
6
7
  * `"fetch"` — streams via fetch, enables in-app progress tracking
7
8
  */
8
9
  mode?: "native" | "fetch";
10
+ /** Target bucket (overrides server default) */
11
+ bucket?: string;
9
12
  };
10
13
  export type UseDownloadState = {
11
14
  phase: DownloadPhase;
@@ -1,4 +1,4 @@
1
- import type { UploadProgress, MultiUploadPhase, MultiUploadFileState } from "@better-s3/core";
1
+ import type { UploadProgress, MultiUploadPhase, MultiUploadFileState } from "./types";
2
2
  import { type UseMultiUploadOptions } from "./use-multi-upload";
3
3
  export type UseMultiUploadControlsOptions = UseMultiUploadOptions & {
4
4
  objectKey: (file: File) => string;
@@ -1,6 +1,11 @@
1
- import type { PresignApi, UploadConfig, UploadProgress, MultiUploadPhase, MultiUploadFileState, MultiUploadHooks } from "@better-s3/core";
1
+ import type { PresignApi } from "@better-s3/server";
2
+ import type { UploadConfig, UploadProgress, UploadRequestOptions, MultiUploadPhase, MultiUploadFileState, MultiUploadHooks } from "./types";
2
3
  export type UseMultiUploadOptions = UploadConfig & MultiUploadHooks & {
3
4
  presignApi: PresignApi;
5
+ /** Static request options applied to all files */
6
+ uploadOptions?: UploadRequestOptions;
7
+ /** Per-file request options (overrides uploadOptions) */
8
+ getUploadOptions?: (file: File) => UploadRequestOptions;
4
9
  };
5
10
  export type UseMultiUploadState = {
6
11
  phase: MultiUploadPhase;
@@ -1,7 +1,9 @@
1
- import type { UploadPhase, UploadProgress } from "@better-s3/core";
1
+ import type { UploadPhase, UploadProgress, UploadRequestOptions } from "./types";
2
2
  import { type UseUploadOptions } from "./use-upload";
3
3
  export type UseUploadControlsOptions = UseUploadOptions & {
4
4
  objectKey: string | ((file: File) => string);
5
+ /** Per-file request options (metadata, bucket, contentType) */
6
+ getUploadOptions?: (file: File) => UploadRequestOptions;
5
7
  };
6
8
  export type UseUploadControlsReturn = {
7
9
  /** Current upload phase */
@@ -1,4 +1,5 @@
1
- import type { PresignApi, UploadConfig, UploadHooks, UploadPhase, UploadProgress, UploadResult } from "@better-s3/core";
1
+ import type { PresignApi } from "@better-s3/server";
2
+ import type { UploadConfig, UploadHooks, UploadPhase, UploadProgress, UploadResult, UploadRequestOptions } from "./types";
2
3
  export type UseUploadOptions = UploadConfig & UploadHooks & {
3
4
  presignApi: PresignApi;
4
5
  };
@@ -11,7 +12,7 @@ export type UseUploadState = {
11
12
  fileSize: number | null;
12
13
  };
13
14
  export type UseUploadReturn = UseUploadState & {
14
- upload: (file: File, objectKey: string) => Promise<void>;
15
+ upload: (file: File, objectKey: string, requestOptions?: UploadRequestOptions) => Promise<void>;
15
16
  cancel: () => void;
16
17
  reset: () => void;
17
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-s3/react",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "React hooks for S3-compatible file uploads, downloads, and deletes",
5
5
  "keywords": [
6
6
  "s3",
@@ -33,16 +33,16 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@better-s3/core": "1.0.0"
36
+ "@better-s3/server": "2.0.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "react": ">=18"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/react": "^19.0.0",
43
- "react": "^19.0.0",
42
+ "@types/react": "^19.2.14",
43
+ "react": "^19.2.5",
44
44
  "tsup": "^8.5.1",
45
- "typescript": "^5.8.3"
45
+ "typescript": "^6.0.3"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public"