@amaster.ai/s3-client 1.1.20 → 1.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,7 +21,12 @@ const s3Client = createS3Client();
21
21
  const fileInput = document.querySelector('input[type="file"]');
22
22
  if (fileInput.files.length > 0) {
23
23
  const file = fileInput.files[0];
24
- const uploadResult = await s3Client.upload(file);
24
+ const uploadResult = await s3Client.upload(file, {
25
+ category: 'avatars',
26
+ onProgress: ({ percentage }) => {
27
+ console.log(`Uploading: ${percentage}%`);
28
+ },
29
+ });
25
30
 
26
31
  if (uploadResult.data) {
27
32
  console.log('Uploaded:', uploadResult.data.url);
@@ -51,15 +56,19 @@ if (downloadResult.data) {
51
56
 
52
57
  Creates a new instance of the S3 client. Optionally accepts a custom HTTP client.
53
58
 
54
- ### `upload(file: File | Blob)`
59
+ ### `upload(file: File | Blob, options?: S3UploadOptions)`
55
60
 
56
61
  Uploads a file to the storage.
57
62
 
58
63
  Large files automatically switch to multipart upload inside the SDK. The caller
59
- still uses the same `upload(file)` entrypoint.
64
+ still uses the same `upload(file, options?)` entrypoint. Browser multipart
65
+ uploads report aggregate progress across all parts.
60
66
 
61
67
  - **Parameters**:
62
68
  - `file`: `File` or `Blob` object to upload.
69
+ - `options.fileName`: optional filename override.
70
+ - `options.category`: optional storage category metadata.
71
+ - `options.onProgress`: optional callback receiving `{ loaded, total, percentage, phase }`.
63
72
  - **Returns**: `Promise<ClientResult<UploadRes>>`
64
73
 
65
74
  ### `download(filename: string)`
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var httpClient=require('@amaster.ai/http-client');var c="/api/storage",P=1024*1024,S=32*P,w=8*P,F=3;function f(t,e,r){return {data:null,error:{message:t,status:e,details:r},status:e}}function h(){let t=new Error("Upload aborted");return t.name="AbortError",t}function $(t,e){return t instanceof Error&&t.message?t.message:typeof t=="string"&&t?t:e}function B(t){return "name"in t&&typeof t.name=="string"&&t.name.trim()?t.name.trim():"blob"}function L(t){return typeof t.type=="string"&&t.type?t.type:void 0}function I(t){return t.size>=S}function M(t,e){if(!t)return null;if(typeof Headers<"u"&&t instanceof Headers)return t.get(e);if(typeof t=="object"){for(let[r,a]of Object.entries(t))if(r.toLowerCase()===e.toLowerCase())return a==null?null:String(a)}return null}function x(t){return (typeof t=="object"||typeof t=="function")&&t!==null&&"then"in t&&typeof t.then=="function"}function E(t){return typeof t=="object"&&t!==null&&("statusCode"in t||"status"in t||"data"in t||"header"in t||"headers"in t)}function y(t){return E(t)?t:{}}async function H(t,e,r){return await new Promise((a,o)=>{let s=false,u=null,m=()=>{r?.removeEventListener("abort",i);},d=n=>{s||(s=true,m(),a(n));},l=n=>{s||(s=true,m(),o(n));},i=()=>{try{u?.abort?.();}catch{}l(h());};if(r?.aborted){l(h());return}r?.addEventListener("abort",i,{once:true});try{let n=t({...e,success:p=>{d(y(p));},fail:l});if(typeof n=="object"&&n!==null&&(u=n),x(n)){n.then(p=>{d(y(p));},l);return}E(n)&&d(n);}catch(n){l(n);}})}async function j(t,e,r){if(typeof fetch!="function")throw new Error("Fetch API is not available for multipart upload.");let a=await fetch(t,{method:"PUT",body:e,signal:r});if(!a.ok){let s="";try{s=await a.text();}catch{}throw new Error(`Multipart part upload failed (${a.status})${s?`: ${s}`:""}`)}let o=M(a.headers,"etag");if(!o)throw new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers.");return o}async function O(t,e,r){let a=httpClient.getMiniProgramRequest();if(!a)throw new Error("Mini-program request API is not available for multipart upload.");let o=await H(a,{url:t,method:"PUT",data:await e.arrayBuffer(),header:{}},r),s=Number(o?.statusCode??o?.status??0);if(s<200||s>=300){let m=typeof o?.data=="string"&&o.data?`: ${o.data}`:"";throw new Error(`Multipart part upload failed (${s||"unknown"})${m}`)}let u=M(o?.header??o?.headers,"etag");if(!u)throw new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers.");return u}async function _(t,e,r){return httpClient.detectMiniProgramPlatform()?await O(t,e,r):await j(t,e,r)}async function b(t,e,r){try{await t.request({url:`${c}/upload/multipart/abort`,method:"post",data:{filePath:e,uploadId:r}});}catch{}}async function D(t,e){let r=await t.request({url:`${c}/upload/multipart/init`,method:"post",data:{filename:B(e),contentType:L(e)}});if(r.error)return f(r.error.message,r.status||500,r.error.details);if(!r.data?.uploadId||!r.data?.filePath)return f("Multipart upload init returned an invalid response.",r.status||500);let{uploadId:a,filePath:o}=r.data,s=Math.max(1,Math.ceil(e.size/w)),u=new Array(s),m=Math.min(F,s),d=new AbortController,l=0;try{if(await Promise.all(Array.from({length:m},async()=>{for(;;){let n=l;if(l+=1,n>=s)return;let p=n+1,R=n*w,C=Math.min(R+w,e.size),T=e.slice(R,C),g=await t.request({url:`${c}/upload/multipart/url`,method:"post",data:{filePath:o,uploadId:a,partNumber:p},signal:d.signal});if(g.error)throw new Error(g.error.message);if(!g.data?.url)throw new Error(`Multipart upload URL request returned an invalid response for part ${p}.`);let k=await _(g.data.url,T,d.signal);u[n]={partNumber:p,eTag:k};}})),u.some(n=>!n?.eTag))throw new Error("Multipart upload did not collect all uploaded part ETags.");let i=await t.request({url:`${c}/upload/multipart/complete`,method:"post",data:{filePath:o,uploadId:a,parts:u}});return i.error?(await b(t,o,a),f(i.error.message,i.status||500,i.error.details)):i.data?i:(await b(t,o,a),f("Multipart upload complete returned an empty response.",i.status||500))}catch(i){return d.abort(),await b(t,o,a),f($(i,"Multipart upload failed"),i instanceof Error&&i.name==="AbortError"?499:500,i)}}function N(t=httpClient.createHttpClient()){return {async download(e){return e?await t.request({url:`${c}/download`,method:"get",params:{filename:e},responseType:"blob"}):{data:null,error:{message:"Filename is required",status:400},status:400}},async getMetadata(e){return e?t.request({url:`${c}/metadata`,method:"get",params:{key:e}}):{data:null,error:{message:"Key is required",status:400},status:400}},async upload(e){if(!e)return {data:null,error:{message:"File is required",status:400},status:400};if(I(e))return await D(t,e);let r=new FormData;return r.append("file",e),t.request({url:`${c}/upload`,method:"post",data:r,headers:{"Content-Type":"multipart/form-data"}})}}}exports.createS3Client=N;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var httpClient=require('@amaster.ai/http-client');var P="/api/storage",A=1024*1024,I=32*A,k=8*A,j=3;function h(t,e,r){return {data:null,error:{message:t,status:e,details:r},status:e}}function b(){let t=new Error("Upload aborted");return t.name="AbortError",t}function v(t,e){return t instanceof Error&&t.message?t.message:typeof t=="string"&&t?t:e}function D(t,e){return e?.trim()?e.trim():"name"in t&&typeof t.name=="string"&&t.name.trim()?t.name.trim():"blob"}function N(t){return typeof t.type=="string"&&t.type?t.type:void 0}function _(t){return t.size>=I}function y(t,e,r,n){let o=Number.isFinite(e)&&e>0?e:0,a=Number.isFinite(t)?t:0,u=o>0?o:a,s=Math.max(0,Math.min(a,u)),m=o>0?Math.min(100,Math.round(s/o*1e4)/100):0;return {loaded:s,total:o,percentage:m,phase:r,...n}}function R(t,e){t?.(e);}function X(t,e,r){if(r?.trim()){t.append("file",e,r.trim());return}t.append("file",e);}function F(t,e){if(!t)return null;if(typeof Headers<"u"&&t instanceof Headers)return t.get(e);if(typeof t=="object"){for(let[r,n]of Object.entries(t))if(r.toLowerCase()===e.toLowerCase())return n==null?null:String(n)}return null}function W(t){return (typeof t=="object"||typeof t=="function")&&t!==null&&"then"in t&&typeof t.then=="function"}function L(t){return typeof t=="object"&&t!==null&&("statusCode"in t||"status"in t||"data"in t||"header"in t||"headers"in t)}function q(t){return L(t)?t:{}}async function K(t,e,r){return await new Promise((n,o)=>{let a=false,u=null,s=()=>{r?.removeEventListener("abort",d);},m=i=>{a||(a=true,s(),n(i));},c=i=>{a||(a=true,s(),o(i));},d=()=>{try{u?.abort?.();}catch{}c(b());};if(r?.aborted){c(b());return}r?.addEventListener("abort",d,{once:true});try{let i=t({...e,success:l=>{m(q(l));},fail:c});if(typeof i=="object"&&i!==null&&(u=i),W(i)){i.then(l=>{m(q(l));},c);return}L(i)&&m(i);}catch(i){c(i);}})}async function V(t,e,r,n){if(typeof XMLHttpRequest!="function")throw new Error("XMLHttpRequest is not available for multipart upload.");return await new Promise((o,a)=>{let u=false,s=new XMLHttpRequest,m=()=>{r?.removeEventListener("abort",i);},c=l=>{u||(u=true,m(),o(l));},d=l=>{u||(u=true,m(),a(l));},i=()=>{try{s.abort();}catch{}d(b());};if(r?.aborted){d(b());return}r?.addEventListener("abort",i,{once:true}),s.upload.onprogress=l=>{let f=l.lengthComputable?l.total:e.size;n?.(l.loaded,f);},s.onload=()=>{if(s.status<200||s.status>=300){let f=s.responseText?`: ${s.responseText}`:"";d(new Error(`Multipart part upload failed (${s.status})${f}`));return}let l=s.getResponseHeader("etag");if(!l){d(new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers."));return}n?.(e.size,e.size),c(l);},s.onerror=()=>{d(new Error("Multipart part upload failed (network error)"));},s.onabort=()=>{d(b());},s.open("PUT",t),s.send(e);})}async function Y(t,e,r){if(typeof fetch!="function")throw new Error("Fetch API is not available for multipart upload.");let n=await fetch(t,{method:"PUT",body:e,signal:r});if(!n.ok){let a="";try{a=await n.text();}catch{}throw new Error(`Multipart part upload failed (${n.status})${a?`: ${a}`:""}`)}let o=F(n.headers,"etag");if(!o)throw new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers.");return o}async function Z(t,e,r){let n=httpClient.getMiniProgramRequest();if(!n)throw new Error("Mini-program request API is not available for multipart upload.");let o=await K(n,{url:t,method:"PUT",data:await e.arrayBuffer(),header:{}},r),a=Number(o?.statusCode??o?.status??0);if(a<200||a>=300){let s=typeof o?.data=="string"&&o.data?`: ${o.data}`:"";throw new Error(`Multipart part upload failed (${a||"unknown"})${s}`)}let u=F(o?.header??o?.headers,"etag");if(!u)throw new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers.");return u}async function G(t,e,r,n){return httpClient.detectMiniProgramPlatform()?await Z(t,e,r):n&&typeof XMLHttpRequest=="function"?await V(t,e,r,n):await Y(t,e,r)}async function S(t,e,r){try{await t.request({url:`${P}/upload/multipart/abort`,method:"post",data:{filePath:e,uploadId:r}});}catch{}}async function J(t,e,r={}){R(r.onProgress,y(0,e.size,"preparing"));let n=await t.request({url:`${P}/upload/multipart/init`,method:"post",data:{filename:D(e,r.fileName),contentType:N(e),...r.category?{category:r.category}:{}}});if(n.error)return h(n.error.message,n.status||500,n.error.details);if(!n.data?.uploadId||!n.data?.filePath)return h("Multipart upload init returned an invalid response.",n.status||500);let{uploadId:o,filePath:a}=n.data,u=Math.max(1,Math.ceil(e.size/k)),s=new Array(u),m=Math.min(j,u),c=new AbortController,d=new Array(u).fill(0),i=0,l=0,f=(p,g)=>{let w=d.reduce((M,T)=>M+T,0);R(r.onProgress,y(Math.min(e.size,i+w),e.size,p,{...g?{partNumber:g}:{},totalParts:u}));};f("uploading");try{if(await Promise.all(Array.from({length:m},async()=>{for(;;){let g=l;if(l+=1,g>=u)return;let w=g+1,M=g*k,T=Math.min(M+k,e.size),E=e.slice(M,T),U=await t.request({url:`${P}/upload/multipart/url`,method:"post",data:{filePath:a,uploadId:o,partNumber:w},signal:c.signal});if(U.error)throw new Error(U.error.message);if(!U.data?.url)throw new Error(`Multipart upload URL request returned an invalid response for part ${w}.`);let z=await G(U.data.url,E,c.signal,(B,C)=>{let H=C&&C>0?C:E.size;d[g]=Math.min(B,H,E.size),f("uploading",w);});d[g]=0,i+=E.size,f("uploading",w),s[g]={partNumber:w,eTag:z};}})),s.some(g=>!g?.eTag))throw new Error("Multipart upload did not collect all uploaded part ETags.");i=e.size,d.fill(0),f("completing");let p=await t.request({url:`${P}/upload/multipart/complete`,method:"post",data:{filePath:a,uploadId:o,parts:s}});return p.error?(await S(t,a,o),h(p.error.message,p.status||500,p.error.details)):p.data?(f("completed"),p):(await S(t,a,o),h("Multipart upload complete returned an empty response.",p.status||500))}catch(p){return c.abort(),await S(t,a,o),h(v(p,"Multipart upload failed"),p instanceof Error&&p.name==="AbortError"?499:500,p)}}function tt(t=httpClient.createHttpClient()){return {async download(e){return e?await t.request({url:`${P}/download`,method:"get",params:{filename:e},responseType:"blob"}):{data:null,error:{message:"Filename is required",status:400},status:400}},async getMetadata(e){return e?t.request({url:`${P}/metadata`,method:"get",params:{key:e}}):{data:null,error:{message:"Key is required",status:400},status:400}},async upload(e,r={}){if(!e)return {data:null,error:{message:"File is required",status:400},status:400};if(_(e))return await J(t,e,r);let n=new FormData;X(n,e,r.fileName),r.category&&n.append("category",r.category),R(r.onProgress,y(0,e.size,"uploading"));let o=await t.request({url:`${P}/upload`,method:"post",data:n,headers:{"Content-Type":"multipart/form-data"},onUploadProgress:r.onProgress?a=>{R(r.onProgress,y(a.loaded,a.total??e.size,"uploading"));}:void 0});return o.error||R(r.onProgress,y(e.size,e.size,"completed")),o}}}exports.createS3Client=tt;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/s3-client.ts"],"names":["BASE_URL","MB","DEFAULT_MULTIPART_THRESHOLD","DEFAULT_PART_SIZE","DEFAULT_CONCURRENCY","createErrorResult","message","status","details","createAbortError","error","getErrorMessage","fallback","resolveFilename","file","resolveContentType","shouldUseMultipartUpload","getHeaderValue","headers","key","headerKey","headerValue","isPromiseLike","value","isMiniProgramRequestResponse","normalizeMiniProgramRequestResponse","callMiniProgramRequest","request","options","signal","resolve","reject","settled","task","cleanup","onAbort","resolveOnce","rejectOnce","taskResult","uploadPartWithFetch","url","blob","response","eTag","uploadPartWithMiniProgram","getMiniProgramRequest","uploadPartToPresignedUrl","detectMiniProgramPlatform","abortMultipartUpload","http","filePath","uploadId","uploadWithMultipart","initResult","totalParts","parts","workerCount","uploadAbortController","nextPartIndex","currentIndex","partNumber","start","end","urlResult","part","completeResult","createS3Client","createHttpClient","filename","formData"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n createHttpClient,\n detectMiniProgramPlatform,\n getMiniProgramRequest,\n type ClientResult,\n type HttpClient,\n} from \"@amaster.ai/http-client\";\nimport type { S3Client, S3Metadata, UploadRes } from \"./types\";\n\nconst BASE_URL = \"/api/storage\";\nconst MB = 1024 * 1024;\nconst DEFAULT_MULTIPART_THRESHOLD = 32 * MB;\nconst DEFAULT_PART_SIZE = 8 * MB;\nconst DEFAULT_CONCURRENCY = 3;\n\ntype MultipartInitRes = {\n uploadId: string;\n filePath: string;\n};\n\ntype MultipartUrlRes = {\n url: string;\n};\n\ntype PartETag = {\n partNumber: number;\n eTag: string;\n};\n\ntype MiniProgramRequestResponse = {\n statusCode?: unknown;\n status?: unknown;\n data?: unknown;\n header?: unknown;\n headers?: unknown;\n};\n\ntype AbortableTask = {\n abort?: () => void;\n};\n\nfunction createErrorResult<T>(\n message: string,\n status: number,\n details?: unknown,\n): ClientResult<T> {\n return {\n data: null,\n error: {\n message,\n status,\n details,\n },\n status,\n };\n}\n\nfunction createAbortError(): Error {\n const error = new Error(\"Upload aborted\");\n error.name = \"AbortError\";\n return error;\n}\n\nfunction getErrorMessage(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n if (typeof error === \"string\" && error) {\n return error;\n }\n return fallback;\n}\n\nfunction resolveFilename(file: File | Blob): string {\n if (\"name\" in file && typeof file.name === \"string\" && file.name.trim()) {\n return file.name.trim();\n }\n return \"blob\";\n}\n\nfunction resolveContentType(file: File | Blob): string | undefined {\n return typeof file.type === \"string\" && file.type ? file.type : undefined;\n}\n\nfunction shouldUseMultipartUpload(file: File | Blob): boolean {\n return file.size >= DEFAULT_MULTIPART_THRESHOLD;\n}\n\nfunction getHeaderValue(headers: unknown, key: string): string | null {\n if (!headers) {\n return null;\n }\n\n if (typeof Headers !== \"undefined\" && headers instanceof Headers) {\n return headers.get(key);\n }\n\n if (typeof headers === \"object\") {\n for (const [headerKey, headerValue] of Object.entries(\n headers as Record<string, unknown>,\n )) {\n if (headerKey.toLowerCase() === key.toLowerCase()) {\n return headerValue == null ? null : String(headerValue);\n }\n }\n }\n\n return null;\n}\n\nfunction isPromiseLike<T = unknown>(value: unknown): value is PromiseLike<T> {\n return (\n (typeof value === \"object\" || typeof value === \"function\") &&\n value !== null &&\n \"then\" in value &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\nfunction isMiniProgramRequestResponse(\n value: unknown,\n): value is MiniProgramRequestResponse {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (\"statusCode\" in value ||\n \"status\" in value ||\n \"data\" in value ||\n \"header\" in value ||\n \"headers\" in value)\n );\n}\n\nfunction normalizeMiniProgramRequestResponse(\n value: unknown,\n): MiniProgramRequestResponse {\n return isMiniProgramRequestResponse(value) ? value : {};\n}\n\nasync function callMiniProgramRequest(\n request: (_options: Record<string, unknown>) => unknown,\n options: Record<string, unknown>,\n signal?: AbortSignal,\n): Promise<MiniProgramRequestResponse> {\n return await new Promise<MiniProgramRequestResponse>((resolve, reject) => {\n let settled = false;\n let task: AbortableTask | null = null;\n\n const cleanup = () => {\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n const resolveOnce = (value: MiniProgramRequestResponse) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const rejectOnce = (error: unknown) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n reject(error);\n };\n\n const onAbort = () => {\n try {\n task?.abort?.();\n } catch {\n // ignore abort failures from runtime-specific request tasks\n }\n rejectOnce(createAbortError());\n };\n\n if (signal?.aborted) {\n rejectOnce(createAbortError());\n return;\n }\n\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n try {\n const taskResult = request({\n ...options,\n success: (value: unknown) => {\n resolveOnce(normalizeMiniProgramRequestResponse(value));\n },\n fail: rejectOnce,\n });\n\n if (typeof taskResult === \"object\" && taskResult !== null) {\n task = taskResult as AbortableTask;\n }\n\n if (isPromiseLike(taskResult)) {\n void taskResult.then(\n (value) => {\n resolveOnce(normalizeMiniProgramRequestResponse(value));\n },\n rejectOnce,\n );\n return;\n }\n\n if (isMiniProgramRequestResponse(taskResult)) {\n resolveOnce(taskResult);\n }\n } catch (error) {\n rejectOnce(error);\n }\n });\n}\n\nasync function uploadPartWithFetch(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n if (typeof fetch !== \"function\") {\n throw new Error(\"Fetch API is not available for multipart upload.\");\n }\n\n const response = await fetch(url, {\n method: \"PUT\",\n body: blob,\n signal,\n });\n\n if (!response.ok) {\n let details = \"\";\n try {\n details = await response.text();\n } catch {\n // ignore response body parse failures\n }\n throw new Error(\n `Multipart part upload failed (${response.status})${details ? `: ${details}` : \"\"}`,\n );\n }\n\n const eTag = getHeaderValue(response.headers, \"etag\");\n if (!eTag) {\n throw new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n );\n }\n\n return eTag;\n}\n\nasync function uploadPartWithMiniProgram(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n const request = getMiniProgramRequest();\n if (!request) {\n throw new Error(\"Mini-program request API is not available for multipart upload.\");\n }\n\n const response = await callMiniProgramRequest(\n request,\n {\n url,\n method: \"PUT\",\n data: await blob.arrayBuffer(),\n header: {},\n },\n signal,\n );\n\n const status = Number(response?.statusCode ?? response?.status ?? 0);\n if (status < 200 || status >= 300) {\n const details =\n typeof response?.data === \"string\" && response.data\n ? `: ${response.data}`\n : \"\";\n throw new Error(\n `Multipart part upload failed (${status || \"unknown\"})${details}`,\n );\n }\n\n const eTag = getHeaderValue(response?.header ?? response?.headers, \"etag\");\n if (!eTag) {\n throw new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n );\n }\n\n return eTag;\n}\n\nasync function uploadPartToPresignedUrl(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n if (detectMiniProgramPlatform()) {\n return await uploadPartWithMiniProgram(url, blob, signal);\n }\n\n return await uploadPartWithFetch(url, blob, signal);\n}\n\nasync function abortMultipartUpload(\n http: HttpClient,\n filePath: string,\n uploadId: string,\n): Promise<void> {\n try {\n await http.request<void>({\n url: `${BASE_URL}/upload/multipart/abort`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n },\n });\n } catch {\n // Best-effort cleanup only.\n }\n}\n\nasync function uploadWithMultipart(\n http: HttpClient,\n file: File | Blob,\n): Promise<ClientResult<UploadRes>> {\n const initResult = await http.request<MultipartInitRes>({\n url: `${BASE_URL}/upload/multipart/init`,\n method: \"post\",\n data: {\n filename: resolveFilename(file),\n contentType: resolveContentType(file),\n },\n });\n\n if (initResult.error) {\n return createErrorResult(\n initResult.error.message,\n initResult.status || 500,\n initResult.error.details,\n );\n }\n\n if (!initResult.data?.uploadId || !initResult.data?.filePath) {\n return createErrorResult(\n \"Multipart upload init returned an invalid response.\",\n initResult.status || 500,\n );\n }\n\n const { uploadId, filePath } = initResult.data;\n const totalParts = Math.max(1, Math.ceil(file.size / DEFAULT_PART_SIZE));\n const parts: PartETag[] = new Array(totalParts);\n const workerCount = Math.min(DEFAULT_CONCURRENCY, totalParts);\n const uploadAbortController = new AbortController();\n let nextPartIndex = 0;\n\n try {\n await Promise.all(\n Array.from({ length: workerCount }, async () => {\n while (true) {\n const currentIndex = nextPartIndex;\n nextPartIndex += 1;\n\n if (currentIndex >= totalParts) {\n return;\n }\n\n const partNumber = currentIndex + 1;\n const start = currentIndex * DEFAULT_PART_SIZE;\n const end = Math.min(start + DEFAULT_PART_SIZE, file.size);\n const blob = file.slice(start, end);\n\n const urlResult = await http.request<MultipartUrlRes>({\n url: `${BASE_URL}/upload/multipart/url`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n partNumber,\n },\n signal: uploadAbortController.signal,\n });\n\n if (urlResult.error) {\n throw new Error(urlResult.error.message);\n }\n\n if (!urlResult.data?.url) {\n throw new Error(\n `Multipart upload URL request returned an invalid response for part ${partNumber}.`,\n );\n }\n\n const eTag = await uploadPartToPresignedUrl(\n urlResult.data.url,\n blob,\n uploadAbortController.signal,\n );\n\n parts[currentIndex] = {\n partNumber,\n eTag,\n };\n }\n }),\n );\n\n if (parts.some((part) => !part?.eTag)) {\n throw new Error(\"Multipart upload did not collect all uploaded part ETags.\");\n }\n\n const completeResult = await http.request<UploadRes>({\n url: `${BASE_URL}/upload/multipart/complete`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n parts,\n },\n });\n\n if (completeResult.error) {\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n completeResult.error.message,\n completeResult.status || 500,\n completeResult.error.details,\n );\n }\n\n if (!completeResult.data) {\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n \"Multipart upload complete returned an empty response.\",\n completeResult.status || 500,\n );\n }\n\n return completeResult;\n } catch (error) {\n uploadAbortController.abort();\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n getErrorMessage(error, \"Multipart upload failed\"),\n error instanceof Error && error.name === \"AbortError\" ? 499 : 500,\n error,\n );\n }\n}\n\nexport function createS3Client(http: HttpClient = createHttpClient()): S3Client {\n return {\n async download(filename: string): Promise<ClientResult<Blob>> {\n if (!filename) {\n return {\n data: null,\n error: { message: \"Filename is required\", status: 400 },\n status: 400,\n };\n }\n\n const result = await http.request<Blob>({\n url: `${BASE_URL}/download`,\n method: \"get\",\n params: { filename },\n responseType: \"blob\",\n });\n\n return result;\n },\n\n async getMetadata(key: string): Promise<ClientResult<S3Metadata>> {\n if (!key) {\n return {\n data: null,\n error: { message: \"Key is required\", status: 400 },\n status: 400,\n };\n }\n\n return http.request<S3Metadata>({\n url: `${BASE_URL}/metadata`,\n method: \"get\",\n params: { key },\n });\n },\n\n async upload(file: File | Blob): Promise<ClientResult<UploadRes>> {\n if (!file) {\n return {\n data: null,\n error: { message: \"File is required\", status: 400 },\n status: 400,\n };\n }\n\n if (shouldUseMultipartUpload(file)) {\n return await uploadWithMultipart(http, file);\n }\n\n const formData = new FormData();\n formData.append(\"file\", file);\n\n return http.request<UploadRes>({\n url: `${BASE_URL}/upload`,\n method: \"post\",\n data: formData,\n headers: {\n \"Content-Type\": \"multipart/form-data\",\n },\n });\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/s3-client.ts"],"names":["BASE_URL","MB","DEFAULT_MULTIPART_THRESHOLD","DEFAULT_PART_SIZE","DEFAULT_CONCURRENCY","createErrorResult","message","status","details","createAbortError","error","getErrorMessage","fallback","resolveFilename","file","fileName","resolveContentType","shouldUseMultipartUpload","createUploadProgress","loaded","total","phase","extra","safeTotal","normalizedLoaded","loadedLimit","safeLoaded","percentage","emitUploadProgress","onProgress","progress","appendFormDataFile","formData","getHeaderValue","headers","key","headerKey","headerValue","isPromiseLike","value","isMiniProgramRequestResponse","normalizeMiniProgramRequestResponse","callMiniProgramRequest","request","options","signal","resolve","reject","settled","task","cleanup","onAbort","resolveOnce","rejectOnce","taskResult","uploadPartWithXhr","url","blob","xhr","event","eTag","uploadPartWithFetch","response","uploadPartWithMiniProgram","getMiniProgramRequest","uploadPartToPresignedUrl","detectMiniProgramPlatform","abortMultipartUpload","http","filePath","uploadId","uploadWithMultipart","initResult","totalParts","parts","workerCount","uploadAbortController","inFlightPartBytes","completedBytes","nextPartIndex","emitMultipartProgress","partNumber","inFlightBytes","sum","currentIndex","start","end","urlResult","partTotal","part","completeResult","createS3Client","createHttpClient","filename","result"],"mappings":"+DAgBA,IAAMA,CAAAA,CAAW,cAAA,CACXC,EAAK,IAAA,CAAO,IAAA,CACZC,EAA8B,EAAA,CAAKD,CAAAA,CACnCE,EAAoB,CAAA,CAAIF,CAAAA,CACxBG,EAAsB,CAAA,CA8B5B,SAASC,EACPC,CAAAA,CACAC,CAAAA,CACAC,EACiB,CACjB,OAAO,CACL,IAAA,CAAM,IAAA,CACN,MAAO,CACL,OAAA,CAAAF,EACA,MAAA,CAAAC,CAAAA,CACA,QAAAC,CACF,CAAA,CACA,OAAAD,CACF,CACF,CAEA,SAASE,CAAAA,EAA0B,CACjC,IAAMC,CAAAA,CAAQ,IAAI,MAAM,gBAAgB,CAAA,CACxC,OAAAA,CAAAA,CAAM,IAAA,CAAO,aACNA,CACT,CAEA,SAASC,CAAAA,CAAgBD,CAAAA,CAAgBE,EAA0B,CACjE,OAAIF,aAAiB,KAAA,EAASA,CAAAA,CAAM,OAAA,CAC3BA,CAAAA,CAAM,OAAA,CAEX,OAAOA,GAAU,QAAA,EAAYA,CAAAA,CACxBA,EAEFE,CACT,CAEA,SAASC,CAAAA,CAAgBC,CAAAA,CAAmBC,EAA2B,CACrE,OAAIA,GAAU,IAAA,EAAK,CACVA,EAAS,IAAA,EAAK,CAGnB,SAAUD,CAAAA,EAAQ,OAAOA,CAAAA,CAAK,IAAA,EAAS,QAAA,EAAYA,CAAAA,CAAK,KAAK,IAAA,EAAK,CAC7DA,EAAK,IAAA,CAAK,IAAA,GAEZ,MACT,CAEA,SAASE,CAAAA,CAAmBF,CAAAA,CAAuC,CACjE,OAAO,OAAOA,EAAK,IAAA,EAAS,QAAA,EAAYA,EAAK,IAAA,CAAOA,CAAAA,CAAK,IAAA,CAAO,MAClE,CAEA,SAASG,EAAyBH,CAAAA,CAA4B,CAC5D,OAAOA,CAAAA,CAAK,IAAA,EAAQZ,CACtB,CAEA,SAASgB,EACPC,CAAAA,CACAC,CAAAA,CACAC,EACAC,CAAAA,CACkB,CAClB,IAAMC,CAAAA,CAAY,MAAA,CAAO,SAASH,CAAK,CAAA,EAAKA,CAAAA,CAAQ,CAAA,CAAIA,CAAAA,CAAQ,CAAA,CAC1DI,EAAmB,MAAA,CAAO,QAAA,CAASL,CAAM,CAAA,CAAIA,CAAAA,CAAS,EACtDM,CAAAA,CAAcF,CAAAA,CAAY,EAAIA,CAAAA,CAAYC,CAAAA,CAC1CE,EAAa,IAAA,CAAK,GAAA,CAAI,EAAG,IAAA,CAAK,GAAA,CAAIF,EAAkBC,CAAW,CAAC,CAAA,CAChEE,CAAAA,CACJJ,CAAAA,CAAY,CAAA,CAAI,KAAK,GAAA,CAAI,GAAA,CAAK,KAAK,KAAA,CAAOG,CAAAA,CAAaH,EAAa,GAAK,CAAA,CAAI,GAAG,CAAA,CAAI,CAAA,CAEtF,OAAO,CACL,MAAA,CAAQG,EACR,KAAA,CAAOH,CAAAA,CACP,WAAAI,CAAAA,CACA,KAAA,CAAAN,CAAAA,CACA,GAAGC,CACL,CACF,CAEA,SAASM,CAAAA,CACPC,EACAC,CAAAA,CACM,CACND,IAAaC,CAAQ,EACvB,CAEA,SAASC,CAAAA,CACPC,EACAlB,CAAAA,CACAC,CAAAA,CACM,CACN,GAAIA,CAAAA,EAAU,MAAK,CAAG,CACpBiB,EAAS,MAAA,CAAO,MAAA,CAAQlB,EAAMC,CAAAA,CAAS,IAAA,EAAM,CAAA,CAC7C,MACF,CAEAiB,CAAAA,CAAS,MAAA,CAAO,OAAQlB,CAAI,EAC9B,CAEA,SAASmB,CAAAA,CAAeC,EAAkBC,CAAAA,CAA4B,CACpE,GAAI,CAACD,CAAAA,CACH,OAAO,IAAA,CAGT,GAAI,OAAO,QAAY,GAAA,EAAeA,CAAAA,YAAmB,QACvD,OAAOA,CAAAA,CAAQ,IAAIC,CAAG,CAAA,CAGxB,GAAI,OAAOD,CAAAA,EAAY,UACrB,IAAA,GAAW,CAACE,EAAWC,CAAW,CAAA,GAAK,OAAO,OAAA,CAC5CH,CACF,CAAA,CACE,GAAIE,CAAAA,CAAU,WAAA,KAAkBD,CAAAA,CAAI,WAAA,GAClC,OAAOE,CAAAA,EAAe,KAAO,IAAA,CAAO,MAAA,CAAOA,CAAW,CAAA,CAK5D,OAAO,IACT,CAEA,SAASC,EAA2BC,CAAAA,CAAyC,CAC3E,QACG,OAAOA,CAAAA,EAAU,QAAA,EAAY,OAAOA,CAAAA,EAAU,UAAA,GAC/CA,IAAU,IAAA,EACV,MAAA,GAAUA,GACV,OAAQA,CAAAA,CAA6B,MAAS,UAElD,CAEA,SAASC,CAAAA,CACPD,CAAAA,CACqC,CACrC,OACE,OAAOA,GAAU,QAAA,EACjBA,CAAAA,GAAU,OACT,YAAA,GAAgBA,CAAAA,EACf,QAAA,GAAYA,CAAAA,EACZ,MAAA,GAAUA,CAAAA,EACV,WAAYA,CAAAA,EACZ,SAAA,GAAaA,EAEnB,CAEA,SAASE,EACPF,CAAAA,CAC4B,CAC5B,OAAOC,CAAAA,CAA6BD,CAAK,EAAIA,CAAAA,CAAQ,EACvD,CAEA,eAAeG,EACbC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACqC,CACrC,OAAO,MAAM,IAAI,OAAA,CAAoC,CAACC,EAASC,CAAAA,GAAW,CACxE,IAAIC,CAAAA,CAAU,KAAA,CACVC,EAA6B,IAAA,CAE3BC,CAAAA,CAAU,IAAM,CACpBL,CAAAA,EAAQ,oBAAoB,OAAA,CAASM,CAAO,EAC9C,CAAA,CAEMC,CAAAA,CAAeb,CAAAA,EAAsC,CACrDS,CAAAA,GAGJA,CAAAA,CAAU,KACVE,CAAAA,EAAQ,CACRJ,EAAQP,CAAK,CAAA,EACf,EAEMc,CAAAA,CAAc3C,CAAAA,EAAmB,CACjCsC,CAAAA,GAGJA,CAAAA,CAAU,KACVE,CAAAA,EAAQ,CACRH,EAAOrC,CAAK,CAAA,EACd,EAEMyC,CAAAA,CAAU,IAAM,CACpB,GAAI,CACFF,CAAAA,EAAM,UACR,CAAA,KAAQ,CAER,CACAI,CAAAA,CAAW5C,GAAkB,EAC/B,EAEA,GAAIoC,CAAAA,EAAQ,QAAS,CACnBQ,CAAAA,CAAW5C,GAAkB,CAAA,CAC7B,MACF,CAEAoC,CAAAA,EAAQ,iBAAiB,OAAA,CAASM,CAAAA,CAAS,CAAE,IAAA,CAAM,IAAK,CAAC,CAAA,CAEzD,GAAI,CACF,IAAMG,CAAAA,CAAaX,EAAQ,CACzB,GAAGC,EACH,OAAA,CAAUL,CAAAA,EAAmB,CAC3Ba,CAAAA,CAAYX,CAAAA,CAAoCF,CAAK,CAAC,EACxD,CAAA,CACA,IAAA,CAAMc,CACR,CAAC,EAMD,GAJI,OAAOC,GAAe,QAAA,EAAYA,CAAAA,GAAe,OACnDL,CAAAA,CAAOK,CAAAA,CAAAA,CAGLhB,EAAcgB,CAAU,CAAA,CAAG,CACxBA,CAAAA,CAAW,IAAA,CACbf,GAAU,CACTa,CAAAA,CAAYX,EAAoCF,CAAK,CAAC,EACxD,CAAA,CACAc,CACF,CAAA,CACA,MACF,CAEIb,CAAAA,CAA6Bc,CAAU,CAAA,EACzCF,CAAAA,CAAYE,CAAU,EAE1B,CAAA,MAAS5C,EAAO,CACd2C,CAAAA,CAAW3C,CAAK,EAClB,CACF,CAAC,CACH,CAEA,eAAe6C,CAAAA,CACbC,CAAAA,CACAC,CAAAA,CACAZ,CAAAA,CACAhB,CAAAA,CACiB,CACjB,GAAI,OAAO,cAAA,EAAmB,WAC5B,MAAM,IAAI,MAAM,uDAAuD,CAAA,CAGzE,OAAO,MAAM,IAAI,QAAgB,CAACiB,CAAAA,CAASC,IAAW,CACpD,IAAIC,EAAU,KAAA,CACRU,CAAAA,CAAM,IAAI,cAAA,CAEVR,CAAAA,CAAU,IAAM,CACpBL,CAAAA,EAAQ,mBAAA,CAAoB,QAASM,CAAO,EAC9C,EAEMC,CAAAA,CAAeb,CAAAA,EAAkB,CACjCS,CAAAA,GAGJA,CAAAA,CAAU,KACVE,CAAAA,EAAQ,CACRJ,EAAQP,CAAK,CAAA,EACf,EAEMc,CAAAA,CAAc3C,CAAAA,EAAmB,CACjCsC,CAAAA,GAGJA,CAAAA,CAAU,IAAA,CACVE,GAAQ,CACRH,CAAAA,CAAOrC,CAAK,CAAA,EACd,CAAA,CAEMyC,EAAU,IAAM,CACpB,GAAI,CACFO,CAAAA,CAAI,QACN,CAAA,KAAQ,CAER,CACAL,CAAAA,CAAW5C,GAAkB,EAC/B,CAAA,CAEA,GAAIoC,CAAAA,EAAQ,OAAA,CAAS,CACnBQ,CAAAA,CAAW5C,CAAAA,EAAkB,CAAA,CAC7B,MACF,CAEAoC,CAAAA,EAAQ,gBAAA,CAAiB,QAASM,CAAAA,CAAS,CAAE,KAAM,IAAK,CAAC,EAEzDO,CAAAA,CAAI,MAAA,CAAO,WAAcC,CAAAA,EAAU,CACjC,IAAMvC,CAAAA,CAAQuC,CAAAA,CAAM,gBAAA,CAAmBA,EAAM,KAAA,CAAQF,CAAAA,CAAK,KAC1D5B,CAAAA,GAAa8B,CAAAA,CAAM,OAAQvC,CAAK,EAClC,EAEAsC,CAAAA,CAAI,MAAA,CAAS,IAAM,CACjB,GAAIA,EAAI,MAAA,CAAS,GAAA,EAAOA,EAAI,MAAA,EAAU,GAAA,CAAK,CACzC,IAAMlD,CAAAA,CAAUkD,CAAAA,CAAI,aAAe,CAAA,EAAA,EAAKA,CAAAA,CAAI,YAAY,CAAA,CAAA,CAAK,EAAA,CAC7DL,EACE,IAAI,KAAA,CAAM,iCAAiCK,CAAAA,CAAI,MAAM,IAAIlD,CAAO,CAAA,CAAE,CACpE,CAAA,CACA,MACF,CAEA,IAAMoD,CAAAA,CAAOF,CAAAA,CAAI,iBAAA,CAAkB,MAAM,CAAA,CACzC,GAAI,CAACE,CAAAA,CAAM,CACTP,CAAAA,CACE,IAAI,MACF,0FACF,CACF,EACA,MACF,CAEAxB,IAAa4B,CAAAA,CAAK,IAAA,CAAMA,EAAK,IAAI,CAAA,CACjCL,EAAYQ,CAAI,EAClB,CAAA,CAEAF,CAAAA,CAAI,OAAA,CAAU,IAAM,CAClBL,CAAAA,CAAW,IAAI,MAAM,8CAA8C,CAAC,EACtE,CAAA,CAEAK,CAAAA,CAAI,QAAU,IAAM,CAClBL,EAAW5C,CAAAA,EAAkB,EAC/B,CAAA,CAEAiD,CAAAA,CAAI,KAAK,KAAA,CAAOF,CAAG,CAAA,CACnBE,CAAAA,CAAI,IAAA,CAAKD,CAAI,EACf,CAAC,CACH,CAEA,eAAeI,CAAAA,CACbL,EACAC,CAAAA,CACAZ,CAAAA,CACiB,CACjB,GAAI,OAAO,OAAU,UAAA,CACnB,MAAM,IAAI,KAAA,CAAM,kDAAkD,EAGpE,IAAMiB,CAAAA,CAAW,MAAM,KAAA,CAAMN,CAAAA,CAAK,CAChC,OAAQ,KAAA,CACR,IAAA,CAAMC,EACN,MAAA,CAAAZ,CACF,CAAC,CAAA,CAED,GAAI,CAACiB,CAAAA,CAAS,EAAA,CAAI,CAChB,IAAItD,CAAAA,CAAU,GACd,GAAI,CACFA,EAAU,MAAMsD,CAAAA,CAAS,IAAA,GAC3B,CAAA,KAAQ,CAER,CACA,MAAM,IAAI,MACR,CAAA,8BAAA,EAAiCA,CAAAA,CAAS,MAAM,CAAA,CAAA,EAAItD,CAAAA,CAAU,KAAKA,CAAO,CAAA,CAAA,CAAK,EAAE,CAAA,CACnF,CACF,CAEA,IAAMoD,CAAAA,CAAO3B,EAAe6B,CAAAA,CAAS,OAAA,CAAS,MAAM,CAAA,CACpD,GAAI,CAACF,EACH,MAAM,IAAI,MACR,0FACF,CAAA,CAGF,OAAOA,CACT,CAEA,eAAeG,CAAAA,CACbP,CAAAA,CACAC,EACAZ,CAAAA,CACiB,CACjB,IAAMF,CAAAA,CAAUqB,gCAAAA,GAChB,GAAI,CAACrB,CAAAA,CACH,MAAM,IAAI,KAAA,CAAM,iEAAiE,CAAA,CAGnF,IAAMmB,EAAW,MAAMpB,CAAAA,CACrBC,EACA,CACE,GAAA,CAAAa,EACA,MAAA,CAAQ,KAAA,CACR,KAAM,MAAMC,CAAAA,CAAK,aAAY,CAC7B,MAAA,CAAQ,EACV,CAAA,CACAZ,CACF,CAAA,CAEMtC,CAAAA,CAAS,OAAOuD,CAAAA,EAAU,UAAA,EAAcA,GAAU,MAAA,EAAU,CAAC,EACnE,GAAIvD,CAAAA,CAAS,KAAOA,CAAAA,EAAU,GAAA,CAAK,CACjC,IAAMC,CAAAA,CACJ,OAAOsD,CAAAA,EAAU,IAAA,EAAS,UAAYA,CAAAA,CAAS,IAAA,CAC3C,CAAA,EAAA,EAAKA,CAAAA,CAAS,IAAI,CAAA,CAAA,CAClB,GACN,MAAM,IAAI,MACR,CAAA,8BAAA,EAAiCvD,CAAAA,EAAU,SAAS,CAAA,CAAA,EAAIC,CAAO,EACjE,CACF,CAEA,IAAMoD,CAAAA,CAAO3B,CAAAA,CAAe6B,GAAU,MAAA,EAAUA,CAAAA,EAAU,QAAS,MAAM,CAAA,CACzE,GAAI,CAACF,CAAAA,CACH,MAAM,IAAI,KAAA,CACR,0FACF,EAGF,OAAOA,CACT,CAEA,eAAeK,CAAAA,CACbT,EACAC,CAAAA,CACAZ,CAAAA,CACAhB,EACiB,CACjB,OAAIqC,sCAA0B,CACrB,MAAMH,EAA0BP,CAAAA,CAAKC,CAAAA,CAAMZ,CAAM,CAAA,CAGtDhB,CAAAA,EAAc,OAAO,gBAAmB,UAAA,CACnC,MAAM0B,EAAkBC,CAAAA,CAAKC,CAAAA,CAAMZ,EAAQhB,CAAU,CAAA,CAGvD,MAAMgC,CAAAA,CAAoBL,CAAAA,CAAKC,EAAMZ,CAAM,CACpD,CAEA,eAAesB,CAAAA,CACbC,EACAC,CAAAA,CACAC,CAAAA,CACe,CACf,GAAI,CACF,MAAMF,EAAK,OAAA,CAAc,CACvB,IAAK,CAAA,EAAGpE,CAAQ,0BAChB,MAAA,CAAQ,MAAA,CACR,KAAM,CACJ,QAAA,CAAAqE,EACA,QAAA,CAAAC,CACF,CACF,CAAC,EACH,MAAQ,CAER,CACF,CAEA,eAAeC,CAAAA,CACbH,CAAAA,CACAtD,EACA8B,CAAAA,CAA2B,GACO,CAClChB,CAAAA,CACEgB,EAAQ,UAAA,CACR1B,CAAAA,CAAqB,EAAGJ,CAAAA,CAAK,IAAA,CAAM,WAAW,CAChD,CAAA,CAEA,IAAM0D,CAAAA,CAAa,MAAMJ,EAAK,OAAA,CAA0B,CACtD,GAAA,CAAK,CAAA,EAAGpE,CAAQ,CAAA,sBAAA,CAAA,CAChB,OAAQ,MAAA,CACR,IAAA,CAAM,CACJ,QAAA,CAAUa,CAAAA,CAAgBC,EAAM8B,CAAAA,CAAQ,QAAQ,EAChD,WAAA,CAAa5B,CAAAA,CAAmBF,CAAI,CAAA,CACpC,GAAI8B,EAAQ,QAAA,CAAW,CAAE,SAAUA,CAAAA,CAAQ,QAAS,CAAA,CAAI,EAC1D,CACF,CAAC,CAAA,CAED,GAAI4B,EAAW,KAAA,CACb,OAAOnE,EACLmE,CAAAA,CAAW,KAAA,CAAM,QACjBA,CAAAA,CAAW,MAAA,EAAU,IACrBA,CAAAA,CAAW,KAAA,CAAM,OACnB,CAAA,CAGF,GAAI,CAACA,CAAAA,CAAW,IAAA,EAAM,QAAA,EAAY,CAACA,CAAAA,CAAW,IAAA,EAAM,SAClD,OAAOnE,CAAAA,CACL,sDACAmE,CAAAA,CAAW,MAAA,EAAU,GACvB,CAAA,CAGF,GAAM,CAAE,QAAA,CAAAF,CAAAA,CAAU,SAAAD,CAAS,CAAA,CAAIG,EAAW,IAAA,CACpCC,CAAAA,CAAa,KAAK,GAAA,CAAI,CAAA,CAAG,IAAA,CAAK,IAAA,CAAK3D,CAAAA,CAAK,IAAA,CAAOX,CAAiB,CAAC,CAAA,CACjEuE,EAAoB,IAAI,KAAA,CAAMD,CAAU,CAAA,CACxCE,CAAAA,CAAc,KAAK,GAAA,CAAIvE,CAAAA,CAAqBqE,CAAU,CAAA,CACtDG,CAAAA,CAAwB,IAAI,eAAA,CAC5BC,CAAAA,CAAoB,IAAI,KAAA,CAAcJ,CAAU,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,CAC1DK,EAAiB,CAAA,CACjBC,CAAAA,CAAgB,EAEdC,CAAAA,CAAwB,CAC5B3D,EACA4D,CAAAA,GACG,CACH,IAAMC,CAAAA,CAAgBL,CAAAA,CAAkB,OAAO,CAACM,CAAAA,CAAKhE,IAAWgE,CAAAA,CAAMhE,CAAAA,CAAQ,CAAC,CAAA,CAC/ES,CAAAA,CACEgB,CAAAA,CAAQ,UAAA,CACR1B,CAAAA,CACE,IAAA,CAAK,IAAIJ,CAAAA,CAAK,IAAA,CAAMgE,EAAiBI,CAAa,CAAA,CAClDpE,EAAK,IAAA,CACLO,CAAAA,CACA,CACE,GAAI4D,CAAAA,CAAa,CAAE,UAAA,CAAAA,CAAW,EAAI,EAAC,CACnC,WAAAR,CACF,CACF,CACF,EACF,CAAA,CAEAO,CAAAA,CAAsB,WAAW,CAAA,CAEjC,GAAI,CA4DF,GA3DA,MAAM,QAAQ,GAAA,CACZ,KAAA,CAAM,KAAK,CAAE,MAAA,CAAQL,CAAY,CAAA,CAAG,SAAY,CAC9C,OAAa,CACX,IAAMS,CAAAA,CAAeL,CAAAA,CAGrB,GAFAA,CAAAA,EAAiB,CAAA,CAEbK,CAAAA,EAAgBX,EAClB,OAGF,IAAMQ,EAAaG,CAAAA,CAAe,CAAA,CAC5BC,EAAQD,CAAAA,CAAejF,CAAAA,CACvBmF,EAAM,IAAA,CAAK,GAAA,CAAID,EAAQlF,CAAAA,CAAmBW,CAAAA,CAAK,IAAI,CAAA,CACnD2C,CAAAA,CAAO3C,EAAK,KAAA,CAAMuE,CAAAA,CAAOC,CAAG,CAAA,CAE5BC,CAAAA,CAAY,MAAMnB,EAAK,OAAA,CAAyB,CACpD,IAAK,CAAA,EAAGpE,CAAQ,wBAChB,MAAA,CAAQ,MAAA,CACR,KAAM,CACJ,QAAA,CAAAqE,EACA,QAAA,CAAAC,CAAAA,CACA,WAAAW,CACF,CAAA,CACA,OAAQL,CAAAA,CAAsB,MAChC,CAAC,CAAA,CAED,GAAIW,CAAAA,CAAU,MACZ,MAAM,IAAI,MAAMA,CAAAA,CAAU,KAAA,CAAM,OAAO,CAAA,CAGzC,GAAI,CAACA,CAAAA,CAAU,IAAA,EAAM,IACnB,MAAM,IAAI,MACR,CAAA,mEAAA,EAAsEN,CAAU,GAClF,CAAA,CAGF,IAAMrB,EAAO,MAAMK,CAAAA,CACjBsB,EAAU,IAAA,CAAK,GAAA,CACf9B,EACAmB,CAAAA,CAAsB,MAAA,CACtB,CAACzD,CAAAA,CAAQC,CAAAA,GAAU,CACjB,IAAMoE,CAAAA,CAAYpE,GAASA,CAAAA,CAAQ,CAAA,CAAIA,EAAQqC,CAAAA,CAAK,IAAA,CACpDoB,EAAkBO,CAAY,CAAA,CAAI,IAAA,CAAK,GAAA,CAAIjE,CAAAA,CAAQqE,CAAAA,CAAW/B,EAAK,IAAI,CAAA,CACvEuB,EAAsB,WAAA,CAAaC,CAAU,EAC/C,CACF,CAAA,CAEAJ,EAAkBO,CAAY,CAAA,CAAI,EAClCN,CAAAA,EAAkBrB,CAAAA,CAAK,KACvBuB,CAAAA,CAAsB,WAAA,CAAaC,CAAU,CAAA,CAE7CP,CAAAA,CAAMU,CAAY,CAAA,CAAI,CACpB,UAAA,CAAAH,EACA,IAAA,CAAArB,CACF,EACF,CACF,CAAC,CACH,CAAA,CAEIc,CAAAA,CAAM,KAAMe,CAAAA,EAAS,CAACA,GAAM,IAAI,CAAA,CAClC,MAAM,IAAI,KAAA,CAAM,2DAA2D,CAAA,CAG7EX,CAAAA,CAAiBhE,CAAAA,CAAK,IAAA,CACtB+D,CAAAA,CAAkB,IAAA,CAAK,CAAC,CAAA,CACxBG,CAAAA,CAAsB,YAAY,CAAA,CAElC,IAAMU,EAAiB,MAAMtB,CAAAA,CAAK,QAAmB,CACnD,GAAA,CAAK,GAAGpE,CAAQ,CAAA,0BAAA,CAAA,CAChB,OAAQ,MAAA,CACR,IAAA,CAAM,CACJ,QAAA,CAAAqE,CAAAA,CACA,QAAA,CAAAC,CAAAA,CACA,KAAA,CAAAI,CACF,CACF,CAAC,CAAA,CAED,OAAIgB,CAAAA,CAAe,KAAA,EACjB,MAAMvB,CAAAA,CAAqBC,CAAAA,CAAMC,EAAUC,CAAQ,CAAA,CAC5CjE,EACLqF,CAAAA,CAAe,KAAA,CAAM,QACrBA,CAAAA,CAAe,MAAA,EAAU,IACzBA,CAAAA,CAAe,KAAA,CAAM,OACvB,CAAA,EAGGA,CAAAA,CAAe,IAAA,EAQpBV,EAAsB,WAAW,CAAA,CAC1BU,IARL,MAAMvB,CAAAA,CAAqBC,EAAMC,CAAAA,CAAUC,CAAQ,EAC5CjE,CAAAA,CACL,uDAAA,CACAqF,EAAe,MAAA,EAAU,GAC3B,EAKJ,CAAA,MAAShF,CAAAA,CAAO,CACd,OAAAkE,CAAAA,CAAsB,KAAA,EAAM,CAC5B,MAAMT,CAAAA,CAAqBC,EAAMC,CAAAA,CAAUC,CAAQ,EAC5CjE,CAAAA,CACLM,CAAAA,CAAgBD,EAAO,yBAAyB,CAAA,CAChDA,aAAiB,KAAA,EAASA,CAAAA,CAAM,OAAS,YAAA,CAAe,GAAA,CAAM,IAC9DA,CACF,CACF,CACF,CAEO,SAASiF,EAAAA,CAAevB,CAAAA,CAAmBwB,2BAAAA,EAAiB,CAAa,CAC9E,OAAO,CACL,MAAM,QAAA,CAASC,CAAAA,CAA+C,CAC5D,OAAKA,CAAAA,CAQU,MAAMzB,CAAAA,CAAK,OAAA,CAAc,CACtC,GAAA,CAAK,CAAA,EAAGpE,CAAQ,CAAA,SAAA,CAAA,CAChB,MAAA,CAAQ,MACR,MAAA,CAAQ,CAAE,QAAA,CAAA6F,CAAS,CAAA,CACnB,YAAA,CAAc,MAChB,CAAC,CAAA,CAZQ,CACL,IAAA,CAAM,IAAA,CACN,MAAO,CAAE,OAAA,CAAS,uBAAwB,MAAA,CAAQ,GAAI,EACtD,MAAA,CAAQ,GACV,CAWJ,CAAA,CAEA,MAAM,YAAY1D,CAAAA,CAAgD,CAChE,OAAKA,CAAAA,CAQEiC,CAAAA,CAAK,OAAA,CAAoB,CAC9B,GAAA,CAAK,CAAA,EAAGpE,CAAQ,CAAA,SAAA,CAAA,CAChB,MAAA,CAAQ,MACR,MAAA,CAAQ,CAAE,IAAAmC,CAAI,CAChB,CAAC,CAAA,CAXQ,CACL,KAAM,IAAA,CACN,KAAA,CAAO,CAAE,OAAA,CAAS,iBAAA,CAAmB,MAAA,CAAQ,GAAI,CAAA,CACjD,MAAA,CAAQ,GACV,CAQJ,CAAA,CAEA,MAAM,MAAA,CACJrB,CAAAA,CACA8B,EAA2B,EAAC,CACM,CAClC,GAAI,CAAC9B,EACH,OAAO,CACL,KAAM,IAAA,CACN,KAAA,CAAO,CAAE,OAAA,CAAS,kBAAA,CAAoB,MAAA,CAAQ,GAAI,CAAA,CAClD,MAAA,CAAQ,GACV,CAAA,CAGF,GAAIG,EAAyBH,CAAI,CAAA,CAC/B,OAAO,MAAMyD,CAAAA,CAAoBH,EAAMtD,CAAAA,CAAM8B,CAAO,EAGtD,IAAMZ,CAAAA,CAAW,IAAI,QAAA,CACrBD,CAAAA,CAAmBC,EAAUlB,CAAAA,CAAM8B,CAAAA,CAAQ,QAAQ,CAAA,CAE/CA,CAAAA,CAAQ,QAAA,EACVZ,EAAS,MAAA,CAAO,UAAA,CAAYY,EAAQ,QAAQ,CAAA,CAG9ChB,EACEgB,CAAAA,CAAQ,UAAA,CACR1B,EAAqB,CAAA,CAAGJ,CAAAA,CAAK,KAAM,WAAW,CAChD,EAEA,IAAMgF,CAAAA,CAAS,MAAM1B,CAAAA,CAAK,OAAA,CAAmB,CAC3C,GAAA,CAAK,CAAA,EAAGpE,CAAQ,UAChB,MAAA,CAAQ,MAAA,CACR,KAAMgC,CAAAA,CACN,OAAA,CAAS,CACP,cAAA,CAAgB,qBAClB,EACA,gBAAA,CAAkBY,CAAAA,CAAQ,WACrBe,CAAAA,EAAU,CACT/B,EACEgB,CAAAA,CAAQ,UAAA,CACR1B,EACEyC,CAAAA,CAAM,MAAA,CACNA,CAAAA,CAAM,KAAA,EAAS7C,CAAAA,CAAK,IAAA,CACpB,WACF,CACF,EACF,EACA,MACN,CAAC,EAED,OAAKgF,CAAAA,CAAO,OACVlE,CAAAA,CACEgB,CAAAA,CAAQ,WACR1B,CAAAA,CAAqBJ,CAAAA,CAAK,KAAMA,CAAAA,CAAK,IAAA,CAAM,WAAW,CACxD,CAAA,CAGKgF,CACT,CACF,CACF","file":"index.cjs","sourcesContent":["import {\n createHttpClient,\n detectMiniProgramPlatform,\n getMiniProgramRequest,\n type ClientResult,\n type HttpClient,\n} from \"@amaster.ai/http-client\";\nimport type {\n S3Client,\n S3Metadata,\n S3UploadOptions,\n S3UploadProgress,\n S3UploadProgressPhase,\n UploadRes,\n} from \"./types\";\n\nconst BASE_URL = \"/api/storage\";\nconst MB = 1024 * 1024;\nconst DEFAULT_MULTIPART_THRESHOLD = 32 * MB;\nconst DEFAULT_PART_SIZE = 8 * MB;\nconst DEFAULT_CONCURRENCY = 3;\n\ntype MultipartInitRes = {\n uploadId: string;\n filePath: string;\n};\n\ntype MultipartUrlRes = {\n url: string;\n};\n\ntype PartETag = {\n partNumber: number;\n eTag: string;\n};\n\ntype MiniProgramRequestResponse = {\n statusCode?: unknown;\n status?: unknown;\n data?: unknown;\n header?: unknown;\n headers?: unknown;\n};\n\ntype AbortableTask = {\n abort?: () => void;\n};\n\ntype UploadProgressHandler = (loaded: number, total?: number) => void;\n\nfunction createErrorResult<T>(\n message: string,\n status: number,\n details?: unknown,\n): ClientResult<T> {\n return {\n data: null,\n error: {\n message,\n status,\n details,\n },\n status,\n };\n}\n\nfunction createAbortError(): Error {\n const error = new Error(\"Upload aborted\");\n error.name = \"AbortError\";\n return error;\n}\n\nfunction getErrorMessage(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n if (typeof error === \"string\" && error) {\n return error;\n }\n return fallback;\n}\n\nfunction resolveFilename(file: File | Blob, fileName?: string): string {\n if (fileName?.trim()) {\n return fileName.trim();\n }\n\n if (\"name\" in file && typeof file.name === \"string\" && file.name.trim()) {\n return file.name.trim();\n }\n return \"blob\";\n}\n\nfunction resolveContentType(file: File | Blob): string | undefined {\n return typeof file.type === \"string\" && file.type ? file.type : undefined;\n}\n\nfunction shouldUseMultipartUpload(file: File | Blob): boolean {\n return file.size >= DEFAULT_MULTIPART_THRESHOLD;\n}\n\nfunction createUploadProgress(\n loaded: number,\n total: number,\n phase: S3UploadProgressPhase,\n extra?: Pick<S3UploadProgress, \"partNumber\" | \"totalParts\">,\n): S3UploadProgress {\n const safeTotal = Number.isFinite(total) && total > 0 ? total : 0;\n const normalizedLoaded = Number.isFinite(loaded) ? loaded : 0;\n const loadedLimit = safeTotal > 0 ? safeTotal : normalizedLoaded;\n const safeLoaded = Math.max(0, Math.min(normalizedLoaded, loadedLimit));\n const percentage =\n safeTotal > 0 ? Math.min(100, Math.round((safeLoaded / safeTotal) * 10000) / 100) : 0;\n\n return {\n loaded: safeLoaded,\n total: safeTotal,\n percentage,\n phase,\n ...extra,\n };\n}\n\nfunction emitUploadProgress(\n onProgress: S3UploadOptions[\"onProgress\"] | undefined,\n progress: S3UploadProgress,\n): void {\n onProgress?.(progress);\n}\n\nfunction appendFormDataFile(\n formData: FormData,\n file: File | Blob,\n fileName?: string,\n): void {\n if (fileName?.trim()) {\n formData.append(\"file\", file, fileName.trim());\n return;\n }\n\n formData.append(\"file\", file);\n}\n\nfunction getHeaderValue(headers: unknown, key: string): string | null {\n if (!headers) {\n return null;\n }\n\n if (typeof Headers !== \"undefined\" && headers instanceof Headers) {\n return headers.get(key);\n }\n\n if (typeof headers === \"object\") {\n for (const [headerKey, headerValue] of Object.entries(\n headers as Record<string, unknown>,\n )) {\n if (headerKey.toLowerCase() === key.toLowerCase()) {\n return headerValue == null ? null : String(headerValue);\n }\n }\n }\n\n return null;\n}\n\nfunction isPromiseLike<T = unknown>(value: unknown): value is PromiseLike<T> {\n return (\n (typeof value === \"object\" || typeof value === \"function\") &&\n value !== null &&\n \"then\" in value &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\nfunction isMiniProgramRequestResponse(\n value: unknown,\n): value is MiniProgramRequestResponse {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (\"statusCode\" in value ||\n \"status\" in value ||\n \"data\" in value ||\n \"header\" in value ||\n \"headers\" in value)\n );\n}\n\nfunction normalizeMiniProgramRequestResponse(\n value: unknown,\n): MiniProgramRequestResponse {\n return isMiniProgramRequestResponse(value) ? value : {};\n}\n\nasync function callMiniProgramRequest(\n request: (_options: Record<string, unknown>) => unknown,\n options: Record<string, unknown>,\n signal?: AbortSignal,\n): Promise<MiniProgramRequestResponse> {\n return await new Promise<MiniProgramRequestResponse>((resolve, reject) => {\n let settled = false;\n let task: AbortableTask | null = null;\n\n const cleanup = () => {\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n const resolveOnce = (value: MiniProgramRequestResponse) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const rejectOnce = (error: unknown) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n reject(error);\n };\n\n const onAbort = () => {\n try {\n task?.abort?.();\n } catch {\n // ignore abort failures from runtime-specific request tasks\n }\n rejectOnce(createAbortError());\n };\n\n if (signal?.aborted) {\n rejectOnce(createAbortError());\n return;\n }\n\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n try {\n const taskResult = request({\n ...options,\n success: (value: unknown) => {\n resolveOnce(normalizeMiniProgramRequestResponse(value));\n },\n fail: rejectOnce,\n });\n\n if (typeof taskResult === \"object\" && taskResult !== null) {\n task = taskResult as AbortableTask;\n }\n\n if (isPromiseLike(taskResult)) {\n void taskResult.then(\n (value) => {\n resolveOnce(normalizeMiniProgramRequestResponse(value));\n },\n rejectOnce,\n );\n return;\n }\n\n if (isMiniProgramRequestResponse(taskResult)) {\n resolveOnce(taskResult);\n }\n } catch (error) {\n rejectOnce(error);\n }\n });\n}\n\nasync function uploadPartWithXhr(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n onProgress?: UploadProgressHandler,\n): Promise<string> {\n if (typeof XMLHttpRequest !== \"function\") {\n throw new Error(\"XMLHttpRequest is not available for multipart upload.\");\n }\n\n return await new Promise<string>((resolve, reject) => {\n let settled = false;\n const xhr = new XMLHttpRequest();\n\n const cleanup = () => {\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n const resolveOnce = (value: string) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const rejectOnce = (error: unknown) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n reject(error);\n };\n\n const onAbort = () => {\n try {\n xhr.abort();\n } catch {\n // ignore abort failures from runtime-specific XHR implementations\n }\n rejectOnce(createAbortError());\n };\n\n if (signal?.aborted) {\n rejectOnce(createAbortError());\n return;\n }\n\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n xhr.upload.onprogress = (event) => {\n const total = event.lengthComputable ? event.total : blob.size;\n onProgress?.(event.loaded, total);\n };\n\n xhr.onload = () => {\n if (xhr.status < 200 || xhr.status >= 300) {\n const details = xhr.responseText ? `: ${xhr.responseText}` : \"\";\n rejectOnce(\n new Error(`Multipart part upload failed (${xhr.status})${details}`),\n );\n return;\n }\n\n const eTag = xhr.getResponseHeader(\"etag\");\n if (!eTag) {\n rejectOnce(\n new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n ),\n );\n return;\n }\n\n onProgress?.(blob.size, blob.size);\n resolveOnce(eTag);\n };\n\n xhr.onerror = () => {\n rejectOnce(new Error(\"Multipart part upload failed (network error)\"));\n };\n\n xhr.onabort = () => {\n rejectOnce(createAbortError());\n };\n\n xhr.open(\"PUT\", url);\n xhr.send(blob);\n });\n}\n\nasync function uploadPartWithFetch(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n if (typeof fetch !== \"function\") {\n throw new Error(\"Fetch API is not available for multipart upload.\");\n }\n\n const response = await fetch(url, {\n method: \"PUT\",\n body: blob,\n signal,\n });\n\n if (!response.ok) {\n let details = \"\";\n try {\n details = await response.text();\n } catch {\n // ignore response body parse failures\n }\n throw new Error(\n `Multipart part upload failed (${response.status})${details ? `: ${details}` : \"\"}`,\n );\n }\n\n const eTag = getHeaderValue(response.headers, \"etag\");\n if (!eTag) {\n throw new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n );\n }\n\n return eTag;\n}\n\nasync function uploadPartWithMiniProgram(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n const request = getMiniProgramRequest();\n if (!request) {\n throw new Error(\"Mini-program request API is not available for multipart upload.\");\n }\n\n const response = await callMiniProgramRequest(\n request,\n {\n url,\n method: \"PUT\",\n data: await blob.arrayBuffer(),\n header: {},\n },\n signal,\n );\n\n const status = Number(response?.statusCode ?? response?.status ?? 0);\n if (status < 200 || status >= 300) {\n const details =\n typeof response?.data === \"string\" && response.data\n ? `: ${response.data}`\n : \"\";\n throw new Error(\n `Multipart part upload failed (${status || \"unknown\"})${details}`,\n );\n }\n\n const eTag = getHeaderValue(response?.header ?? response?.headers, \"etag\");\n if (!eTag) {\n throw new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n );\n }\n\n return eTag;\n}\n\nasync function uploadPartToPresignedUrl(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n onProgress?: UploadProgressHandler,\n): Promise<string> {\n if (detectMiniProgramPlatform()) {\n return await uploadPartWithMiniProgram(url, blob, signal);\n }\n\n if (onProgress && typeof XMLHttpRequest === \"function\") {\n return await uploadPartWithXhr(url, blob, signal, onProgress);\n }\n\n return await uploadPartWithFetch(url, blob, signal);\n}\n\nasync function abortMultipartUpload(\n http: HttpClient,\n filePath: string,\n uploadId: string,\n): Promise<void> {\n try {\n await http.request<void>({\n url: `${BASE_URL}/upload/multipart/abort`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n },\n });\n } catch {\n // Best-effort cleanup only.\n }\n}\n\nasync function uploadWithMultipart(\n http: HttpClient,\n file: File | Blob,\n options: S3UploadOptions = {},\n): Promise<ClientResult<UploadRes>> {\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(0, file.size, \"preparing\"),\n );\n\n const initResult = await http.request<MultipartInitRes>({\n url: `${BASE_URL}/upload/multipart/init`,\n method: \"post\",\n data: {\n filename: resolveFilename(file, options.fileName),\n contentType: resolveContentType(file),\n ...(options.category ? { category: options.category } : {}),\n },\n });\n\n if (initResult.error) {\n return createErrorResult(\n initResult.error.message,\n initResult.status || 500,\n initResult.error.details,\n );\n }\n\n if (!initResult.data?.uploadId || !initResult.data?.filePath) {\n return createErrorResult(\n \"Multipart upload init returned an invalid response.\",\n initResult.status || 500,\n );\n }\n\n const { uploadId, filePath } = initResult.data;\n const totalParts = Math.max(1, Math.ceil(file.size / DEFAULT_PART_SIZE));\n const parts: PartETag[] = new Array(totalParts);\n const workerCount = Math.min(DEFAULT_CONCURRENCY, totalParts);\n const uploadAbortController = new AbortController();\n const inFlightPartBytes = new Array<number>(totalParts).fill(0);\n let completedBytes = 0;\n let nextPartIndex = 0;\n\n const emitMultipartProgress = (\n phase: S3UploadProgressPhase,\n partNumber?: number,\n ) => {\n const inFlightBytes = inFlightPartBytes.reduce((sum, loaded) => sum + loaded, 0);\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(\n Math.min(file.size, completedBytes + inFlightBytes),\n file.size,\n phase,\n {\n ...(partNumber ? { partNumber } : {}),\n totalParts,\n },\n ),\n );\n };\n\n emitMultipartProgress(\"uploading\");\n\n try {\n await Promise.all(\n Array.from({ length: workerCount }, async () => {\n while (true) {\n const currentIndex = nextPartIndex;\n nextPartIndex += 1;\n\n if (currentIndex >= totalParts) {\n return;\n }\n\n const partNumber = currentIndex + 1;\n const start = currentIndex * DEFAULT_PART_SIZE;\n const end = Math.min(start + DEFAULT_PART_SIZE, file.size);\n const blob = file.slice(start, end);\n\n const urlResult = await http.request<MultipartUrlRes>({\n url: `${BASE_URL}/upload/multipart/url`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n partNumber,\n },\n signal: uploadAbortController.signal,\n });\n\n if (urlResult.error) {\n throw new Error(urlResult.error.message);\n }\n\n if (!urlResult.data?.url) {\n throw new Error(\n `Multipart upload URL request returned an invalid response for part ${partNumber}.`,\n );\n }\n\n const eTag = await uploadPartToPresignedUrl(\n urlResult.data.url,\n blob,\n uploadAbortController.signal,\n (loaded, total) => {\n const partTotal = total && total > 0 ? total : blob.size;\n inFlightPartBytes[currentIndex] = Math.min(loaded, partTotal, blob.size);\n emitMultipartProgress(\"uploading\", partNumber);\n },\n );\n\n inFlightPartBytes[currentIndex] = 0;\n completedBytes += blob.size;\n emitMultipartProgress(\"uploading\", partNumber);\n\n parts[currentIndex] = {\n partNumber,\n eTag,\n };\n }\n }),\n );\n\n if (parts.some((part) => !part?.eTag)) {\n throw new Error(\"Multipart upload did not collect all uploaded part ETags.\");\n }\n\n completedBytes = file.size;\n inFlightPartBytes.fill(0);\n emitMultipartProgress(\"completing\");\n\n const completeResult = await http.request<UploadRes>({\n url: `${BASE_URL}/upload/multipart/complete`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n parts,\n },\n });\n\n if (completeResult.error) {\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n completeResult.error.message,\n completeResult.status || 500,\n completeResult.error.details,\n );\n }\n\n if (!completeResult.data) {\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n \"Multipart upload complete returned an empty response.\",\n completeResult.status || 500,\n );\n }\n\n emitMultipartProgress(\"completed\");\n return completeResult;\n } catch (error) {\n uploadAbortController.abort();\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n getErrorMessage(error, \"Multipart upload failed\"),\n error instanceof Error && error.name === \"AbortError\" ? 499 : 500,\n error,\n );\n }\n}\n\nexport function createS3Client(http: HttpClient = createHttpClient()): S3Client {\n return {\n async download(filename: string): Promise<ClientResult<Blob>> {\n if (!filename) {\n return {\n data: null,\n error: { message: \"Filename is required\", status: 400 },\n status: 400,\n };\n }\n\n const result = await http.request<Blob>({\n url: `${BASE_URL}/download`,\n method: \"get\",\n params: { filename },\n responseType: \"blob\",\n });\n\n return result;\n },\n\n async getMetadata(key: string): Promise<ClientResult<S3Metadata>> {\n if (!key) {\n return {\n data: null,\n error: { message: \"Key is required\", status: 400 },\n status: 400,\n };\n }\n\n return http.request<S3Metadata>({\n url: `${BASE_URL}/metadata`,\n method: \"get\",\n params: { key },\n });\n },\n\n async upload(\n file: File | Blob,\n options: S3UploadOptions = {},\n ): Promise<ClientResult<UploadRes>> {\n if (!file) {\n return {\n data: null,\n error: { message: \"File is required\", status: 400 },\n status: 400,\n };\n }\n\n if (shouldUseMultipartUpload(file)) {\n return await uploadWithMultipart(http, file, options);\n }\n\n const formData = new FormData();\n appendFormDataFile(formData, file, options.fileName);\n\n if (options.category) {\n formData.append(\"category\", options.category);\n }\n\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(0, file.size, \"uploading\"),\n );\n\n const result = await http.request<UploadRes>({\n url: `${BASE_URL}/upload`,\n method: \"post\",\n data: formData,\n headers: {\n \"Content-Type\": \"multipart/form-data\",\n },\n onUploadProgress: options.onProgress\n ? (event) => {\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(\n event.loaded,\n event.total ?? file.size,\n \"uploading\",\n ),\n );\n }\n : undefined,\n });\n\n if (!result.error) {\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(file.size, file.size, \"completed\"),\n );\n }\n\n return result;\n },\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -10,6 +10,20 @@ interface S3Metadata {
10
10
  lastModified?: string;
11
11
  [key: string]: any;
12
12
  }
13
+ type S3UploadProgressPhase = "preparing" | "uploading" | "completing" | "completed";
14
+ interface S3UploadProgress {
15
+ loaded: number;
16
+ total: number;
17
+ percentage: number;
18
+ phase: S3UploadProgressPhase;
19
+ partNumber?: number;
20
+ totalParts?: number;
21
+ }
22
+ interface S3UploadOptions {
23
+ fileName?: string;
24
+ category?: string;
25
+ onProgress?: (progress: S3UploadProgress) => void;
26
+ }
13
27
  type S3Client = {
14
28
  /**
15
29
  * Download a file
@@ -24,10 +38,11 @@ type S3Client = {
24
38
  /**
25
39
  * Upload a file
26
40
  * @param file - The file to upload (File or Blob)
41
+ * @param options - Optional upload filename/category and progress callback
27
42
  */
28
- upload(file: File | Blob): Promise<ClientResult<UploadRes>>;
43
+ upload(file: File | Blob, options?: S3UploadOptions): Promise<ClientResult<UploadRes>>;
29
44
  };
30
45
 
31
46
  declare function createS3Client(http?: HttpClient): S3Client;
32
47
 
33
- export { type S3Client, type S3Metadata, type UploadRes, createS3Client };
48
+ export { type S3Client, type S3Metadata, type S3UploadOptions, type S3UploadProgress, type S3UploadProgressPhase, type UploadRes, createS3Client };
package/dist/index.d.ts CHANGED
@@ -10,6 +10,20 @@ interface S3Metadata {
10
10
  lastModified?: string;
11
11
  [key: string]: any;
12
12
  }
13
+ type S3UploadProgressPhase = "preparing" | "uploading" | "completing" | "completed";
14
+ interface S3UploadProgress {
15
+ loaded: number;
16
+ total: number;
17
+ percentage: number;
18
+ phase: S3UploadProgressPhase;
19
+ partNumber?: number;
20
+ totalParts?: number;
21
+ }
22
+ interface S3UploadOptions {
23
+ fileName?: string;
24
+ category?: string;
25
+ onProgress?: (progress: S3UploadProgress) => void;
26
+ }
13
27
  type S3Client = {
14
28
  /**
15
29
  * Download a file
@@ -24,10 +38,11 @@ type S3Client = {
24
38
  /**
25
39
  * Upload a file
26
40
  * @param file - The file to upload (File or Blob)
41
+ * @param options - Optional upload filename/category and progress callback
27
42
  */
28
- upload(file: File | Blob): Promise<ClientResult<UploadRes>>;
43
+ upload(file: File | Blob, options?: S3UploadOptions): Promise<ClientResult<UploadRes>>;
29
44
  };
30
45
 
31
46
  declare function createS3Client(http?: HttpClient): S3Client;
32
47
 
33
- export { type S3Client, type S3Metadata, type UploadRes, createS3Client };
48
+ export { type S3Client, type S3Metadata, type S3UploadOptions, type S3UploadProgress, type S3UploadProgressPhase, type UploadRes, createS3Client };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import {createHttpClient,detectMiniProgramPlatform,getMiniProgramRequest}from'@amaster.ai/http-client';var c="/api/storage",P=1024*1024,S=32*P,w=8*P,F=3;function f(t,e,r){return {data:null,error:{message:t,status:e,details:r},status:e}}function h(){let t=new Error("Upload aborted");return t.name="AbortError",t}function $(t,e){return t instanceof Error&&t.message?t.message:typeof t=="string"&&t?t:e}function B(t){return "name"in t&&typeof t.name=="string"&&t.name.trim()?t.name.trim():"blob"}function L(t){return typeof t.type=="string"&&t.type?t.type:void 0}function I(t){return t.size>=S}function M(t,e){if(!t)return null;if(typeof Headers<"u"&&t instanceof Headers)return t.get(e);if(typeof t=="object"){for(let[r,a]of Object.entries(t))if(r.toLowerCase()===e.toLowerCase())return a==null?null:String(a)}return null}function x(t){return (typeof t=="object"||typeof t=="function")&&t!==null&&"then"in t&&typeof t.then=="function"}function E(t){return typeof t=="object"&&t!==null&&("statusCode"in t||"status"in t||"data"in t||"header"in t||"headers"in t)}function y(t){return E(t)?t:{}}async function H(t,e,r){return await new Promise((a,o)=>{let s=false,u=null,m=()=>{r?.removeEventListener("abort",i);},d=n=>{s||(s=true,m(),a(n));},l=n=>{s||(s=true,m(),o(n));},i=()=>{try{u?.abort?.();}catch{}l(h());};if(r?.aborted){l(h());return}r?.addEventListener("abort",i,{once:true});try{let n=t({...e,success:p=>{d(y(p));},fail:l});if(typeof n=="object"&&n!==null&&(u=n),x(n)){n.then(p=>{d(y(p));},l);return}E(n)&&d(n);}catch(n){l(n);}})}async function j(t,e,r){if(typeof fetch!="function")throw new Error("Fetch API is not available for multipart upload.");let a=await fetch(t,{method:"PUT",body:e,signal:r});if(!a.ok){let s="";try{s=await a.text();}catch{}throw new Error(`Multipart part upload failed (${a.status})${s?`: ${s}`:""}`)}let o=M(a.headers,"etag");if(!o)throw new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers.");return o}async function O(t,e,r){let a=getMiniProgramRequest();if(!a)throw new Error("Mini-program request API is not available for multipart upload.");let o=await H(a,{url:t,method:"PUT",data:await e.arrayBuffer(),header:{}},r),s=Number(o?.statusCode??o?.status??0);if(s<200||s>=300){let m=typeof o?.data=="string"&&o.data?`: ${o.data}`:"";throw new Error(`Multipart part upload failed (${s||"unknown"})${m}`)}let u=M(o?.header??o?.headers,"etag");if(!u)throw new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers.");return u}async function _(t,e,r){return detectMiniProgramPlatform()?await O(t,e,r):await j(t,e,r)}async function b(t,e,r){try{await t.request({url:`${c}/upload/multipart/abort`,method:"post",data:{filePath:e,uploadId:r}});}catch{}}async function D(t,e){let r=await t.request({url:`${c}/upload/multipart/init`,method:"post",data:{filename:B(e),contentType:L(e)}});if(r.error)return f(r.error.message,r.status||500,r.error.details);if(!r.data?.uploadId||!r.data?.filePath)return f("Multipart upload init returned an invalid response.",r.status||500);let{uploadId:a,filePath:o}=r.data,s=Math.max(1,Math.ceil(e.size/w)),u=new Array(s),m=Math.min(F,s),d=new AbortController,l=0;try{if(await Promise.all(Array.from({length:m},async()=>{for(;;){let n=l;if(l+=1,n>=s)return;let p=n+1,R=n*w,C=Math.min(R+w,e.size),T=e.slice(R,C),g=await t.request({url:`${c}/upload/multipart/url`,method:"post",data:{filePath:o,uploadId:a,partNumber:p},signal:d.signal});if(g.error)throw new Error(g.error.message);if(!g.data?.url)throw new Error(`Multipart upload URL request returned an invalid response for part ${p}.`);let k=await _(g.data.url,T,d.signal);u[n]={partNumber:p,eTag:k};}})),u.some(n=>!n?.eTag))throw new Error("Multipart upload did not collect all uploaded part ETags.");let i=await t.request({url:`${c}/upload/multipart/complete`,method:"post",data:{filePath:o,uploadId:a,parts:u}});return i.error?(await b(t,o,a),f(i.error.message,i.status||500,i.error.details)):i.data?i:(await b(t,o,a),f("Multipart upload complete returned an empty response.",i.status||500))}catch(i){return d.abort(),await b(t,o,a),f($(i,"Multipart upload failed"),i instanceof Error&&i.name==="AbortError"?499:500,i)}}function N(t=createHttpClient()){return {async download(e){return e?await t.request({url:`${c}/download`,method:"get",params:{filename:e},responseType:"blob"}):{data:null,error:{message:"Filename is required",status:400},status:400}},async getMetadata(e){return e?t.request({url:`${c}/metadata`,method:"get",params:{key:e}}):{data:null,error:{message:"Key is required",status:400},status:400}},async upload(e){if(!e)return {data:null,error:{message:"File is required",status:400},status:400};if(I(e))return await D(t,e);let r=new FormData;return r.append("file",e),t.request({url:`${c}/upload`,method:"post",data:r,headers:{"Content-Type":"multipart/form-data"}})}}}export{N as createS3Client};//# sourceMappingURL=index.js.map
1
+ import {createHttpClient,detectMiniProgramPlatform,getMiniProgramRequest}from'@amaster.ai/http-client';var P="/api/storage",A=1024*1024,I=32*A,k=8*A,j=3;function h(t,e,r){return {data:null,error:{message:t,status:e,details:r},status:e}}function b(){let t=new Error("Upload aborted");return t.name="AbortError",t}function v(t,e){return t instanceof Error&&t.message?t.message:typeof t=="string"&&t?t:e}function D(t,e){return e?.trim()?e.trim():"name"in t&&typeof t.name=="string"&&t.name.trim()?t.name.trim():"blob"}function N(t){return typeof t.type=="string"&&t.type?t.type:void 0}function _(t){return t.size>=I}function y(t,e,r,n){let o=Number.isFinite(e)&&e>0?e:0,a=Number.isFinite(t)?t:0,u=o>0?o:a,s=Math.max(0,Math.min(a,u)),m=o>0?Math.min(100,Math.round(s/o*1e4)/100):0;return {loaded:s,total:o,percentage:m,phase:r,...n}}function R(t,e){t?.(e);}function X(t,e,r){if(r?.trim()){t.append("file",e,r.trim());return}t.append("file",e);}function F(t,e){if(!t)return null;if(typeof Headers<"u"&&t instanceof Headers)return t.get(e);if(typeof t=="object"){for(let[r,n]of Object.entries(t))if(r.toLowerCase()===e.toLowerCase())return n==null?null:String(n)}return null}function W(t){return (typeof t=="object"||typeof t=="function")&&t!==null&&"then"in t&&typeof t.then=="function"}function L(t){return typeof t=="object"&&t!==null&&("statusCode"in t||"status"in t||"data"in t||"header"in t||"headers"in t)}function q(t){return L(t)?t:{}}async function K(t,e,r){return await new Promise((n,o)=>{let a=false,u=null,s=()=>{r?.removeEventListener("abort",d);},m=i=>{a||(a=true,s(),n(i));},c=i=>{a||(a=true,s(),o(i));},d=()=>{try{u?.abort?.();}catch{}c(b());};if(r?.aborted){c(b());return}r?.addEventListener("abort",d,{once:true});try{let i=t({...e,success:l=>{m(q(l));},fail:c});if(typeof i=="object"&&i!==null&&(u=i),W(i)){i.then(l=>{m(q(l));},c);return}L(i)&&m(i);}catch(i){c(i);}})}async function V(t,e,r,n){if(typeof XMLHttpRequest!="function")throw new Error("XMLHttpRequest is not available for multipart upload.");return await new Promise((o,a)=>{let u=false,s=new XMLHttpRequest,m=()=>{r?.removeEventListener("abort",i);},c=l=>{u||(u=true,m(),o(l));},d=l=>{u||(u=true,m(),a(l));},i=()=>{try{s.abort();}catch{}d(b());};if(r?.aborted){d(b());return}r?.addEventListener("abort",i,{once:true}),s.upload.onprogress=l=>{let f=l.lengthComputable?l.total:e.size;n?.(l.loaded,f);},s.onload=()=>{if(s.status<200||s.status>=300){let f=s.responseText?`: ${s.responseText}`:"";d(new Error(`Multipart part upload failed (${s.status})${f}`));return}let l=s.getResponseHeader("etag");if(!l){d(new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers."));return}n?.(e.size,e.size),c(l);},s.onerror=()=>{d(new Error("Multipart part upload failed (network error)"));},s.onabort=()=>{d(b());},s.open("PUT",t),s.send(e);})}async function Y(t,e,r){if(typeof fetch!="function")throw new Error("Fetch API is not available for multipart upload.");let n=await fetch(t,{method:"PUT",body:e,signal:r});if(!n.ok){let a="";try{a=await n.text();}catch{}throw new Error(`Multipart part upload failed (${n.status})${a?`: ${a}`:""}`)}let o=F(n.headers,"etag");if(!o)throw new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers.");return o}async function Z(t,e,r){let n=getMiniProgramRequest();if(!n)throw new Error("Mini-program request API is not available for multipart upload.");let o=await K(n,{url:t,method:"PUT",data:await e.arrayBuffer(),header:{}},r),a=Number(o?.statusCode??o?.status??0);if(a<200||a>=300){let s=typeof o?.data=="string"&&o.data?`: ${o.data}`:"";throw new Error(`Multipart part upload failed (${a||"unknown"})${s}`)}let u=F(o?.header??o?.headers,"etag");if(!u)throw new Error("Multipart upload response missing ETag header. Check object storage CORS expose headers.");return u}async function G(t,e,r,n){return detectMiniProgramPlatform()?await Z(t,e,r):n&&typeof XMLHttpRequest=="function"?await V(t,e,r,n):await Y(t,e,r)}async function S(t,e,r){try{await t.request({url:`${P}/upload/multipart/abort`,method:"post",data:{filePath:e,uploadId:r}});}catch{}}async function J(t,e,r={}){R(r.onProgress,y(0,e.size,"preparing"));let n=await t.request({url:`${P}/upload/multipart/init`,method:"post",data:{filename:D(e,r.fileName),contentType:N(e),...r.category?{category:r.category}:{}}});if(n.error)return h(n.error.message,n.status||500,n.error.details);if(!n.data?.uploadId||!n.data?.filePath)return h("Multipart upload init returned an invalid response.",n.status||500);let{uploadId:o,filePath:a}=n.data,u=Math.max(1,Math.ceil(e.size/k)),s=new Array(u),m=Math.min(j,u),c=new AbortController,d=new Array(u).fill(0),i=0,l=0,f=(p,g)=>{let w=d.reduce((M,T)=>M+T,0);R(r.onProgress,y(Math.min(e.size,i+w),e.size,p,{...g?{partNumber:g}:{},totalParts:u}));};f("uploading");try{if(await Promise.all(Array.from({length:m},async()=>{for(;;){let g=l;if(l+=1,g>=u)return;let w=g+1,M=g*k,T=Math.min(M+k,e.size),E=e.slice(M,T),U=await t.request({url:`${P}/upload/multipart/url`,method:"post",data:{filePath:a,uploadId:o,partNumber:w},signal:c.signal});if(U.error)throw new Error(U.error.message);if(!U.data?.url)throw new Error(`Multipart upload URL request returned an invalid response for part ${w}.`);let z=await G(U.data.url,E,c.signal,(B,C)=>{let H=C&&C>0?C:E.size;d[g]=Math.min(B,H,E.size),f("uploading",w);});d[g]=0,i+=E.size,f("uploading",w),s[g]={partNumber:w,eTag:z};}})),s.some(g=>!g?.eTag))throw new Error("Multipart upload did not collect all uploaded part ETags.");i=e.size,d.fill(0),f("completing");let p=await t.request({url:`${P}/upload/multipart/complete`,method:"post",data:{filePath:a,uploadId:o,parts:s}});return p.error?(await S(t,a,o),h(p.error.message,p.status||500,p.error.details)):p.data?(f("completed"),p):(await S(t,a,o),h("Multipart upload complete returned an empty response.",p.status||500))}catch(p){return c.abort(),await S(t,a,o),h(v(p,"Multipart upload failed"),p instanceof Error&&p.name==="AbortError"?499:500,p)}}function tt(t=createHttpClient()){return {async download(e){return e?await t.request({url:`${P}/download`,method:"get",params:{filename:e},responseType:"blob"}):{data:null,error:{message:"Filename is required",status:400},status:400}},async getMetadata(e){return e?t.request({url:`${P}/metadata`,method:"get",params:{key:e}}):{data:null,error:{message:"Key is required",status:400},status:400}},async upload(e,r={}){if(!e)return {data:null,error:{message:"File is required",status:400},status:400};if(_(e))return await J(t,e,r);let n=new FormData;X(n,e,r.fileName),r.category&&n.append("category",r.category),R(r.onProgress,y(0,e.size,"uploading"));let o=await t.request({url:`${P}/upload`,method:"post",data:n,headers:{"Content-Type":"multipart/form-data"},onUploadProgress:r.onProgress?a=>{R(r.onProgress,y(a.loaded,a.total??e.size,"uploading"));}:void 0});return o.error||R(r.onProgress,y(e.size,e.size,"completed")),o}}}export{tt as createS3Client};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/s3-client.ts"],"names":["BASE_URL","MB","DEFAULT_MULTIPART_THRESHOLD","DEFAULT_PART_SIZE","DEFAULT_CONCURRENCY","createErrorResult","message","status","details","createAbortError","error","getErrorMessage","fallback","resolveFilename","file","resolveContentType","shouldUseMultipartUpload","getHeaderValue","headers","key","headerKey","headerValue","isPromiseLike","value","isMiniProgramRequestResponse","normalizeMiniProgramRequestResponse","callMiniProgramRequest","request","options","signal","resolve","reject","settled","task","cleanup","onAbort","resolveOnce","rejectOnce","taskResult","uploadPartWithFetch","url","blob","response","eTag","uploadPartWithMiniProgram","getMiniProgramRequest","uploadPartToPresignedUrl","detectMiniProgramPlatform","abortMultipartUpload","http","filePath","uploadId","uploadWithMultipart","initResult","totalParts","parts","workerCount","uploadAbortController","nextPartIndex","currentIndex","partNumber","start","end","urlResult","part","completeResult","createS3Client","createHttpClient","filename","formData"],"mappings":"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","file":"index.js","sourcesContent":["import {\n createHttpClient,\n detectMiniProgramPlatform,\n getMiniProgramRequest,\n type ClientResult,\n type HttpClient,\n} from \"@amaster.ai/http-client\";\nimport type { S3Client, S3Metadata, UploadRes } from \"./types\";\n\nconst BASE_URL = \"/api/storage\";\nconst MB = 1024 * 1024;\nconst DEFAULT_MULTIPART_THRESHOLD = 32 * MB;\nconst DEFAULT_PART_SIZE = 8 * MB;\nconst DEFAULT_CONCURRENCY = 3;\n\ntype MultipartInitRes = {\n uploadId: string;\n filePath: string;\n};\n\ntype MultipartUrlRes = {\n url: string;\n};\n\ntype PartETag = {\n partNumber: number;\n eTag: string;\n};\n\ntype MiniProgramRequestResponse = {\n statusCode?: unknown;\n status?: unknown;\n data?: unknown;\n header?: unknown;\n headers?: unknown;\n};\n\ntype AbortableTask = {\n abort?: () => void;\n};\n\nfunction createErrorResult<T>(\n message: string,\n status: number,\n details?: unknown,\n): ClientResult<T> {\n return {\n data: null,\n error: {\n message,\n status,\n details,\n },\n status,\n };\n}\n\nfunction createAbortError(): Error {\n const error = new Error(\"Upload aborted\");\n error.name = \"AbortError\";\n return error;\n}\n\nfunction getErrorMessage(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n if (typeof error === \"string\" && error) {\n return error;\n }\n return fallback;\n}\n\nfunction resolveFilename(file: File | Blob): string {\n if (\"name\" in file && typeof file.name === \"string\" && file.name.trim()) {\n return file.name.trim();\n }\n return \"blob\";\n}\n\nfunction resolveContentType(file: File | Blob): string | undefined {\n return typeof file.type === \"string\" && file.type ? file.type : undefined;\n}\n\nfunction shouldUseMultipartUpload(file: File | Blob): boolean {\n return file.size >= DEFAULT_MULTIPART_THRESHOLD;\n}\n\nfunction getHeaderValue(headers: unknown, key: string): string | null {\n if (!headers) {\n return null;\n }\n\n if (typeof Headers !== \"undefined\" && headers instanceof Headers) {\n return headers.get(key);\n }\n\n if (typeof headers === \"object\") {\n for (const [headerKey, headerValue] of Object.entries(\n headers as Record<string, unknown>,\n )) {\n if (headerKey.toLowerCase() === key.toLowerCase()) {\n return headerValue == null ? null : String(headerValue);\n }\n }\n }\n\n return null;\n}\n\nfunction isPromiseLike<T = unknown>(value: unknown): value is PromiseLike<T> {\n return (\n (typeof value === \"object\" || typeof value === \"function\") &&\n value !== null &&\n \"then\" in value &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\nfunction isMiniProgramRequestResponse(\n value: unknown,\n): value is MiniProgramRequestResponse {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (\"statusCode\" in value ||\n \"status\" in value ||\n \"data\" in value ||\n \"header\" in value ||\n \"headers\" in value)\n );\n}\n\nfunction normalizeMiniProgramRequestResponse(\n value: unknown,\n): MiniProgramRequestResponse {\n return isMiniProgramRequestResponse(value) ? value : {};\n}\n\nasync function callMiniProgramRequest(\n request: (_options: Record<string, unknown>) => unknown,\n options: Record<string, unknown>,\n signal?: AbortSignal,\n): Promise<MiniProgramRequestResponse> {\n return await new Promise<MiniProgramRequestResponse>((resolve, reject) => {\n let settled = false;\n let task: AbortableTask | null = null;\n\n const cleanup = () => {\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n const resolveOnce = (value: MiniProgramRequestResponse) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const rejectOnce = (error: unknown) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n reject(error);\n };\n\n const onAbort = () => {\n try {\n task?.abort?.();\n } catch {\n // ignore abort failures from runtime-specific request tasks\n }\n rejectOnce(createAbortError());\n };\n\n if (signal?.aborted) {\n rejectOnce(createAbortError());\n return;\n }\n\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n try {\n const taskResult = request({\n ...options,\n success: (value: unknown) => {\n resolveOnce(normalizeMiniProgramRequestResponse(value));\n },\n fail: rejectOnce,\n });\n\n if (typeof taskResult === \"object\" && taskResult !== null) {\n task = taskResult as AbortableTask;\n }\n\n if (isPromiseLike(taskResult)) {\n void taskResult.then(\n (value) => {\n resolveOnce(normalizeMiniProgramRequestResponse(value));\n },\n rejectOnce,\n );\n return;\n }\n\n if (isMiniProgramRequestResponse(taskResult)) {\n resolveOnce(taskResult);\n }\n } catch (error) {\n rejectOnce(error);\n }\n });\n}\n\nasync function uploadPartWithFetch(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n if (typeof fetch !== \"function\") {\n throw new Error(\"Fetch API is not available for multipart upload.\");\n }\n\n const response = await fetch(url, {\n method: \"PUT\",\n body: blob,\n signal,\n });\n\n if (!response.ok) {\n let details = \"\";\n try {\n details = await response.text();\n } catch {\n // ignore response body parse failures\n }\n throw new Error(\n `Multipart part upload failed (${response.status})${details ? `: ${details}` : \"\"}`,\n );\n }\n\n const eTag = getHeaderValue(response.headers, \"etag\");\n if (!eTag) {\n throw new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n );\n }\n\n return eTag;\n}\n\nasync function uploadPartWithMiniProgram(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n const request = getMiniProgramRequest();\n if (!request) {\n throw new Error(\"Mini-program request API is not available for multipart upload.\");\n }\n\n const response = await callMiniProgramRequest(\n request,\n {\n url,\n method: \"PUT\",\n data: await blob.arrayBuffer(),\n header: {},\n },\n signal,\n );\n\n const status = Number(response?.statusCode ?? response?.status ?? 0);\n if (status < 200 || status >= 300) {\n const details =\n typeof response?.data === \"string\" && response.data\n ? `: ${response.data}`\n : \"\";\n throw new Error(\n `Multipart part upload failed (${status || \"unknown\"})${details}`,\n );\n }\n\n const eTag = getHeaderValue(response?.header ?? response?.headers, \"etag\");\n if (!eTag) {\n throw new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n );\n }\n\n return eTag;\n}\n\nasync function uploadPartToPresignedUrl(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n if (detectMiniProgramPlatform()) {\n return await uploadPartWithMiniProgram(url, blob, signal);\n }\n\n return await uploadPartWithFetch(url, blob, signal);\n}\n\nasync function abortMultipartUpload(\n http: HttpClient,\n filePath: string,\n uploadId: string,\n): Promise<void> {\n try {\n await http.request<void>({\n url: `${BASE_URL}/upload/multipart/abort`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n },\n });\n } catch {\n // Best-effort cleanup only.\n }\n}\n\nasync function uploadWithMultipart(\n http: HttpClient,\n file: File | Blob,\n): Promise<ClientResult<UploadRes>> {\n const initResult = await http.request<MultipartInitRes>({\n url: `${BASE_URL}/upload/multipart/init`,\n method: \"post\",\n data: {\n filename: resolveFilename(file),\n contentType: resolveContentType(file),\n },\n });\n\n if (initResult.error) {\n return createErrorResult(\n initResult.error.message,\n initResult.status || 500,\n initResult.error.details,\n );\n }\n\n if (!initResult.data?.uploadId || !initResult.data?.filePath) {\n return createErrorResult(\n \"Multipart upload init returned an invalid response.\",\n initResult.status || 500,\n );\n }\n\n const { uploadId, filePath } = initResult.data;\n const totalParts = Math.max(1, Math.ceil(file.size / DEFAULT_PART_SIZE));\n const parts: PartETag[] = new Array(totalParts);\n const workerCount = Math.min(DEFAULT_CONCURRENCY, totalParts);\n const uploadAbortController = new AbortController();\n let nextPartIndex = 0;\n\n try {\n await Promise.all(\n Array.from({ length: workerCount }, async () => {\n while (true) {\n const currentIndex = nextPartIndex;\n nextPartIndex += 1;\n\n if (currentIndex >= totalParts) {\n return;\n }\n\n const partNumber = currentIndex + 1;\n const start = currentIndex * DEFAULT_PART_SIZE;\n const end = Math.min(start + DEFAULT_PART_SIZE, file.size);\n const blob = file.slice(start, end);\n\n const urlResult = await http.request<MultipartUrlRes>({\n url: `${BASE_URL}/upload/multipart/url`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n partNumber,\n },\n signal: uploadAbortController.signal,\n });\n\n if (urlResult.error) {\n throw new Error(urlResult.error.message);\n }\n\n if (!urlResult.data?.url) {\n throw new Error(\n `Multipart upload URL request returned an invalid response for part ${partNumber}.`,\n );\n }\n\n const eTag = await uploadPartToPresignedUrl(\n urlResult.data.url,\n blob,\n uploadAbortController.signal,\n );\n\n parts[currentIndex] = {\n partNumber,\n eTag,\n };\n }\n }),\n );\n\n if (parts.some((part) => !part?.eTag)) {\n throw new Error(\"Multipart upload did not collect all uploaded part ETags.\");\n }\n\n const completeResult = await http.request<UploadRes>({\n url: `${BASE_URL}/upload/multipart/complete`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n parts,\n },\n });\n\n if (completeResult.error) {\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n completeResult.error.message,\n completeResult.status || 500,\n completeResult.error.details,\n );\n }\n\n if (!completeResult.data) {\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n \"Multipart upload complete returned an empty response.\",\n completeResult.status || 500,\n );\n }\n\n return completeResult;\n } catch (error) {\n uploadAbortController.abort();\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n getErrorMessage(error, \"Multipart upload failed\"),\n error instanceof Error && error.name === \"AbortError\" ? 499 : 500,\n error,\n );\n }\n}\n\nexport function createS3Client(http: HttpClient = createHttpClient()): S3Client {\n return {\n async download(filename: string): Promise<ClientResult<Blob>> {\n if (!filename) {\n return {\n data: null,\n error: { message: \"Filename is required\", status: 400 },\n status: 400,\n };\n }\n\n const result = await http.request<Blob>({\n url: `${BASE_URL}/download`,\n method: \"get\",\n params: { filename },\n responseType: \"blob\",\n });\n\n return result;\n },\n\n async getMetadata(key: string): Promise<ClientResult<S3Metadata>> {\n if (!key) {\n return {\n data: null,\n error: { message: \"Key is required\", status: 400 },\n status: 400,\n };\n }\n\n return http.request<S3Metadata>({\n url: `${BASE_URL}/metadata`,\n method: \"get\",\n params: { key },\n });\n },\n\n async upload(file: File | Blob): Promise<ClientResult<UploadRes>> {\n if (!file) {\n return {\n data: null,\n error: { message: \"File is required\", status: 400 },\n status: 400,\n };\n }\n\n if (shouldUseMultipartUpload(file)) {\n return await uploadWithMultipart(http, file);\n }\n\n const formData = new FormData();\n formData.append(\"file\", file);\n\n return http.request<UploadRes>({\n url: `${BASE_URL}/upload`,\n method: \"post\",\n data: formData,\n headers: {\n \"Content-Type\": \"multipart/form-data\",\n },\n });\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/s3-client.ts"],"names":["BASE_URL","MB","DEFAULT_MULTIPART_THRESHOLD","DEFAULT_PART_SIZE","DEFAULT_CONCURRENCY","createErrorResult","message","status","details","createAbortError","error","getErrorMessage","fallback","resolveFilename","file","fileName","resolveContentType","shouldUseMultipartUpload","createUploadProgress","loaded","total","phase","extra","safeTotal","normalizedLoaded","loadedLimit","safeLoaded","percentage","emitUploadProgress","onProgress","progress","appendFormDataFile","formData","getHeaderValue","headers","key","headerKey","headerValue","isPromiseLike","value","isMiniProgramRequestResponse","normalizeMiniProgramRequestResponse","callMiniProgramRequest","request","options","signal","resolve","reject","settled","task","cleanup","onAbort","resolveOnce","rejectOnce","taskResult","uploadPartWithXhr","url","blob","xhr","event","eTag","uploadPartWithFetch","response","uploadPartWithMiniProgram","getMiniProgramRequest","uploadPartToPresignedUrl","detectMiniProgramPlatform","abortMultipartUpload","http","filePath","uploadId","uploadWithMultipart","initResult","totalParts","parts","workerCount","uploadAbortController","inFlightPartBytes","completedBytes","nextPartIndex","emitMultipartProgress","partNumber","inFlightBytes","sum","currentIndex","start","end","urlResult","partTotal","part","completeResult","createS3Client","createHttpClient","filename","result"],"mappings":"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","file":"index.js","sourcesContent":["import {\n createHttpClient,\n detectMiniProgramPlatform,\n getMiniProgramRequest,\n type ClientResult,\n type HttpClient,\n} from \"@amaster.ai/http-client\";\nimport type {\n S3Client,\n S3Metadata,\n S3UploadOptions,\n S3UploadProgress,\n S3UploadProgressPhase,\n UploadRes,\n} from \"./types\";\n\nconst BASE_URL = \"/api/storage\";\nconst MB = 1024 * 1024;\nconst DEFAULT_MULTIPART_THRESHOLD = 32 * MB;\nconst DEFAULT_PART_SIZE = 8 * MB;\nconst DEFAULT_CONCURRENCY = 3;\n\ntype MultipartInitRes = {\n uploadId: string;\n filePath: string;\n};\n\ntype MultipartUrlRes = {\n url: string;\n};\n\ntype PartETag = {\n partNumber: number;\n eTag: string;\n};\n\ntype MiniProgramRequestResponse = {\n statusCode?: unknown;\n status?: unknown;\n data?: unknown;\n header?: unknown;\n headers?: unknown;\n};\n\ntype AbortableTask = {\n abort?: () => void;\n};\n\ntype UploadProgressHandler = (loaded: number, total?: number) => void;\n\nfunction createErrorResult<T>(\n message: string,\n status: number,\n details?: unknown,\n): ClientResult<T> {\n return {\n data: null,\n error: {\n message,\n status,\n details,\n },\n status,\n };\n}\n\nfunction createAbortError(): Error {\n const error = new Error(\"Upload aborted\");\n error.name = \"AbortError\";\n return error;\n}\n\nfunction getErrorMessage(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n if (typeof error === \"string\" && error) {\n return error;\n }\n return fallback;\n}\n\nfunction resolveFilename(file: File | Blob, fileName?: string): string {\n if (fileName?.trim()) {\n return fileName.trim();\n }\n\n if (\"name\" in file && typeof file.name === \"string\" && file.name.trim()) {\n return file.name.trim();\n }\n return \"blob\";\n}\n\nfunction resolveContentType(file: File | Blob): string | undefined {\n return typeof file.type === \"string\" && file.type ? file.type : undefined;\n}\n\nfunction shouldUseMultipartUpload(file: File | Blob): boolean {\n return file.size >= DEFAULT_MULTIPART_THRESHOLD;\n}\n\nfunction createUploadProgress(\n loaded: number,\n total: number,\n phase: S3UploadProgressPhase,\n extra?: Pick<S3UploadProgress, \"partNumber\" | \"totalParts\">,\n): S3UploadProgress {\n const safeTotal = Number.isFinite(total) && total > 0 ? total : 0;\n const normalizedLoaded = Number.isFinite(loaded) ? loaded : 0;\n const loadedLimit = safeTotal > 0 ? safeTotal : normalizedLoaded;\n const safeLoaded = Math.max(0, Math.min(normalizedLoaded, loadedLimit));\n const percentage =\n safeTotal > 0 ? Math.min(100, Math.round((safeLoaded / safeTotal) * 10000) / 100) : 0;\n\n return {\n loaded: safeLoaded,\n total: safeTotal,\n percentage,\n phase,\n ...extra,\n };\n}\n\nfunction emitUploadProgress(\n onProgress: S3UploadOptions[\"onProgress\"] | undefined,\n progress: S3UploadProgress,\n): void {\n onProgress?.(progress);\n}\n\nfunction appendFormDataFile(\n formData: FormData,\n file: File | Blob,\n fileName?: string,\n): void {\n if (fileName?.trim()) {\n formData.append(\"file\", file, fileName.trim());\n return;\n }\n\n formData.append(\"file\", file);\n}\n\nfunction getHeaderValue(headers: unknown, key: string): string | null {\n if (!headers) {\n return null;\n }\n\n if (typeof Headers !== \"undefined\" && headers instanceof Headers) {\n return headers.get(key);\n }\n\n if (typeof headers === \"object\") {\n for (const [headerKey, headerValue] of Object.entries(\n headers as Record<string, unknown>,\n )) {\n if (headerKey.toLowerCase() === key.toLowerCase()) {\n return headerValue == null ? null : String(headerValue);\n }\n }\n }\n\n return null;\n}\n\nfunction isPromiseLike<T = unknown>(value: unknown): value is PromiseLike<T> {\n return (\n (typeof value === \"object\" || typeof value === \"function\") &&\n value !== null &&\n \"then\" in value &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\nfunction isMiniProgramRequestResponse(\n value: unknown,\n): value is MiniProgramRequestResponse {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (\"statusCode\" in value ||\n \"status\" in value ||\n \"data\" in value ||\n \"header\" in value ||\n \"headers\" in value)\n );\n}\n\nfunction normalizeMiniProgramRequestResponse(\n value: unknown,\n): MiniProgramRequestResponse {\n return isMiniProgramRequestResponse(value) ? value : {};\n}\n\nasync function callMiniProgramRequest(\n request: (_options: Record<string, unknown>) => unknown,\n options: Record<string, unknown>,\n signal?: AbortSignal,\n): Promise<MiniProgramRequestResponse> {\n return await new Promise<MiniProgramRequestResponse>((resolve, reject) => {\n let settled = false;\n let task: AbortableTask | null = null;\n\n const cleanup = () => {\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n const resolveOnce = (value: MiniProgramRequestResponse) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const rejectOnce = (error: unknown) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n reject(error);\n };\n\n const onAbort = () => {\n try {\n task?.abort?.();\n } catch {\n // ignore abort failures from runtime-specific request tasks\n }\n rejectOnce(createAbortError());\n };\n\n if (signal?.aborted) {\n rejectOnce(createAbortError());\n return;\n }\n\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n try {\n const taskResult = request({\n ...options,\n success: (value: unknown) => {\n resolveOnce(normalizeMiniProgramRequestResponse(value));\n },\n fail: rejectOnce,\n });\n\n if (typeof taskResult === \"object\" && taskResult !== null) {\n task = taskResult as AbortableTask;\n }\n\n if (isPromiseLike(taskResult)) {\n void taskResult.then(\n (value) => {\n resolveOnce(normalizeMiniProgramRequestResponse(value));\n },\n rejectOnce,\n );\n return;\n }\n\n if (isMiniProgramRequestResponse(taskResult)) {\n resolveOnce(taskResult);\n }\n } catch (error) {\n rejectOnce(error);\n }\n });\n}\n\nasync function uploadPartWithXhr(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n onProgress?: UploadProgressHandler,\n): Promise<string> {\n if (typeof XMLHttpRequest !== \"function\") {\n throw new Error(\"XMLHttpRequest is not available for multipart upload.\");\n }\n\n return await new Promise<string>((resolve, reject) => {\n let settled = false;\n const xhr = new XMLHttpRequest();\n\n const cleanup = () => {\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n const resolveOnce = (value: string) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const rejectOnce = (error: unknown) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n reject(error);\n };\n\n const onAbort = () => {\n try {\n xhr.abort();\n } catch {\n // ignore abort failures from runtime-specific XHR implementations\n }\n rejectOnce(createAbortError());\n };\n\n if (signal?.aborted) {\n rejectOnce(createAbortError());\n return;\n }\n\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n xhr.upload.onprogress = (event) => {\n const total = event.lengthComputable ? event.total : blob.size;\n onProgress?.(event.loaded, total);\n };\n\n xhr.onload = () => {\n if (xhr.status < 200 || xhr.status >= 300) {\n const details = xhr.responseText ? `: ${xhr.responseText}` : \"\";\n rejectOnce(\n new Error(`Multipart part upload failed (${xhr.status})${details}`),\n );\n return;\n }\n\n const eTag = xhr.getResponseHeader(\"etag\");\n if (!eTag) {\n rejectOnce(\n new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n ),\n );\n return;\n }\n\n onProgress?.(blob.size, blob.size);\n resolveOnce(eTag);\n };\n\n xhr.onerror = () => {\n rejectOnce(new Error(\"Multipart part upload failed (network error)\"));\n };\n\n xhr.onabort = () => {\n rejectOnce(createAbortError());\n };\n\n xhr.open(\"PUT\", url);\n xhr.send(blob);\n });\n}\n\nasync function uploadPartWithFetch(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n if (typeof fetch !== \"function\") {\n throw new Error(\"Fetch API is not available for multipart upload.\");\n }\n\n const response = await fetch(url, {\n method: \"PUT\",\n body: blob,\n signal,\n });\n\n if (!response.ok) {\n let details = \"\";\n try {\n details = await response.text();\n } catch {\n // ignore response body parse failures\n }\n throw new Error(\n `Multipart part upload failed (${response.status})${details ? `: ${details}` : \"\"}`,\n );\n }\n\n const eTag = getHeaderValue(response.headers, \"etag\");\n if (!eTag) {\n throw new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n );\n }\n\n return eTag;\n}\n\nasync function uploadPartWithMiniProgram(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n): Promise<string> {\n const request = getMiniProgramRequest();\n if (!request) {\n throw new Error(\"Mini-program request API is not available for multipart upload.\");\n }\n\n const response = await callMiniProgramRequest(\n request,\n {\n url,\n method: \"PUT\",\n data: await blob.arrayBuffer(),\n header: {},\n },\n signal,\n );\n\n const status = Number(response?.statusCode ?? response?.status ?? 0);\n if (status < 200 || status >= 300) {\n const details =\n typeof response?.data === \"string\" && response.data\n ? `: ${response.data}`\n : \"\";\n throw new Error(\n `Multipart part upload failed (${status || \"unknown\"})${details}`,\n );\n }\n\n const eTag = getHeaderValue(response?.header ?? response?.headers, \"etag\");\n if (!eTag) {\n throw new Error(\n \"Multipart upload response missing ETag header. Check object storage CORS expose headers.\",\n );\n }\n\n return eTag;\n}\n\nasync function uploadPartToPresignedUrl(\n url: string,\n blob: Blob,\n signal?: AbortSignal,\n onProgress?: UploadProgressHandler,\n): Promise<string> {\n if (detectMiniProgramPlatform()) {\n return await uploadPartWithMiniProgram(url, blob, signal);\n }\n\n if (onProgress && typeof XMLHttpRequest === \"function\") {\n return await uploadPartWithXhr(url, blob, signal, onProgress);\n }\n\n return await uploadPartWithFetch(url, blob, signal);\n}\n\nasync function abortMultipartUpload(\n http: HttpClient,\n filePath: string,\n uploadId: string,\n): Promise<void> {\n try {\n await http.request<void>({\n url: `${BASE_URL}/upload/multipart/abort`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n },\n });\n } catch {\n // Best-effort cleanup only.\n }\n}\n\nasync function uploadWithMultipart(\n http: HttpClient,\n file: File | Blob,\n options: S3UploadOptions = {},\n): Promise<ClientResult<UploadRes>> {\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(0, file.size, \"preparing\"),\n );\n\n const initResult = await http.request<MultipartInitRes>({\n url: `${BASE_URL}/upload/multipart/init`,\n method: \"post\",\n data: {\n filename: resolveFilename(file, options.fileName),\n contentType: resolveContentType(file),\n ...(options.category ? { category: options.category } : {}),\n },\n });\n\n if (initResult.error) {\n return createErrorResult(\n initResult.error.message,\n initResult.status || 500,\n initResult.error.details,\n );\n }\n\n if (!initResult.data?.uploadId || !initResult.data?.filePath) {\n return createErrorResult(\n \"Multipart upload init returned an invalid response.\",\n initResult.status || 500,\n );\n }\n\n const { uploadId, filePath } = initResult.data;\n const totalParts = Math.max(1, Math.ceil(file.size / DEFAULT_PART_SIZE));\n const parts: PartETag[] = new Array(totalParts);\n const workerCount = Math.min(DEFAULT_CONCURRENCY, totalParts);\n const uploadAbortController = new AbortController();\n const inFlightPartBytes = new Array<number>(totalParts).fill(0);\n let completedBytes = 0;\n let nextPartIndex = 0;\n\n const emitMultipartProgress = (\n phase: S3UploadProgressPhase,\n partNumber?: number,\n ) => {\n const inFlightBytes = inFlightPartBytes.reduce((sum, loaded) => sum + loaded, 0);\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(\n Math.min(file.size, completedBytes + inFlightBytes),\n file.size,\n phase,\n {\n ...(partNumber ? { partNumber } : {}),\n totalParts,\n },\n ),\n );\n };\n\n emitMultipartProgress(\"uploading\");\n\n try {\n await Promise.all(\n Array.from({ length: workerCount }, async () => {\n while (true) {\n const currentIndex = nextPartIndex;\n nextPartIndex += 1;\n\n if (currentIndex >= totalParts) {\n return;\n }\n\n const partNumber = currentIndex + 1;\n const start = currentIndex * DEFAULT_PART_SIZE;\n const end = Math.min(start + DEFAULT_PART_SIZE, file.size);\n const blob = file.slice(start, end);\n\n const urlResult = await http.request<MultipartUrlRes>({\n url: `${BASE_URL}/upload/multipart/url`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n partNumber,\n },\n signal: uploadAbortController.signal,\n });\n\n if (urlResult.error) {\n throw new Error(urlResult.error.message);\n }\n\n if (!urlResult.data?.url) {\n throw new Error(\n `Multipart upload URL request returned an invalid response for part ${partNumber}.`,\n );\n }\n\n const eTag = await uploadPartToPresignedUrl(\n urlResult.data.url,\n blob,\n uploadAbortController.signal,\n (loaded, total) => {\n const partTotal = total && total > 0 ? total : blob.size;\n inFlightPartBytes[currentIndex] = Math.min(loaded, partTotal, blob.size);\n emitMultipartProgress(\"uploading\", partNumber);\n },\n );\n\n inFlightPartBytes[currentIndex] = 0;\n completedBytes += blob.size;\n emitMultipartProgress(\"uploading\", partNumber);\n\n parts[currentIndex] = {\n partNumber,\n eTag,\n };\n }\n }),\n );\n\n if (parts.some((part) => !part?.eTag)) {\n throw new Error(\"Multipart upload did not collect all uploaded part ETags.\");\n }\n\n completedBytes = file.size;\n inFlightPartBytes.fill(0);\n emitMultipartProgress(\"completing\");\n\n const completeResult = await http.request<UploadRes>({\n url: `${BASE_URL}/upload/multipart/complete`,\n method: \"post\",\n data: {\n filePath,\n uploadId,\n parts,\n },\n });\n\n if (completeResult.error) {\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n completeResult.error.message,\n completeResult.status || 500,\n completeResult.error.details,\n );\n }\n\n if (!completeResult.data) {\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n \"Multipart upload complete returned an empty response.\",\n completeResult.status || 500,\n );\n }\n\n emitMultipartProgress(\"completed\");\n return completeResult;\n } catch (error) {\n uploadAbortController.abort();\n await abortMultipartUpload(http, filePath, uploadId);\n return createErrorResult(\n getErrorMessage(error, \"Multipart upload failed\"),\n error instanceof Error && error.name === \"AbortError\" ? 499 : 500,\n error,\n );\n }\n}\n\nexport function createS3Client(http: HttpClient = createHttpClient()): S3Client {\n return {\n async download(filename: string): Promise<ClientResult<Blob>> {\n if (!filename) {\n return {\n data: null,\n error: { message: \"Filename is required\", status: 400 },\n status: 400,\n };\n }\n\n const result = await http.request<Blob>({\n url: `${BASE_URL}/download`,\n method: \"get\",\n params: { filename },\n responseType: \"blob\",\n });\n\n return result;\n },\n\n async getMetadata(key: string): Promise<ClientResult<S3Metadata>> {\n if (!key) {\n return {\n data: null,\n error: { message: \"Key is required\", status: 400 },\n status: 400,\n };\n }\n\n return http.request<S3Metadata>({\n url: `${BASE_URL}/metadata`,\n method: \"get\",\n params: { key },\n });\n },\n\n async upload(\n file: File | Blob,\n options: S3UploadOptions = {},\n ): Promise<ClientResult<UploadRes>> {\n if (!file) {\n return {\n data: null,\n error: { message: \"File is required\", status: 400 },\n status: 400,\n };\n }\n\n if (shouldUseMultipartUpload(file)) {\n return await uploadWithMultipart(http, file, options);\n }\n\n const formData = new FormData();\n appendFormDataFile(formData, file, options.fileName);\n\n if (options.category) {\n formData.append(\"category\", options.category);\n }\n\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(0, file.size, \"uploading\"),\n );\n\n const result = await http.request<UploadRes>({\n url: `${BASE_URL}/upload`,\n method: \"post\",\n data: formData,\n headers: {\n \"Content-Type\": \"multipart/form-data\",\n },\n onUploadProgress: options.onProgress\n ? (event) => {\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(\n event.loaded,\n event.total ?? file.size,\n \"uploading\",\n ),\n );\n }\n : undefined,\n });\n\n if (!result.error) {\n emitUploadProgress(\n options.onProgress,\n createUploadProgress(file.size, file.size, \"completed\"),\n );\n }\n\n return result;\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/s3-client",
3
- "version": "1.1.20",
3
+ "version": "1.1.22",
4
4
  "description": "S3 storage client for file upload, download and management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -32,7 +32,7 @@
32
32
  "registry": "https://registry.npmjs.org/"
33
33
  },
34
34
  "dependencies": {
35
- "@amaster.ai/http-client": "1.1.20"
35
+ "@amaster.ai/http-client": "1.1.22"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "axios": "^1.11.0"