@better-s3/react 2.3.1 → 3.1.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 +23 -12
- package/dist/helpers.d.ts +6 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/upload.d.ts +6 -0
- package/dist/upload/multipart.d.ts +1 -1
- package/dist/upload/simple.d.ts +9 -1
- package/dist/use-download.d.ts +4 -3
- package/dist/use-fetch-download.d.ts +1 -1
- package/dist/use-upload-controls.d.ts +35 -16
- package/package.json +2 -2
- package/dist/use-multi-upload-controls.d.ts +0 -40
package/README.md
CHANGED
|
@@ -25,23 +25,34 @@ const api = createS3Api("/api/s3");
|
|
|
25
25
|
```tsx
|
|
26
26
|
const { phase, progress, error, upload, cancel, reset } = useUpload({
|
|
27
27
|
api,
|
|
28
|
-
accept: ["image/*"],
|
|
29
|
-
maxFileSize: 10 * 1024 * 1024,
|
|
28
|
+
accept: ["image/*", ".pdf"],
|
|
29
|
+
maxFileSize: 10 * 1024 * 1024, // client-side pre-validation (UX)
|
|
30
30
|
onSuccess: (_file, result) => console.log("Uploaded:", result.key),
|
|
31
|
+
onError: (_file, error) => {
|
|
32
|
+
// error.message for size violations:
|
|
33
|
+
// Simple: S3 returns 403 — "Upload failed: 403 Forbidden"
|
|
34
|
+
// Multipart: server returns 422 — "File size (X bytes) exceeds …"
|
|
35
|
+
},
|
|
31
36
|
});
|
|
32
37
|
|
|
33
38
|
await upload(file, `uploads/${file.name}`, { metadata: { source: "web" } });
|
|
34
39
|
```
|
|
35
40
|
|
|
36
|
-
**Phases:** `idle → validating →
|
|
41
|
+
**Phases:** `idle → validating → uploading → success | error`
|
|
42
|
+
|
|
43
|
+
> `maxFileSize` performs a client-side check before the upload starts (good UX). The real enforcement is server-side:
|
|
44
|
+
>
|
|
45
|
+
> - **Simple uploads** — S3 enforces exact file size via a signed `content-length-range` policy. The client cannot upload a different-sized file with that URL.
|
|
46
|
+
> - **Multipart uploads** — Server verifies via `HeadObject` after `CompleteMultipartUpload` and deletes the object if the limit is exceeded.
|
|
37
47
|
|
|
38
48
|
### `useUploadControls` — Upload with file picker & drag-drop
|
|
39
49
|
|
|
40
50
|
```tsx
|
|
41
|
-
const { phase, progress, openFilePicker, inputProps, dropHandlers } =
|
|
51
|
+
const { mode, phase, progress, openFilePicker, inputProps, dropHandlers } =
|
|
42
52
|
useUploadControls({
|
|
43
53
|
api,
|
|
44
54
|
objectKey: (file) => `uploads/${file.name}`,
|
|
55
|
+
maxFiles: 5, // > 1 → switches to multi-upload mode automatically
|
|
45
56
|
});
|
|
46
57
|
```
|
|
47
58
|
|
|
@@ -57,17 +68,10 @@ const { phase, files, totalProgress, upload, cancel } = useMultiUpload({
|
|
|
57
68
|
await upload(selectedFiles, (file) => `uploads/${file.name}`);
|
|
58
69
|
```
|
|
59
70
|
|
|
60
|
-
### `useMultiUploadControls` — Batch upload with file picker
|
|
61
|
-
|
|
62
|
-
Same as `useUploadControls` but for multiple files.
|
|
63
|
-
|
|
64
71
|
### `useDownload` — File download
|
|
65
72
|
|
|
66
73
|
```tsx
|
|
67
|
-
const { phase, error, download, reset } = useDownload({
|
|
68
|
-
api,
|
|
69
|
-
});
|
|
70
|
-
|
|
74
|
+
const { phase, error, download, reset } = useDownload({ api });
|
|
71
75
|
download("uploads/photo.jpg", "photo.jpg");
|
|
72
76
|
```
|
|
73
77
|
|
|
@@ -81,6 +85,13 @@ requestDelete("uploads/photo.jpg"); // phase → "confirming"
|
|
|
81
85
|
confirmDelete(); // phase → "deleting" → "success"
|
|
82
86
|
```
|
|
83
87
|
|
|
88
|
+
## Upload modes
|
|
89
|
+
|
|
90
|
+
| Mode | When used | Size enforcement |
|
|
91
|
+
| --------- | ------------------------------------------------ | --------------------------------------------------------- |
|
|
92
|
+
| Simple | `file.size < multipartThreshold` (default 50 MB) | S3 presigned POST policy — exact `content-length-range` |
|
|
93
|
+
| Multipart | `multipart: true` or file ≥ threshold | Server `HeadObject` check after `CompleteMultipartUpload` |
|
|
94
|
+
|
|
84
95
|
## License
|
|
85
96
|
|
|
86
97
|
MIT
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses the filename from a `Content-Disposition` header value.
|
|
3
|
+
* Prefers `filename*` (RFC 5987, full Unicode) over `filename`.
|
|
4
|
+
* Returns `fallback` if no filename is found.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseContentDispositionFilename(header: string | null, fallback: string): string;
|
|
1
7
|
export declare function formatFileSize(bytes: number): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
2
|
export { formatFileSize } from "./helpers";
|
|
3
3
|
export { uploadFile, uploadFiles, type UploadEngineCallbacks, type FileItem, type FileItemStatus, type MultiUploadCallbacks, } from "./upload";
|
|
4
|
-
export { createS3Api, createS3Api as createPresignApi, validateFile, type S3Api, type S3Api as PresignApi, type PresignResponse, type MultipartInitResponse, type MultipartPartResponse, } from "@better-s3/server";
|
|
4
|
+
export { createS3Api, createS3Api as createPresignApi, validateFile, type S3Api, type S3Api as PresignApi, type PresignResponse, type PresignedPostResponse, type MultipartInitResponse, type MultipartPartResponse, type UploadConfirmResponse, } from "@better-s3/server";
|
|
5
5
|
export { useUpload, type UseUploadOptions, type UseUploadState, type UseUploadReturn, } from "./use-upload";
|
|
6
6
|
export { useMultiUpload, type UseMultiUploadOptions, type UseMultiUploadState, type UseMultiUploadReturn, } from "./use-multi-upload";
|
|
7
7
|
export { useUploadControls, type UseUploadControlsOptions, type UseUploadControlsReturn, } from "./use-upload-controls";
|
|
8
|
-
export { useMultiUploadControls, type UseMultiUploadControlsOptions, type UseMultiUploadControlsReturn, } from "./use-multi-upload-controls";
|
|
9
8
|
export { useDownload, type DownloadPhase, type DownloadHooks, type UseDownloadOptions, type UseDownloadState, type UseDownloadReturn, } from "./use-download";
|
|
10
9
|
export { useFetchDownload, type FetchDownloadPhase, type FetchDownloadProgress, type FetchDownloadHooks, type UseFetchDownloadOptions, type UseFetchDownloadState, type UseFetchDownloadReturn, } from "./use-fetch-download";
|
|
11
10
|
export { useDelete, type UseDeleteOptions, type UseDeleteState, type UseDeleteReturn, } from "./use-delete";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {validateFile}from'@better-s3/server';export{createS3Api as createPresignApi,createS3Api,validateFile}from'@better-s3/server';import {useState,useRef,useCallback}from'react';function J(i){if(i===0)return "0 B";let d=["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)} ${d[e]}`}async function v(i,d,e){let r;for(let n=0;n<=d;n++)try{return await i()}catch(u){if(u.name==="AbortError")throw u;if(r=u,n<d){let s=1e3*2**n;if(await new Promise(c=>setTimeout(c,s)),e?.aborted)throw new DOMException("Upload aborted","AbortError")}}throw r}function K(i,d,e,r){return new Promise((n,u)=>{let s=new XMLHttpRequest,c=()=>{s.abort(),u(new DOMException("Upload aborted","AbortError"));};r?.addEventListener("abort",c,{once:true}),s.upload.addEventListener("progress",t=>{t.lengthComputable&&e?.({loaded:t.loaded,total:t.total,percent:Math.round(t.loaded/t.total*100)});}),s.addEventListener("load",()=>{if(r?.removeEventListener("abort",c),s.status>=200&&s.status<300){e?.({loaded:i.size,total:i.size,percent:100});let t=s.getResponseHeader("ETag")?.replace(/"/g,"");n(t??void 0);}else u(new Error(`Upload failed: ${s.status} ${s.statusText}`));}),s.addEventListener("error",()=>{r?.removeEventListener("abort",c),u(new Error("Upload failed: network error"));}),s.addEventListener("abort",()=>{r?.removeEventListener("abort",c),u(new DOMException("Upload aborted","AbortError"));}),s.open("PUT",d),s.setRequestHeader("Content-Type",i.type||"application/octet-stream"),s.send(i);})}function $(i,d,e,r,n,u){return new Promise((s,c)=>{let t=new XMLHttpRequest,f=()=>{t.abort(),c(new DOMException("Upload aborted","AbortError"));};u?.addEventListener("abort",f,{once:true}),t.upload.addEventListener("progress",g=>{g.lengthComputable&&(e.bytes=g.loaded,n());}),t.addEventListener("load",()=>{if(u?.removeEventListener("abort",f),t.status>=200&&t.status<300){e.bytes=i.size,n();let g=t.getResponseHeader("ETag")??"";s(g);}else c(new Error(`Part upload failed: ${t.status}`));}),t.addEventListener("error",()=>{u?.removeEventListener("abort",f),c(new Error("Part upload failed: network error"));}),t.addEventListener("abort",()=>{u?.removeEventListener("abort",f),c(new DOMException("Upload aborted","AbortError"));}),t.open("PUT",d),t.send(i);})}async function B(i,d,e,r,n,u,s,c){let t=c?.contentType??d.type,{uploadId:f,key:g}=await i.multipart.init({key:e,contentType:t,metadata:c?.metadata,bucket:c?.bucket,acl:c?.acl}),o=Math.ceil(d.size/r),l=[],a=Array.from({length:o},()=>({bytes:0})),p=()=>{let U=a.reduce((m,h)=>m+h.bytes,0);u?.({loaded:U,total:d.size,percent:Math.round(U/d.size*100)});};try{for(let U=0;U<o;U+=n){if(s?.aborted)throw new DOMException("Upload aborted","AbortError");let m=Math.min(U+n,o),h=[];for(let b=U;b<m;b++){let w=b*r,S=Math.min(w+r,d.size),E=d.slice(w,S),y=b+1;h.push(v(async()=>{let{presignedUrl:T}=await i.multipart.signPart({key:g,uploadId:f,partNumber:y,bucket:c?.bucket});a[b].bytes=0;let R=await $(E,T,a[b],d.size,p,s);return {partNumber:y,eTag:R.replace(/"/g,"")}},3,s));}let P=await Promise.all(h);l.push(...P);}l.sort((U,m)=>U.partNumber-m.partNumber),await i.multipart.complete({key:g,uploadId:f,parts:l,bucket:c?.bucket}),u?.({loaded:d.size,total:d.size,percent:100});}catch(U){throw i.multipart.abort({key:g,uploadId:f,bucket:c?.bucket}).catch(()=>{}),U}}async function D(i,d,e,r={},n={},u,s){let c=r.multipartThreshold??52428800,t=r.multipart===true&&d.size>=c,f=r.concurrentParts??3,g=s?.contentType??d.type,o;return t?await B(i,d,e,10485760,f,n.onProgress,u,s):(o=await v(async()=>{let l=await i.upload({key:e,contentType:g,metadata:s?.metadata,bucket:s?.bucket,acl:s?.acl});return K(d,l.url,n.onProgress,u)},3,u),await i.confirm({key:e,bucket:s?.bucket})),{key:e,eTag:o}}async function M(i,d,e={},r={},n,u){let s=d.map(l=>({...l,status:"pending",progress:{loaded:0,total:l.file.size,percent:0},result:null,error:null})),c=()=>{let l=s.reduce((p,U)=>p+U.progress.loaded,0),a=s.reduce((p,U)=>p+U.progress.total,0);r.onTotalProgress?.({loaded:l,total:a,percent:a>0?Math.round(l/a*100):0});},t=0,f=async()=>{for(;t<s.length;){if(n?.aborted)return;let l=t++,a=s[l];a.status="uploading";try{let p=await D(i,a.file,a.objectKey,e,{onProgress:U=>{a.progress=U,r.onFileProgress?.(a.id,U),c();}},n,u?.(a.file));a.status="success",a.result=p,a.progress={loaded:a.file.size,total:a.file.size,percent:100},r.onFileSuccess?.(a.id,p),c();}catch(p){if(p.name==="AbortError"){a.status="error",a.error="Upload cancelled";return}let U=p instanceof Error?p.message:"Upload failed";a.status="error",a.error=U,r.onFileError?.(a.id,U),c();}}},g=e.concurrentFiles??2,o=Array.from({length:Math.min(g,d.length)},()=>f());return await Promise.all(o),s}var ee={loaded:0,total:0,percent:0},F={phase:"idle",progress:ee,error:null,result:null,fileName:null,fileSize:null};function O(i){let[d,e]=useState(F),r=useRef(i);r.current=i;let n=useRef(null),u=useCallback(async(t,f,g)=>{e({...F,phase:"validating",fileName:t.name,fileSize:t.size});let o=r.current,l=validateFile(t,{accept:o.accept,maxFileSize:o.maxFileSize});if(l){e(p=>({...p,phase:"error",error:l})),o.onError?.(t,new Error(l),"validating");return}if(o.beforeUpload&&!await o.beforeUpload(t)){e(U=>({...U,phase:"error",error:"Upload blocked by beforeUpload hook"})),o.onError?.(t,new Error("blocked"),"validating");return}e(p=>({...p,phase:"uploading"})),o.onUploadStart?.(t,f);let a=new AbortController;n.current=a;try{let p=await D(o.api,t,f,{multipart:o.multipart,multipartThreshold:o.multipartThreshold,concurrentParts:o.concurrentParts},{onProgress:U=>{e(m=>({...m,progress:U})),o.onProgress?.(t,U);}},a.signal,g);e(U=>({...U,phase:"success",result:p,progress:{loaded:t.size,total:t.size,percent:100}})),await o.onSuccess?.(t,p);}catch(p){if(p.name==="AbortError"){o.onCancel?.(t),e(F);return}let U=p instanceof Error?p.message:"Upload failed";e(m=>({...m,phase:"error",error:U})),o.onError?.(t,p,"uploading");}finally{n.current=null;}},[]),s=useCallback(()=>{n.current?.abort(),e(F);},[]),c=useCallback(()=>{n.current?.abort(),e(F);},[]);return {...d,upload:u,cancel:s,reset:c}}var re={loaded:0,total:0,percent:0},k={phase:"idle",files:[],totalProgress:re,error:null},se=0;function ne(){return `file-${++se}`}function z(i){let[d,e]=useState(k),r=useRef(i);r.current=i;let n=useRef(null),u=useRef(new Map),s=useCallback(async(f,g)=>{let o=r.current,l=[],a=[],p=new Map;if(e(m=>({...m,phase:"validating",error:null})),o.maxFiles&&f.length>o.maxFiles){let m=`Too many files. Maximum is ${o.maxFiles}.`;e(h=>({...h,phase:"error",error:m})),o.onError?.(new Error(m));return}for(let m of f){let h=validateFile(m,{accept:o.accept,maxFileSize:o.maxFileSize});if(h){let P=`${m.name}: ${h}`;e(b=>({...b,phase:"error",error:P})),o.onError?.(new Error(P));return}}if(o.beforeUpload&&!await o.beforeUpload(f)){e(h=>({...h,phase:"error",error:"Upload blocked by beforeUpload hook"})),o.onError?.(new Error("blocked"));return}for(let m of f){let h=ne(),P=g(m);l.push({id:h,file:m,objectKey:P}),p.set(h,m),a.push({id:h,fileName:m.name,fileSize:m.size,status:"pending",progress:{loaded:0,total:m.size,percent:0},error:null});}u.current=p,e({phase:"uploading",files:a,totalProgress:{loaded:0,total:f.reduce((m,h)=>m+h.size,0),percent:0},error:null}),o.onUploadStart?.(f);let U=new AbortController;n.current=U;try{let m=await M(o.api,l,{multipart:o.multipart,multipartThreshold:o.multipartThreshold,concurrentParts:o.concurrentParts,concurrentFiles:o.concurrentFiles},{onFileProgress:(b,w)=>{e(E=>({...E,files:E.files.map(y=>y.id===b?{...y,status:"uploading",progress:w}:y)}));let S=p.get(b);S&&o.onFileProgress?.(S,w);},onFileSuccess:(b,w)=>{e(E=>({...E,files:E.files.map(y=>y.id===b?{...y,status:"success",progress:{loaded:y.fileSize,total:y.fileSize,percent:100}}:y)}));let S=p.get(b);S&&o.onFileSuccess?.(S,w);},onFileError:(b,w)=>{e(E=>({...E,files:E.files.map(y=>y.id===b?{...y,status:"error",error:w}:y)}));let S=p.get(b);S&&o.onFileError?.(S,w);},onTotalProgress:b=>{e(w=>({...w,totalProgress:b})),o.onProgress?.(b);}},U.signal,b=>{let w=o.getUploadOptions?.(b);return o.uploadOptions?{...o.uploadOptions,...w}:w??{}}),h=m.some(b=>b.status==="error"),P=m.filter(b=>b.result!==null).map(b=>b.result);e(b=>({...b,phase:h?"error":"success",error:h?`${m.filter(w=>w.status==="error").length} file(s) failed`:null,totalProgress:h?b.totalProgress:{loaded:b.totalProgress.total,total:b.totalProgress.total,percent:100}})),h||await o.onSuccess?.(P);}catch(m){if(m.name==="AbortError"){o.onCancel?.(),e(k);return}let h=m instanceof Error?m.message:"Upload failed";e(P=>({...P,phase:"error",error:h})),o.onError?.(m);}finally{n.current=null;}},[]),c=useCallback(()=>{n.current?.abort(),e(k);},[]),t=useCallback(()=>{n.current?.abort(),e(k);},[]);return {...d,upload:s,cancel:c,reset:t}}function ie(i){let{objectKey:d,getUploadOptions:e,...r}=i,n=O(r),u=useRef(null),[s,c]=useState(null),t=l=>typeof d=="function"?d(l):d,f=async l=>{let a=l?.[0];a&&(c({name:a.name,size:a.size}),await n.upload(a,t(a),e?.(a)));},g=()=>u.current?.click(),o=n.phase==="uploading";return {phase:n.phase,progress:n.progress,error:n.error,fileInfo:s,isUploading:o,handleFiles:f,openFilePicker:g,cancel:n.cancel,reset:()=>{n.reset(),c(null);},inputProps:{ref:u,type:"file",accept:r.accept?.join(","),hidden:true,onChange:l=>{f(l.target.files),l.target.value="";}},dropHandlers:{onDragOver:l=>{l.preventDefault(),l.stopPropagation();},onDrop:l=>{l.preventDefault(),l.stopPropagation(),o||f(l.dataTransfer.files);}}}}function de(i){let{objectKey:d,...e}=i,r=z(e),n=useRef(null),u=async t=>{t?.length&&await r.upload(Array.from(t),d);},s=()=>n.current?.click(),c=r.phase==="uploading";return {phase:r.phase,files:r.files,totalProgress:r.totalProgress,error:r.error,isUploading:c,handleFiles:u,openFilePicker:s,cancel:r.cancel,reset:r.reset,inputProps:{ref:n,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 N={phase:"idle",error:null,fileName:null};function ge(i){let[d,e]=useState(N),r=useRef(i);r.current=i;let n=useCallback(async(s,c)=>{let t=c??s.split("/").pop()??s,f=r.current;if(f.beforeDownload&&!await f.beforeDownload(s)){e({phase:"error",error:"Download blocked by beforeDownload hook",fileName:t}),f.onError?.(s,new Error("blocked"));return}e({phase:"downloading",error:null,fileName:t});try{let{url:g}=await f.api.download(s,{fileName:t,bucket:f.bucket}),o=await fetch(g);if(!o.ok)throw new Error(o.status===404?"File not found":`Download failed (${o.status})`);let l=await o.blob(),a=URL.createObjectURL(l),p=document.createElement("a");p.href=a,p.download=t,p.click(),URL.revokeObjectURL(a),e({phase:"success",error:null,fileName:t}),await f.onSuccess?.(s),e(N);}catch(g){let o=g instanceof Error?g.message:"Download failed";e({phase:"error",error:o,fileName:t}),f.onError?.(s,g);}},[]),u=useCallback(()=>{e(N);},[]);return {...d,download:n,reset:u}}var X={loaded:0,total:0,percent:0},x={phase:"idle",progress:X,error:null,fileName:null,fileSize:null};function fe(i){let[d,e]=useState(x),r=useRef(i);r.current=i;let n=useRef(null),u=useCallback(async(t,f)=>{let g=f??t.split("/").pop()??t,o=r.current;if(o.beforeDownload&&!await o.beforeDownload(t)){e(a=>({...a,phase:"error",error:"Download blocked by beforeDownload hook"})),o.onError?.(t,new Error("blocked"),"presigning");return}e({phase:"presigning",progress:X,error:null,fileName:g,fileSize:null});try{let{url:l}=await o.api.download(t,{fileName:g,bucket:o.bucket});e(E=>({...E,phase:"downloading"})),o.onDownloadStart?.(t);let a=new AbortController;n.current=a;let p=await fetch(l,{signal:a.signal});if(!p.ok)throw new Error(p.status===404?"File not found":`Download failed (${p.status})`);let U=Number(p.headers.get("content-length")||0);e(E=>({...E,fileSize:U||null}));let m=p.body?.getReader();if(!m)throw new Error("ReadableStream not supported");let h=[],P=0;for(;;){let{done:E,value:y}=await m.read();if(E)break;h.push(y),P+=y.byteLength;let T=U>0?Math.round(P/U*100):0,R={loaded:P,total:U,percent:T};e(Z=>({...Z,progress:R})),o.onProgress?.(t,R);}let b=new Blob(h),w=URL.createObjectURL(b),S=document.createElement("a");S.href=w,S.download=g,S.click(),URL.revokeObjectURL(w),e(E=>({...E,phase:"success",fileSize:b.size,progress:{loaded:b.size,total:b.size,percent:100}})),await o.onSuccess?.(t);}catch(l){if(l.name==="AbortError"){o.onCancel?.(t),e(x);return}let a=l instanceof Error?l.message:"Download failed";e(p=>({...p,phase:"error",error:a})),o.onError?.(t,l,"downloading");}finally{n.current=null;}},[]),s=useCallback(()=>{n.current?.abort(),e(x);},[]),c=useCallback(()=>{n.current?.abort(),e(x);},[]);return {...d,download:u,cancel:s,reset:c}}var _={phase:"idle",error:null};function be(i){let[d,e]=useState(_),[r,n]=useState(null),u=useRef(i);u.current=i;let s=useCallback(g=>{n(g),e({phase:"confirming",error:null});},[]),c=useCallback(async()=>{if(!r)return;let g=u.current;if(g.beforeDelete&&!await g.beforeDelete(r)){e({phase:"error",error:"Delete blocked by beforeDelete hook"}),g.onError?.(r,new Error("blocked"),"confirming"),n(null);return}e({phase:"deleting",error:null}),g.onDeleteStart?.(r);try{await g.api.delete(r,{bucket:g.bucket}),e({phase:"success",error:null}),await g.onSuccess?.(r),n(null);}catch(o){let l=o instanceof Error?o.message:"Delete failed";e({phase:"error",error:l}),g.onError?.(r,o,"deleting");}},[r]),t=useCallback(()=>{n(null),e(_);},[]),f=useCallback(()=>{n(null),e(_);},[]);return {...d,pendingKey:r,requestDelete:s,confirmDelete:c,cancelDelete:t,reset:f}}export{J as formatFileSize,D as uploadFile,M as uploadFiles,be as useDelete,ge as useDownload,fe as useFetchDownload,z as useMultiUpload,de as useMultiUploadControls,O as useUpload,ie as useUploadControls};//# sourceMappingURL=index.js.map
|
|
1
|
+
import {validateFile}from'@better-s3/server';export{createS3Api as createPresignApi,createS3Api,validateFile}from'@better-s3/server';import {useState,useRef,useCallback}from'react';function $(e,n){if(!e)return n;let o=e.match(/filename\*=UTF-8''([^;,\s]+)/i);if(o)try{return decodeURIComponent(o[1])}catch{}let p=e.match(/filename="([^"]+)"/i);return p?p[1]:n}function W(e){if(e===0)return "0 B";let n=["B","KB","MB","GB","TB"],o=Math.floor(Math.log(e)/Math.log(1024));return `${(e/Math.pow(1024,o)).toFixed(o===0?0:1)} ${n[o]}`}async function x(e,n,o){let p;for(let l=0;l<=n;l++)try{return await e()}catch(g){if(g.name==="AbortError")throw g;if(p=g,l<n){let c=1e3*2**l;if(await new Promise(i=>setTimeout(i,c)),o?.aborted)throw new DOMException("Upload aborted","AbortError")}}throw p}function B(e,n,o,p,l){return new Promise((g,c)=>{let i=new XMLHttpRequest,r=()=>{i.abort(),c(new DOMException("Upload aborted","AbortError"));};l?.addEventListener("abort",r,{once:true}),i.upload.addEventListener("progress",a=>{a.lengthComputable&&p?.({loaded:a.loaded,total:a.total,percent:Math.round(a.loaded/a.total*100)});}),i.addEventListener("load",()=>{l?.removeEventListener("abort",r),i.status>=200&&i.status<300?(p?.({loaded:e.size,total:e.size,percent:100}),g()):c(new Error(`Upload failed: ${i.status} ${i.statusText}`));}),i.addEventListener("error",()=>{l?.removeEventListener("abort",r),c(new Error("Upload failed: network error"));}),i.addEventListener("abort",()=>{l?.removeEventListener("abort",r),c(new DOMException("Upload aborted","AbortError"));});let m=new FormData;for(let[a,t]of Object.entries(o))m.append(a,t);m.append("file",e),i.open("POST",n),i.send(m);})}function j(e,n,o,p,l,g){return new Promise((c,i)=>{let r=new XMLHttpRequest,m=()=>{r.abort(),i(new DOMException("Upload aborted","AbortError"));};g?.addEventListener("abort",m,{once:true}),r.upload.addEventListener("progress",a=>{a.lengthComputable&&(o.bytes=a.loaded,l());}),r.addEventListener("load",()=>{if(g?.removeEventListener("abort",m),r.status>=200&&r.status<300){o.bytes=e.size,l();let a=r.getResponseHeader("ETag")??"";c(a);}else i(new Error(`Part upload failed: ${r.status}`));}),r.addEventListener("error",()=>{g?.removeEventListener("abort",m),i(new Error("Part upload failed: network error"));}),r.addEventListener("abort",()=>{g?.removeEventListener("abort",m),i(new DOMException("Upload aborted","AbortError"));}),r.open("PUT",n),r.send(e);})}async function q(e,n,o,p,l,g,c,i){let r=i?.contentType??n.type,{uploadId:m,key:a}=await e.multipart.init({key:o,contentType:r,fileSize:n.size,fileName:i?.fileName!==null?i?.fileName??n.name:void 0,metadata:i?.metadata,bucket:i?.bucket,acl:i?.acl}),t=Math.ceil(n.size/p),f=[],s=Array.from({length:t},()=>({bytes:0})),d=()=>{let U=s.reduce((u,h)=>u+h.bytes,0);g?.({loaded:U,total:n.size,percent:Math.round(U/n.size*100)});};try{for(let u=0;u<t;u+=l){if(c?.aborted)throw new DOMException("Upload aborted","AbortError");let h=Math.min(u+l,t),S=[];for(let y=u;y<h;y++){let P=y*p,F=Math.min(P+p,n.size),w=n.slice(P,F),D=y+1;S.push(x(async()=>{let{presignedUrl:O}=await e.multipart.signPart({key:a,uploadId:m,partNumber:D,bucket:i?.bucket});s[y].bytes=0;let v=await j(w,O,s[y],n.size,d,c);return {partNumber:D,eTag:v.replace(/"/g,"")}},3,c));}let b=await Promise.all(S);f.push(...b);}f.sort((u,h)=>u.partNumber-h.partNumber);let U=await e.multipart.complete({key:a,uploadId:m,parts:f,bucket:i?.bucket});return g?.({loaded:n.size,total:n.size,percent:100}),U.eTag}catch(U){throw e.multipart.abort({key:a,uploadId:m,bucket:i?.bucket}).catch(()=>{}),U}}async function E(e,n,o,p={},l={},g,c){let i=p.multipartThreshold??52428800,r=p.multipart===true&&n.size>=i,m=p.concurrentParts??3,a=c?.contentType??n.type,t;return r?t=await q(e,n,o,10485760,m,l.onProgress,g,c):(await x(async()=>{let s=await e.upload({key:o,contentType:a,fileSize:n.size,fileName:c?.fileName!==null?c?.fileName??n.name:void 0,metadata:c?.metadata,bucket:c?.bucket,acl:c?.acl});await B(n,s.url,s.fields,l.onProgress,g);},3,g),t=(await e.confirm({key:o,bucket:c?.bucket})).eTag),{key:o,eTag:t}}async function A(e,n,o={},p={},l,g){let c=n.map(f=>({...f,status:"pending",progress:{loaded:0,total:f.file.size,percent:0},result:null,error:null})),i=()=>{let f=c.reduce((d,U)=>d+U.progress.loaded,0),s=c.reduce((d,U)=>d+U.progress.total,0);p.onTotalProgress?.({loaded:f,total:s,percent:s>0?Math.round(f/s*100):0});},r=0,m=async()=>{for(;r<c.length;){if(l?.aborted)return;let f=r++,s=c[f];s.status="uploading";try{let d=await E(e,s.file,s.objectKey,o,{onProgress:U=>{s.progress=U,p.onFileProgress?.(s.id,U),i();}},l,g?.(s.file));s.status="success",s.result=d,s.progress={loaded:s.file.size,total:s.file.size,percent:100},p.onFileSuccess?.(s.id,d),i();}catch(d){if(d.name==="AbortError"){s.status="error",s.error="Upload cancelled";return}let U=d instanceof Error?d.message:"Upload failed";s.status="error",s.error=U,p.onFileError?.(s.id,U),i();}}},a=o.concurrentFiles??2,t=Array.from({length:Math.min(a,n.length)},()=>m());return await Promise.all(t),c}var re={loaded:0,total:0,percent:0},R={phase:"idle",progress:re,error:null,result:null,fileName:null,fileSize:null};function I(e){let[n,o]=useState(R),p=useRef(e);p.current=e;let l=useRef(null),g=useCallback(async(r,m,a)=>{o({...R,phase:"validating",fileName:r.name,fileSize:r.size});let t=p.current,f=validateFile(r,{accept:t.accept,maxFileSize:t.maxFileSize});if(f){o(d=>({...d,phase:"error",error:f})),t.onError?.(r,new Error(f),"validating");return}if(t.beforeUpload&&!await t.beforeUpload(r)){o(U=>({...U,phase:"error",error:"Upload blocked by beforeUpload hook"})),t.onError?.(r,new Error("blocked"),"validating");return}o(d=>({...d,phase:"uploading"})),t.onUploadStart?.(r,m);let s=new AbortController;l.current=s;try{let d=await E(t.api,r,m,{multipart:t.multipart,multipartThreshold:t.multipartThreshold,concurrentParts:t.concurrentParts},{onProgress:U=>{o(u=>({...u,progress:U})),t.onProgress?.(r,U);}},s.signal,a);o(U=>({...U,phase:"success",result:d,progress:{loaded:r.size,total:r.size,percent:100}})),await t.onSuccess?.(r,d);}catch(d){if(d.name==="AbortError"){t.onCancel?.(r),o(R);return}let U=d instanceof Error?d.message:"Upload failed";o(u=>({...u,phase:"error",error:U})),t.onError?.(r,d,"uploading");}finally{l.current=null;}},[]),c=useCallback(()=>{l.current?.abort(),o(R);},[]),i=useCallback(()=>{l.current?.abort(),o(R);},[]);return {...n,upload:g,cancel:c,reset:i}}var le={loaded:0,total:0,percent:0},k={phase:"idle",files:[],totalProgress:le,error:null},ae=0;function ie(){return `file-${++ae}`}function N(e){let[n,o]=useState(k),p=useRef(e);p.current=e;let l=useRef(null),g=useRef(new Map),c=useCallback(async(m,a)=>{let t=p.current,f=[],s=[],d=new Map;if(o(u=>({...u,phase:"validating",error:null})),t.maxFiles&&m.length>t.maxFiles){let u=`Too many files. Maximum is ${t.maxFiles}.`;o(h=>({...h,phase:"error",error:u})),t.onError?.(new Error(u));return}for(let u of m){let h=validateFile(u,{accept:t.accept,maxFileSize:t.maxFileSize});if(h){let S=`${u.name}: ${h}`;o(b=>({...b,phase:"error",error:S})),t.onError?.(new Error(S));return}}if(t.beforeUpload&&!await t.beforeUpload(m)){o(h=>({...h,phase:"error",error:"Upload blocked by beforeUpload hook"})),t.onError?.(new Error("blocked"));return}for(let u of m){let h=ie(),S=a(u);f.push({id:h,file:u,objectKey:S}),d.set(h,u),s.push({id:h,fileName:u.name,fileSize:u.size,status:"pending",progress:{loaded:0,total:u.size,percent:0},error:null});}g.current=d,o({phase:"uploading",files:s,totalProgress:{loaded:0,total:m.reduce((u,h)=>u+h.size,0),percent:0},error:null}),t.onUploadStart?.(m);let U=new AbortController;l.current=U;try{let u=await A(t.api,f,{multipart:t.multipart,multipartThreshold:t.multipartThreshold,concurrentParts:t.concurrentParts,concurrentFiles:t.concurrentFiles},{onFileProgress:(b,y)=>{o(F=>({...F,files:F.files.map(w=>w.id===b?{...w,status:"uploading",progress:y}:w)}));let P=d.get(b);P&&t.onFileProgress?.(P,y);},onFileSuccess:(b,y)=>{o(F=>({...F,files:F.files.map(w=>w.id===b?{...w,status:"success",progress:{loaded:w.fileSize,total:w.fileSize,percent:100}}:w)}));let P=d.get(b);P&&t.onFileSuccess?.(P,y);},onFileError:(b,y)=>{o(F=>({...F,files:F.files.map(w=>w.id===b?{...w,status:"error",error:y}:w)}));let P=d.get(b);P&&t.onFileError?.(P,y);},onTotalProgress:b=>{o(y=>({...y,totalProgress:b})),t.onProgress?.(b);}},U.signal,b=>{let y=t.getUploadOptions?.(b);return t.uploadOptions?{...t.uploadOptions,...y}:y??{}}),h=u.some(b=>b.status==="error"),S=u.filter(b=>b.result!==null).map(b=>b.result);o(b=>({...b,phase:h?"error":"success",error:h?`${u.filter(y=>y.status==="error").length} file(s) failed`:null,totalProgress:h?b.totalProgress:{loaded:b.totalProgress.total,total:b.totalProgress.total,percent:100}})),h||await t.onSuccess?.(S);}catch(u){if(u.name==="AbortError"){t.onCancel?.(),o(k);return}let h=u instanceof Error?u.message:"Upload failed";o(S=>({...S,phase:"error",error:h})),t.onError?.(u);}finally{l.current=null;}},[]),i=useCallback(()=>{l.current?.abort(),o(k);},[]),r=useCallback(()=>{l.current?.abort(),o(k);},[]);return {...n,upload:c,cancel:i,reset:r}}var X={loaded:0,total:0,percent:0};function ce(e){let n=(e.maxFiles??1)>1,o={api:e.api,accept:e.accept,maxFileSize:e.maxFileSize,multipart:e.multipart,multipartThreshold:e.multipartThreshold,concurrentParts:e.concurrentParts,beforeUpload:e.beforeUpload,onUploadStart:e.onUploadStart,onProgress:e.onProgress,onSuccess:e.onSuccess,onError:e.onError,onCancel:e.onCancel},p={api:e.api,accept:e.accept,maxFileSize:e.maxFileSize,maxFiles:e.maxFiles,multipart:e.multipart,multipartThreshold:e.multipartThreshold,concurrentParts:e.concurrentParts,concurrentFiles:e.concurrentFiles,uploadOptions:e.uploadOptions,getUploadOptions:e.getUploadOptions,beforeUpload:e.beforeUpload,onUploadStart:e.onUploadStart,onProgress:e.onProgress,onSuccess:e.onSuccess,onError:e.onError,onCancel:e.onCancel,onFileProgress:e.onFileProgress,onFileSuccess:e.onFileSuccess,onFileError:e.onFileError},l=I(o),g=N(p),c=useRef(null),[i,r]=useState(null),m=s=>typeof e.objectKey=="function"?e.objectKey(s):e.objectKey,a=async s=>{if(n){if(!s?.length)return;await g.upload(Array.from(s),m);}else {let d=s?.[0];if(!d)return;r({name:d.name,size:d.size}),await l.upload(d,m(d),e.getUploadOptions?.(d));}},t=()=>c.current?.click(),f=n?g.phase==="uploading":l.phase==="uploading";return {mode:n?"multi":"single",phase:n?g.phase:l.phase,fileInfo:n?null:i,progress:n?X:l.progress,files:n?g.files:[],totalProgress:n?g.totalProgress:X,error:n?g.error:l.error,isUploading:f,handleFiles:a,openFilePicker:t,cancel:n?g.cancel:l.cancel,reset:n?g.reset:()=>{l.reset(),r(null);},inputProps:{ref:c,type:"file",...n&&{multiple:true},accept:e.accept?.join(","),hidden:true,onChange:s=>{a(s.target.files),s.target.value="";}},dropHandlers:{onDragOver:s=>{s.preventDefault(),s.stopPropagation();},onDrop:s=>{s.preventDefault(),s.stopPropagation(),f||a(s.dataTransfer.files);}}}}var H={phase:"idle",error:null};function ge(e){let[n,o]=useState(H),p=useRef(e);p.current=e;let l=useCallback(async(c,i)=>{let r=p.current;if(r.beforeDownload&&!await r.beforeDownload(c)){o({phase:"error",error:"Download blocked by beforeDownload hook"}),r.onError?.(c,new Error("blocked"));return}o({phase:"presigning",error:null});try{let{url:m}=await r.api.download(c,{fileName:i,bucket:r.bucket}),a=document.createElement("a");a.href=m,i&&(a.download=i),a.click(),o(H),r.onInitiated?.(c);}catch(m){let a=m instanceof Error?m.message:"Download failed";o({phase:"error",error:a}),r.onError?.(c,m);}},[]),g=useCallback(()=>{o(H);},[]);return {...n,download:l,reset:g}}var J={loaded:0,total:0,percent:0},M={phase:"idle",progress:J,error:null,fileName:null,fileSize:null};function fe(e){let[n,o]=useState(M),p=useRef(e);p.current=e;let l=useRef(null),g=useCallback(async(r,m)=>{let a=r.split("/").pop()??r,t=p.current;if(t.beforeDownload&&!await t.beforeDownload(r)){o(s=>({...s,phase:"error",error:"Download blocked by beforeDownload hook"})),t.onError?.(r,new Error("blocked"),"presigning");return}o({phase:"presigning",progress:J,error:null,fileName:m??null,fileSize:null});try{let{url:f}=await t.api.download(r,{fileName:m,bucket:t.bucket});o(w=>({...w,phase:"downloading"})),t.onDownloadStart?.(r);let s=new AbortController;l.current=s;let d=await fetch(f,{signal:s.signal});if(!d.ok)throw new Error(d.status===404?"File not found":`Download failed (${d.status})`);let U=Number(d.headers.get("content-length")||0),u=m??$(d.headers.get("content-disposition"),a);o(w=>({...w,fileName:u,fileSize:U||null}));let h=d.body?.getReader();if(!h)throw new Error("ReadableStream not supported");let S=[],b=0;for(;;){let{done:w,value:D}=await h.read();if(w)break;S.push(D),b+=D.byteLength;let O=U>0?Math.round(b/U*100):0,v={loaded:b,total:U,percent:O};o(V=>({...V,progress:v})),t.onProgress?.(r,v);}let y=new Blob(S),P=URL.createObjectURL(y),F=document.createElement("a");F.href=P,F.download=u??a,F.click(),URL.revokeObjectURL(P),o(w=>({...w,phase:"success",fileSize:y.size,progress:{loaded:y.size,total:y.size,percent:100}})),await t.onSuccess?.(r,u??a);}catch(f){if(f.name==="AbortError"){t.onCancel?.(r),o(M);return}let s=f instanceof Error?f.message:"Download failed";o(d=>({...d,phase:"error",error:s})),t.onError?.(r,f,"downloading");}finally{l.current=null;}},[]),c=useCallback(()=>{l.current?.abort(),o(M);},[]),i=useCallback(()=>{l.current?.abort(),o(M);},[]);return {...n,download:g,cancel:c,reset:i}}var K={phase:"idle",error:null};function he(e){let[n,o]=useState(K),[p,l]=useState(null),g=useRef(e);g.current=e;let c=useCallback(a=>{l(a),o({phase:"confirming",error:null});},[]),i=useCallback(async()=>{if(!p)return;let a=g.current;if(a.beforeDelete&&!await a.beforeDelete(p)){o({phase:"error",error:"Delete blocked by beforeDelete hook"}),a.onError?.(p,new Error("blocked"),"confirming"),l(null);return}o({phase:"deleting",error:null}),a.onDeleteStart?.(p);try{await a.api.delete(p,{bucket:a.bucket}),o({phase:"success",error:null}),await a.onSuccess?.(p),l(null);}catch(t){let f=t instanceof Error?t.message:"Delete failed";o({phase:"error",error:f}),a.onError?.(p,t,"deleting");}},[p]),r=useCallback(()=>{l(null),o(K);},[]),m=useCallback(()=>{l(null),o(K);},[]);return {...n,pendingKey:p,requestDelete:c,confirmDelete:i,cancelDelete:r,reset:m}}export{W as formatFileSize,E as uploadFile,A as uploadFiles,he as useDelete,ge as useDownload,fe as useFetchDownload,N as useMultiUpload,I as useUpload,ce 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/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-fetch-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","api","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","url","res","blobUrl","anchor","useFetchDownload","contentLength","reader","chunks","done","value","percent","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 { S3Api } from \"@better-s3/server\";\nimport { MAX_RETRIES } from \"./constants\";\nimport { withRetry } from \"./retry\";\nimport { uploadPart } from \"./part\";\n\nexport async function uploadMultipart(\n api: S3Api,\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 api.multipart.init({\n key: objectKey,\n contentType,\n metadata: requestOptions?.metadata,\n bucket: requestOptions?.bucket,\n acl: requestOptions?.acl,\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 api.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 api.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 api.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 { S3Api } 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 api: S3Api,\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 api,\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 api.upload({\n key: objectKey,\n contentType,\n metadata: requestOptions?.metadata,\n bucket: requestOptions?.bucket,\n acl: requestOptions?.acl,\n });\n return uploadSimple(file, presign.url, callbacks.onProgress, signal);\n },\n MAX_RETRIES,\n signal,\n );\n\n await api.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 { S3Api } 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 api: S3Api,\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 api,\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 { S3Api } 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 api: S3Api;\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.api,\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 { S3Api } 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 api: S3Api;\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.api,\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 { S3Api } from \"@better-s3/server\";\n\nexport type DownloadPhase = \"idle\" | \"downloading\" | \"success\" | \"error\";\n\nexport type DownloadHooks = {\n beforeDownload?: (key: string) => Promise<boolean> | boolean;\n onSuccess?: (key: string) => Promise<void> | void;\n onError?: (key: string, error: unknown) => void;\n};\n\nexport type UseDownloadOptions = DownloadHooks & {\n api: S3Api;\n /** Target bucket (overrides server default) */\n bucket?: string;\n};\n\nexport type UseDownloadState = {\n phase: DownloadPhase;\n error: string | null;\n fileName: string | null;\n};\n\nexport type UseDownloadReturn = UseDownloadState & {\n download: (key: string, downloadName?: string) => Promise<void>;\n reset: () => void;\n};\n\nconst INITIAL_STATE: UseDownloadState = {\n phase: \"idle\",\n error: null,\n fileName: 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\n const download = useCallback(async (key: string, downloadName?: string) => {\n const name = downloadName ?? key.split(\"/\").pop() ?? key;\n const opts = optionsRef.current;\n\n if (opts.beforeDownload) {\n const allowed = await opts.beforeDownload(key);\n if (!allowed) {\n setState({\n phase: \"error\",\n error: \"Download blocked by beforeDownload hook\",\n fileName: name,\n });\n opts.onError?.(key, new Error(\"blocked\"));\n return;\n }\n }\n\n setState({ phase: \"downloading\", error: null, fileName: name });\n\n try {\n const { url } = await opts.api.download(key, {\n fileName: name,\n bucket: opts.bucket,\n });\n\n // Fetch as blob so the download attribute works correctly\n // (cross-origin presigned URLs ignore anchor.download).\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(\n res.status === 404\n ? \"File not found\"\n : `Download failed (${res.status})`,\n );\n }\n\n const blob = await res.blob();\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({ phase: \"success\", error: null, fileName: name });\n await opts.onSuccess?.(key);\n setState(INITIAL_STATE);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Download failed\";\n setState({ phase: \"error\", error: message, fileName: name });\n opts.onError?.(key, err);\n }\n }, []);\n\n const reset = useCallback(() => {\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, download, reset };\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { S3Api } from \"@better-s3/server\";\n\nexport type FetchDownloadPhase =\n | \"idle\"\n | \"presigning\"\n | \"downloading\"\n | \"success\"\n | \"error\";\n\nexport type FetchDownloadProgress = {\n loaded: number;\n total: number;\n percent: number;\n};\n\nexport type FetchDownloadHooks = {\n beforeDownload?: (key: string) => Promise<boolean> | boolean;\n onDownloadStart?: (key: string) => void;\n onProgress?: (key: string, progress: FetchDownloadProgress) => void;\n onSuccess?: (key: string) => Promise<void> | void;\n onError?: (key: string, error: unknown, phase: FetchDownloadPhase) => void;\n onCancel?: (key: string) => void;\n};\n\nexport type UseFetchDownloadOptions = FetchDownloadHooks & {\n api: S3Api;\n /** Target bucket (overrides server default) */\n bucket?: string;\n};\n\nexport type UseFetchDownloadState = {\n phase: FetchDownloadPhase;\n progress: FetchDownloadProgress;\n error: string | null;\n fileName: string | null;\n fileSize: number | null;\n};\n\nexport type UseFetchDownloadReturn = UseFetchDownloadState & {\n download: (key: string, downloadName?: string) => Promise<void>;\n cancel: () => void;\n reset: () => void;\n};\n\nconst INITIAL_PROGRESS: FetchDownloadProgress = {\n loaded: 0,\n total: 0,\n percent: 0,\n};\n\nconst INITIAL_STATE: UseFetchDownloadState = {\n phase: \"idle\",\n progress: INITIAL_PROGRESS,\n error: null,\n fileName: null,\n fileSize: null,\n};\n\nexport function useFetchDownload(\n options: UseFetchDownloadOptions,\n): UseFetchDownloadReturn {\n const [state, setState] = useState<UseFetchDownloadState>(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\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.api.download(key, {\n fileName: name,\n bucket: opts.bucket,\n });\n\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) {\n throw new Error(\n res.status === 404\n ? \"File not found\"\n : `Download failed (${res.status})`,\n );\n }\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: FetchDownloadProgress = {\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 { S3Api } from \"@better-s3/server\";\nimport type { DeletePhase, DeleteHooks } from \"./types\";\n\nexport type UseDeleteOptions = DeleteHooks & {\n api: S3Api;\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.api.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"]}
|
|
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-download.ts","../src/use-fetch-download.ts","../src/use-delete.ts"],"names":["parseContentDispositionFilename","header","fallback","starMatch","match","formatFileSize","bytes","units","i","withRetry","fn","retries","signal","lastError","attempt","err","delay","r","uploadSimple","file","url","fields","onProgress","resolve","reject","xhr","onAbort","e","formData","k","v","uploadPart","blob","presignedUrl","partLoaded","totalSize","reportProgress","eTag","uploadMultipart","api","objectKey","partSize","concurrentParts","requestOptions","contentType","uploadId","key","totalParts","parts","partProgress","loaded","sum","p","batchStart","batchEnd","batch","start","end","partNumber","batchResults","a","b","result","uploadFile","config","callbacks","threshold","useMultipart","presign","uploadFiles","items","getRequestOptions","results","item","reportTotalProgress","total","nextIndex","processNext","idx","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","isMulti","singleOpts","multiOpts","single","multi","inputRef","fileInfo","setFileInfo","handleFiles","openFilePicker","isUploading","useDownload","download","downloadName","anchor","useFetchDownload","res","contentLength","name","reader","chunks","done","value","percent","blobUrl","useDelete","pendingKey","setPendingKey","requestDelete","confirmDelete","cancelDelete"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Parses the filename from a `Content-Disposition` header value.\n * Prefers `filename*` (RFC 5987, full Unicode) over `filename`.\n * Returns `fallback` if no filename is found.\n */\nexport function parseContentDispositionFilename(\n header: string | null,\n fallback: string,\n): string {\n if (!header) return fallback;\n const starMatch = header.match(/filename\\*=UTF-8''([^;,\\s]+)/i);\n if (starMatch) {\n try {\n return decodeURIComponent(starMatch[1]);\n } catch {\n // fall through\n }\n }\n const match = header.match(/filename=\"([^\"]+)\"/i);\n if (match) return match[1];\n return fallback;\n}\n\nexport 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\n/**\n * Uploads a file directly to S3 using a presigned POST form.\n *\n * All policy fields (acl, Content-Type, content-length-range, signature, etc.)\n * are embedded in `fields` and must be appended to the FormData **before** the\n * file — this is an S3 requirement. The size constraint is enforced by S3 at\n * the storage layer, so the server never needs to re-validate it.\n */\nexport function uploadSimple(\n file: File,\n url: string,\n fields: Record<string, string>,\n onProgress?: (progress: UploadProgress) => void,\n signal?: AbortSignal,\n): Promise<void> {\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 resolve();\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 // S3 presigned POST: policy fields must come before the file field.\n const formData = new FormData();\n for (const [k, v] of Object.entries(fields)) {\n formData.append(k, v);\n }\n formData.append(\"file\", file);\n\n xhr.open(\"POST\", url);\n xhr.send(formData);\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 { S3Api } from \"@better-s3/server\";\nimport { MAX_RETRIES } from \"./constants\";\nimport { withRetry } from \"./retry\";\nimport { uploadPart } from \"./part\";\n\nexport async function uploadMultipart(\n api: S3Api,\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<string | undefined> {\n const contentType = requestOptions?.contentType ?? file.type;\n const { uploadId, key } = await api.multipart.init({\n key: objectKey,\n contentType,\n fileSize: file.size,\n fileName:\n requestOptions?.fileName !== null\n ? (requestOptions?.fileName ?? file.name)\n : undefined,\n metadata: requestOptions?.metadata,\n bucket: requestOptions?.bucket,\n acl: requestOptions?.acl,\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 api.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 const result = await api.multipart.complete({\n key,\n uploadId,\n parts,\n bucket: requestOptions?.bucket,\n });\n onProgress?.({ loaded: file.size, total: file.size, percent: 100 });\n return result.eTag;\n } catch (err) {\n api.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 { S3Api } 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 api: S3Api,\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 eTag = await uploadMultipart(\n api,\n file,\n objectKey,\n DEFAULT_PART_SIZE,\n concurrentParts,\n callbacks.onProgress,\n signal,\n requestOptions,\n );\n } else {\n await withRetry(\n async () => {\n const presign = await api.upload({\n key: objectKey,\n contentType,\n fileSize: file.size,\n fileName:\n requestOptions?.fileName !== null\n ? (requestOptions?.fileName ?? file.name)\n : undefined,\n metadata: requestOptions?.metadata,\n bucket: requestOptions?.bucket,\n acl: requestOptions?.acl,\n });\n await uploadSimple(\n file,\n presign.url,\n presign.fields,\n callbacks.onProgress,\n signal,\n );\n },\n MAX_RETRIES,\n signal,\n );\n\n const confirmed = await api.confirm({\n key: objectKey,\n bucket: requestOptions?.bucket,\n });\n eTag = confirmed.eTag;\n }\n\n return { key: objectKey, eTag };\n}\n","import type {\n UploadConfig,\n UploadProgress,\n UploadResult,\n UploadRequestOptions,\n} from \"../types\";\nimport type { S3Api } 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 api: S3Api,\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 api,\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 { S3Api } 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 api: S3Api;\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.api,\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 { S3Api } 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 api: S3Api;\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.api,\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 UploadResult,\n UploadRequestOptions,\n MultiUploadFileState,\n MultiUploadPhase,\n} from \"./types\";\nimport { useUpload, type UseUploadOptions } from \"./use-upload\";\nimport { useMultiUpload, type UseMultiUploadOptions } from \"./use-multi-upload\";\nimport type { S3Api } from \"@better-s3/server\";\nimport type { UploadConfig } from \"./types\";\n\nconst INITIAL_PROGRESS: UploadProgress = { loaded: 0, total: 0, percent: 0 };\n\nexport type UseUploadControlsOptions = UploadConfig & {\n api: S3Api;\n objectKey: string | ((file: File) => string);\n /**\n * Maximum number of files. When > 1 the hook switches to multi-upload mode\n * and the native file picker allows selecting multiple files.\n * @default 1\n */\n maxFiles?: number;\n /** Static request options applied to all files (multi mode) */\n uploadOptions?: UploadRequestOptions;\n /** Per-file request options */\n getUploadOptions?: (file: File) => UploadRequestOptions;\n // Callbacks – typed as a union to support both single and multi modes\n beforeUpload?:\n | ((file: File) => boolean | Promise<boolean>)\n | ((files: File[]) => boolean | Promise<boolean>);\n onUploadStart?:\n | ((file: File, key: string) => void)\n | ((files: File[]) => void);\n onProgress?:\n | ((file: File, progress: UploadProgress) => void)\n | ((progress: UploadProgress) => void);\n onSuccess?:\n | ((file: File, result: UploadResult) => void | Promise<void>)\n | ((results: UploadResult[]) => void | Promise<void>);\n onError?:\n | ((file: File | null, error: unknown, phase: UploadPhase) => void)\n | ((error: unknown) => void);\n onCancel?: ((file: File | null) => void) | (() => void);\n // Multi-only callbacks\n onFileProgress?: (file: File, progress: UploadProgress) => void;\n onFileSuccess?: (file: File, result: UploadResult) => void;\n onFileError?: (file: File, error: string) => void;\n};\n\nexport type UseUploadControlsReturn = {\n /** Whether the hook is in single-file or multi-file mode */\n mode: \"single\" | \"multi\";\n phase: UploadPhase | MultiUploadPhase;\n /** Info about the selected file (single mode only) */\n fileInfo: { name: string; size: number } | null;\n /** Upload progress (single mode) */\n progress: UploadProgress;\n /** Per-file states (multi mode only) */\n files: MultiUploadFileState[];\n /** Aggregated progress across all files (multi mode) */\n totalProgress: UploadProgress;\n error: string | null;\n isUploading: boolean;\n handleFiles: (files: FileList | null) => void;\n openFilePicker: () => void;\n cancel: () => void;\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 useUploadControls(\n options: UseUploadControlsOptions,\n): UseUploadControlsReturn {\n const isMulti = (options.maxFiles ?? 1) > 1;\n\n // Build per-mode options. Both hooks are called unconditionally (React rules).\n const singleOpts: UseUploadOptions = {\n api: options.api,\n accept: options.accept,\n maxFileSize: options.maxFileSize,\n multipart: options.multipart,\n multipartThreshold: options.multipartThreshold,\n concurrentParts: options.concurrentParts,\n beforeUpload: options.beforeUpload as UseUploadOptions[\"beforeUpload\"],\n onUploadStart: options.onUploadStart as UseUploadOptions[\"onUploadStart\"],\n onProgress: options.onProgress as UseUploadOptions[\"onProgress\"],\n onSuccess: options.onSuccess as UseUploadOptions[\"onSuccess\"],\n onError: options.onError as UseUploadOptions[\"onError\"],\n onCancel: options.onCancel as UseUploadOptions[\"onCancel\"],\n };\n\n const multiOpts: UseMultiUploadOptions = {\n api: options.api,\n accept: options.accept,\n maxFileSize: options.maxFileSize,\n maxFiles: options.maxFiles,\n multipart: options.multipart,\n multipartThreshold: options.multipartThreshold,\n concurrentParts: options.concurrentParts,\n concurrentFiles: options.concurrentFiles,\n uploadOptions: options.uploadOptions,\n getUploadOptions: options.getUploadOptions,\n beforeUpload: options.beforeUpload as UseMultiUploadOptions[\"beforeUpload\"],\n onUploadStart:\n options.onUploadStart as UseMultiUploadOptions[\"onUploadStart\"],\n onProgress: options.onProgress as UseMultiUploadOptions[\"onProgress\"],\n onSuccess: options.onSuccess as UseMultiUploadOptions[\"onSuccess\"],\n onError: options.onError as UseMultiUploadOptions[\"onError\"],\n onCancel: options.onCancel as UseMultiUploadOptions[\"onCancel\"],\n onFileProgress: options.onFileProgress,\n onFileSuccess: options.onFileSuccess,\n onFileError: options.onFileError,\n };\n\n const single = useUpload(singleOpts);\n const multi = useMultiUpload(multiOpts);\n\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 options.objectKey === \"function\"\n ? options.objectKey(file)\n : options.objectKey;\n\n const handleFiles = async (files: FileList | null) => {\n if (isMulti) {\n if (!files?.length) return;\n await multi.upload(Array.from(files), resolveKey);\n } else {\n const file = files?.[0];\n if (!file) return;\n setFileInfo({ name: file.name, size: file.size });\n await single.upload(\n file,\n resolveKey(file),\n options.getUploadOptions?.(file),\n );\n }\n };\n\n const openFilePicker = () => inputRef.current?.click();\n const isUploading = isMulti\n ? multi.phase === \"uploading\"\n : single.phase === \"uploading\";\n\n return {\n mode: isMulti ? \"multi\" : \"single\",\n phase: isMulti ? multi.phase : single.phase,\n fileInfo: isMulti ? null : fileInfo,\n progress: isMulti ? INITIAL_PROGRESS : single.progress,\n files: isMulti ? multi.files : [],\n totalProgress: isMulti ? multi.totalProgress : INITIAL_PROGRESS,\n error: isMulti ? multi.error : single.error,\n isUploading,\n handleFiles,\n openFilePicker,\n cancel: isMulti ? multi.cancel : single.cancel,\n reset: isMulti\n ? multi.reset\n : () => {\n single.reset();\n setFileInfo(null);\n },\n inputProps: {\n ref: inputRef,\n type: \"file\",\n ...(isMulti && { multiple: true as const }),\n accept: options.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 { S3Api } from \"@better-s3/server\";\n\n/** \"presigning\" = fetching the presigned URL from the server; download itself is native */\nexport type DownloadPhase = \"idle\" | \"presigning\" | \"error\";\n\nexport type DownloadHooks = {\n beforeDownload?: (key: string) => Promise<boolean> | boolean;\n /** Fires as soon as the browser has been handed the URL — not when download completes. */\n onInitiated?: (key: string) => void;\n onError?: (key: string, error: unknown) => void;\n};\n\nexport type UseDownloadOptions = DownloadHooks & {\n api: S3Api;\n /** Target bucket (overrides server default) */\n bucket?: string;\n};\n\nexport type UseDownloadState = {\n phase: DownloadPhase;\n error: string | null;\n};\n\nexport type UseDownloadReturn = UseDownloadState & {\n download: (key: string, downloadName?: string) => Promise<void>;\n reset: () => void;\n};\n\nconst INITIAL_STATE: UseDownloadState = {\n phase: \"idle\",\n error: 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\n const download = useCallback(async (key: string, downloadName?: string) => {\n const opts = optionsRef.current;\n\n if (opts.beforeDownload) {\n const allowed = await opts.beforeDownload(key);\n if (!allowed) {\n setState({\n phase: \"error\",\n error: \"Download blocked by beforeDownload hook\",\n });\n opts.onError?.(key, new Error(\"blocked\"));\n return;\n }\n }\n\n setState({ phase: \"presigning\", error: null });\n\n try {\n const { url } = await opts.api.download(key, {\n fileName: downloadName,\n bucket: opts.bucket,\n });\n\n // Native browser download — no fetch, no RAM usage.\n // S3 uses the Content-Disposition stored at upload time for the filename.\n // Pass downloadName only if the caller explicitly wants to override it.\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n if (downloadName) anchor.download = downloadName;\n anchor.click();\n\n setState(INITIAL_STATE);\n opts.onInitiated?.(key);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Download failed\";\n setState({ phase: \"error\", error: message });\n opts.onError?.(key, err);\n }\n }, []);\n\n const reset = useCallback(() => {\n setState(INITIAL_STATE);\n }, []);\n\n return { ...state, download, reset };\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { S3Api } from \"@better-s3/server\";\nimport { parseContentDispositionFilename } from \"./helpers\";\n\nexport type FetchDownloadPhase =\n | \"idle\"\n | \"presigning\"\n | \"downloading\"\n | \"success\"\n | \"error\";\n\nexport type FetchDownloadProgress = {\n loaded: number;\n total: number;\n percent: number;\n};\n\nexport type FetchDownloadHooks = {\n beforeDownload?: (key: string) => Promise<boolean> | boolean;\n onDownloadStart?: (key: string) => void;\n onProgress?: (key: string, progress: FetchDownloadProgress) => void;\n onSuccess?: (key: string, fileName: string) => Promise<void> | void;\n onError?: (key: string, error: unknown, phase: FetchDownloadPhase) => void;\n onCancel?: (key: string) => void;\n};\n\nexport type UseFetchDownloadOptions = FetchDownloadHooks & {\n api: S3Api;\n /** Target bucket (overrides server default) */\n bucket?: string;\n};\n\nexport type UseFetchDownloadState = {\n phase: FetchDownloadPhase;\n progress: FetchDownloadProgress;\n error: string | null;\n fileName: string | null;\n fileSize: number | null;\n};\n\nexport type UseFetchDownloadReturn = UseFetchDownloadState & {\n download: (key: string, downloadName?: string) => Promise<void>;\n cancel: () => void;\n reset: () => void;\n};\n\nconst INITIAL_PROGRESS: FetchDownloadProgress = {\n loaded: 0,\n total: 0,\n percent: 0,\n};\n\nconst INITIAL_STATE: UseFetchDownloadState = {\n phase: \"idle\",\n progress: INITIAL_PROGRESS,\n error: null,\n fileName: null,\n fileSize: null,\n};\n\nexport function useFetchDownload(\n options: UseFetchDownloadOptions,\n): UseFetchDownloadReturn {\n const [state, setState] = useState<UseFetchDownloadState>(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 fallback = key.split(\"/\").pop() ?? key;\n const opts = optionsRef.current;\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: downloadName ?? null,\n fileSize: null,\n });\n\n try {\n const { url } = await opts.api.download(key, {\n fileName: downloadName,\n bucket: opts.bucket,\n });\n\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) {\n throw new Error(\n res.status === 404\n ? \"File not found\"\n : `Download failed (${res.status})`,\n );\n }\n\n const contentLength = Number(res.headers.get(\"content-length\") || 0);\n const name =\n downloadName ??\n parseContentDispositionFilename(\n res.headers.get(\"content-disposition\"),\n fallback,\n );\n setState((s) => ({\n ...s,\n fileName: name,\n fileSize: contentLength || null,\n }));\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: FetchDownloadProgress = {\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 ?? fallback;\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, name ?? fallback);\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 { S3Api } from \"@better-s3/server\";\nimport type { DeletePhase, DeleteHooks } from \"./types\";\n\nexport type UseDeleteOptions = DeleteHooks & {\n api: S3Api;\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.api.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"]}
|
package/dist/types/upload.d.ts
CHANGED
|
@@ -18,6 +18,12 @@ export type UploadRequestOptions = {
|
|
|
18
18
|
contentType?: string;
|
|
19
19
|
/** Object ACL – defaults to `'private'` */
|
|
20
20
|
acl?: "private" | "public-read";
|
|
21
|
+
/**
|
|
22
|
+
* Original file name stored as `Content-Disposition` on the S3 object.
|
|
23
|
+
* Defaults to the browser's `file.name` when not provided.
|
|
24
|
+
* Pass `null` to omit Content-Disposition entirely.
|
|
25
|
+
*/
|
|
26
|
+
fileName?: string | null;
|
|
21
27
|
};
|
|
22
28
|
export type UploadHooks = {
|
|
23
29
|
beforeUpload?: (file: File) => Promise<boolean> | boolean;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { UploadProgress, UploadRequestOptions } from "../types";
|
|
2
2
|
import type { S3Api } from "@better-s3/server";
|
|
3
|
-
export declare function uploadMultipart(api: S3Api, file: File, objectKey: string, partSize: number, concurrentParts: number, onProgress?: (progress: UploadProgress) => void, signal?: AbortSignal, requestOptions?: UploadRequestOptions): Promise<
|
|
3
|
+
export declare function uploadMultipart(api: S3Api, file: File, objectKey: string, partSize: number, concurrentParts: number, onProgress?: (progress: UploadProgress) => void, signal?: AbortSignal, requestOptions?: UploadRequestOptions): Promise<string | undefined>;
|
package/dist/upload/simple.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
import type { UploadProgress } from "../types";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Uploads a file directly to S3 using a presigned POST form.
|
|
4
|
+
*
|
|
5
|
+
* All policy fields (acl, Content-Type, content-length-range, signature, etc.)
|
|
6
|
+
* are embedded in `fields` and must be appended to the FormData **before** the
|
|
7
|
+
* file — this is an S3 requirement. The size constraint is enforced by S3 at
|
|
8
|
+
* the storage layer, so the server never needs to re-validate it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function uploadSimple(file: File, url: string, fields: Record<string, string>, onProgress?: (progress: UploadProgress) => void, signal?: AbortSignal): Promise<void>;
|
package/dist/use-download.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { S3Api } from "@better-s3/server";
|
|
2
|
-
|
|
2
|
+
/** "presigning" = fetching the presigned URL from the server; download itself is native */
|
|
3
|
+
export type DownloadPhase = "idle" | "presigning" | "error";
|
|
3
4
|
export type DownloadHooks = {
|
|
4
5
|
beforeDownload?: (key: string) => Promise<boolean> | boolean;
|
|
5
|
-
|
|
6
|
+
/** Fires as soon as the browser has been handed the URL — not when download completes. */
|
|
7
|
+
onInitiated?: (key: string) => void;
|
|
6
8
|
onError?: (key: string, error: unknown) => void;
|
|
7
9
|
};
|
|
8
10
|
export type UseDownloadOptions = DownloadHooks & {
|
|
@@ -13,7 +15,6 @@ export type UseDownloadOptions = DownloadHooks & {
|
|
|
13
15
|
export type UseDownloadState = {
|
|
14
16
|
phase: DownloadPhase;
|
|
15
17
|
error: string | null;
|
|
16
|
-
fileName: string | null;
|
|
17
18
|
};
|
|
18
19
|
export type UseDownloadReturn = UseDownloadState & {
|
|
19
20
|
download: (key: string, downloadName?: string) => Promise<void>;
|
|
@@ -9,7 +9,7 @@ export type FetchDownloadHooks = {
|
|
|
9
9
|
beforeDownload?: (key: string) => Promise<boolean> | boolean;
|
|
10
10
|
onDownloadStart?: (key: string) => void;
|
|
11
11
|
onProgress?: (key: string, progress: FetchDownloadProgress) => void;
|
|
12
|
-
onSuccess?: (key: string) => Promise<void> | void;
|
|
12
|
+
onSuccess?: (key: string, fileName: string) => Promise<void> | void;
|
|
13
13
|
onError?: (key: string, error: unknown, phase: FetchDownloadPhase) => void;
|
|
14
14
|
onCancel?: (key: string) => void;
|
|
15
15
|
};
|
|
@@ -1,36 +1,55 @@
|
|
|
1
|
-
import type { UploadPhase, UploadProgress, UploadRequestOptions } from "./types";
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import type { UploadPhase, UploadProgress, UploadResult, UploadRequestOptions, MultiUploadFileState, MultiUploadPhase } from "./types";
|
|
2
|
+
import type { S3Api } from "@better-s3/server";
|
|
3
|
+
import type { UploadConfig } from "./types";
|
|
4
|
+
export type UseUploadControlsOptions = UploadConfig & {
|
|
5
|
+
api: S3Api;
|
|
4
6
|
objectKey: string | ((file: File) => string);
|
|
5
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Maximum number of files. When > 1 the hook switches to multi-upload mode
|
|
9
|
+
* and the native file picker allows selecting multiple files.
|
|
10
|
+
* @default 1
|
|
11
|
+
*/
|
|
12
|
+
maxFiles?: number;
|
|
13
|
+
/** Static request options applied to all files (multi mode) */
|
|
14
|
+
uploadOptions?: UploadRequestOptions;
|
|
15
|
+
/** Per-file request options */
|
|
6
16
|
getUploadOptions?: (file: File) => UploadRequestOptions;
|
|
17
|
+
beforeUpload?: ((file: File) => boolean | Promise<boolean>) | ((files: File[]) => boolean | Promise<boolean>);
|
|
18
|
+
onUploadStart?: ((file: File, key: string) => void) | ((files: File[]) => void);
|
|
19
|
+
onProgress?: ((file: File, progress: UploadProgress) => void) | ((progress: UploadProgress) => void);
|
|
20
|
+
onSuccess?: ((file: File, result: UploadResult) => void | Promise<void>) | ((results: UploadResult[]) => void | Promise<void>);
|
|
21
|
+
onError?: ((file: File | null, error: unknown, phase: UploadPhase) => void) | ((error: unknown) => void);
|
|
22
|
+
onCancel?: ((file: File | null) => void) | (() => void);
|
|
23
|
+
onFileProgress?: (file: File, progress: UploadProgress) => void;
|
|
24
|
+
onFileSuccess?: (file: File, result: UploadResult) => void;
|
|
25
|
+
onFileError?: (file: File, error: string) => void;
|
|
7
26
|
};
|
|
8
27
|
export type UseUploadControlsReturn = {
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/** Error message if upload failed */
|
|
14
|
-
error: string | null;
|
|
15
|
-
/** Info about the selected file */
|
|
28
|
+
/** Whether the hook is in single-file or multi-file mode */
|
|
29
|
+
mode: "single" | "multi";
|
|
30
|
+
phase: UploadPhase | MultiUploadPhase;
|
|
31
|
+
/** Info about the selected file (single mode only) */
|
|
16
32
|
fileInfo: {
|
|
17
33
|
name: string;
|
|
18
34
|
size: number;
|
|
19
35
|
} | null;
|
|
20
|
-
/**
|
|
36
|
+
/** Upload progress (single mode) */
|
|
37
|
+
progress: UploadProgress;
|
|
38
|
+
/** Per-file states (multi mode only) */
|
|
39
|
+
files: MultiUploadFileState[];
|
|
40
|
+
/** Aggregated progress across all files (multi mode) */
|
|
41
|
+
totalProgress: UploadProgress;
|
|
42
|
+
error: string | null;
|
|
21
43
|
isUploading: boolean;
|
|
22
|
-
/** Trigger upload from a FileList (e.g. from a file input or drop event) */
|
|
23
44
|
handleFiles: (files: FileList | null) => void;
|
|
24
|
-
/** Open the native file picker dialog */
|
|
25
45
|
openFilePicker: () => void;
|
|
26
|
-
/** Cancel the current upload */
|
|
27
46
|
cancel: () => void;
|
|
28
|
-
/** Reset to idle state */
|
|
29
47
|
reset: () => void;
|
|
30
48
|
/** Spread on a hidden `<input>` element */
|
|
31
49
|
inputProps: {
|
|
32
50
|
ref: React.RefObject<HTMLInputElement | null>;
|
|
33
51
|
type: "file";
|
|
52
|
+
multiple?: true;
|
|
34
53
|
accept?: string;
|
|
35
54
|
hidden: true;
|
|
36
55
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-s3/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "React hooks for S3-compatible file uploads, downloads, and deletes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"s3",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dist"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@better-s3/server": "
|
|
36
|
+
"@better-s3/server": "3.1.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"react": ">=18"
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { UploadProgress, MultiUploadPhase, MultiUploadFileState } from "./types";
|
|
2
|
-
import { type UseMultiUploadOptions } from "./use-multi-upload";
|
|
3
|
-
export type UseMultiUploadControlsOptions = UseMultiUploadOptions & {
|
|
4
|
-
objectKey: (file: File) => string;
|
|
5
|
-
};
|
|
6
|
-
export type UseMultiUploadControlsReturn = {
|
|
7
|
-
/** Current upload phase */
|
|
8
|
-
phase: MultiUploadPhase;
|
|
9
|
-
/** Per-file upload state */
|
|
10
|
-
files: MultiUploadFileState[];
|
|
11
|
-
/** Aggregated progress across all files */
|
|
12
|
-
totalProgress: UploadProgress;
|
|
13
|
-
/** Error message if upload failed */
|
|
14
|
-
error: string | null;
|
|
15
|
-
/** Whether an upload is currently in progress */
|
|
16
|
-
isUploading: boolean;
|
|
17
|
-
/** Trigger upload from a FileList (e.g. from a file input or drop event) */
|
|
18
|
-
handleFiles: (files: FileList | null) => void;
|
|
19
|
-
/** Open the native file picker dialog */
|
|
20
|
-
openFilePicker: () => void;
|
|
21
|
-
/** Cancel all uploads */
|
|
22
|
-
cancel: () => void;
|
|
23
|
-
/** Reset to idle state */
|
|
24
|
-
reset: () => void;
|
|
25
|
-
/** Spread on a hidden `<input>` element */
|
|
26
|
-
inputProps: {
|
|
27
|
-
ref: React.RefObject<HTMLInputElement | null>;
|
|
28
|
-
type: "file";
|
|
29
|
-
multiple: true;
|
|
30
|
-
accept?: string;
|
|
31
|
-
hidden: true;
|
|
32
|
-
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
33
|
-
};
|
|
34
|
-
/** Spread on a container to enable drag-and-drop */
|
|
35
|
-
dropHandlers: {
|
|
36
|
-
onDragOver: (e: React.DragEvent) => void;
|
|
37
|
-
onDrop: (e: React.DragEvent) => void;
|
|
38
|
-
};
|
|
39
|
-
};
|
|
40
|
-
export declare function useMultiUploadControls(options: UseMultiUploadControlsOptions): UseMultiUploadControlsReturn;
|