@better-s3/server 3.2.1 → 3.1044.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @better-s3/server
2
2
 
3
- Framework-agnostic S3 server handlers — presigned uploads, downloads, deletes, and multipart operations. Works with any `Request`/`Response` runtime (Next.js, Hono, Bun, Deno, Cloudflare Workers, etc).
3
+ Server-side presigned URL handlers for S3 upload, download, delete, and multipart. Works with any `Request`/`Response` runtime (Next.js, Hono, Bun, Deno, Cloudflare Workers, etc).
4
4
 
5
5
  ## Install
6
6
 
@@ -34,7 +34,7 @@ export const s3Config = {
34
34
 
35
35
  upload: {
36
36
  enabled: true,
37
- onUploaded: async ({ key, contentLength }) => {
37
+ onUploadConfirmed: async ({ key, contentLength }) => {
38
38
  // await db.file.create({ data: { key, contentLength } });
39
39
  },
40
40
  },
@@ -84,11 +84,11 @@ multipart: { enabled: true } // POST /presign/multipart/{init,part,complete,abo
84
84
  Run server-side logic at key points. Every hook receives the `Request` object. **Throw to reject** — returned as `{ message }` with status 403, or any status you set on the thrown error.
85
85
 
86
86
  ```
87
- Simple: upload.guard → upload.onPresigned → [S3] → upload.onUploaded
88
- Multipart: multipart.guard(init) → multipart.onInit
89
- multipart.guard(part) → [S3]
90
- multipart.guard(complete) → HeadObject → multipart.onComplete
91
- multipart.guard(abort) → multipart.onAbort
87
+ Simple: upload.presignGuard → upload.onPresigned → [S3] → upload.confirmGuard → upload.onUploadConfirmed
88
+ Multipart: multipart.initGuard → multipart.onInit
89
+ multipart.partGuard → [S3]
90
+ multipart.completeGuard → HeadObject → multipart.onComplete
91
+ multipart.abortGuard → multipart.onAbort
92
92
  ```
93
93
 
94
94
  ### Authentication
@@ -102,7 +102,7 @@ guard: async ({ request }) => {
102
102
 
103
103
  ### Quota check
104
104
 
105
- `fileSize` in `upload.guard` and `multipart.guard` (init only) is **declared by the client** — use it for pre-checks. `contentLength` in `onUploaded` / `multipart.onComplete` is verified by S3.
105
+ `fileSize` in `upload.presignGuard` and `multipart.initGuard` is **declared by the client** — use it for pre-checks. `contentLength` in `onUploadConfirmed` / `multipart.onComplete` is verified by S3.
106
106
 
107
107
  ```ts
108
108
  upload: {
@@ -116,8 +116,6 @@ upload: {
116
116
  },
117
117
  ```
118
118
 
119
- ````
120
-
121
119
  ## Multipart cost attacks
122
120
 
123
121
  S3 presigned `UploadPart` URLs cannot enforce per-part size. A malicious client can upload oversized parts — the post-complete `HeadObject` check will catch and delete the final object, but temporary part storage still accumulates cost. Without completion, orphaned parts stay on AWS S3 indefinitely (R2 cleans up after 7 days).
@@ -156,7 +154,7 @@ multipart: {
156
154
  },
157
155
  },
158
156
  }
159
- ````
157
+ ```
160
158
 
161
159
  **Cron job** — abort stale uploads and release S3 part storage:
162
160
 
@@ -1,3 +1,3 @@
1
- import {createPresignedPost}from'@aws-sdk/s3-presigned-post';import {HeadObjectCommand,GetObjectCommand,DeleteObjectCommand,CreateMultipartUploadCommand,UploadPartCommand,CompleteMultipartUploadCommand,AbortMultipartUploadCommand,GetObjectAclCommand}from'@aws-sdk/client-s3';import {getSignedUrl}from'@aws-sdk/s3-request-presigner';async function f(e){try{let n=await e.json();return n&&typeof n=="object"?n:null}catch{return null}}function m(e,n){let t=typeof e=="string"?e.trim():"";return t||Response.json({message:`${n} is required`},{status:400})}function k(e){let n=Number(e);return Number.isFinite(n)&&n>0?Math.floor(n):600}function p(e){return async n=>{try{return await e(n)}catch(t){let r=t instanceof Error?t.message:"Internal server error";return console.error("[S3 API]",r),Response.json({message:r},{status:500})}}}async function l(e,n){if(!e)return null;try{return await e(n),null}catch(t){let r=t instanceof Error?t.message:"Forbidden",a=typeof t?.status=="number"?t.status:403;return Response.json({message:r},{status:a})}}function g(e){let n=e.replace(/[^\x20-\x7E]/g,"_").replace(/["\\]/g,"_"),t=encodeURIComponent(e);return `attachment; filename="${n}"; filename*=UTF-8''${t}`}function H(e,n,t,r){return r!=="public-read"||!e?void 0:`${e(n).replace(/\/$/,"")}/${t}`}function C(e){if(!e)return;let n=e.match(/filename\*=UTF-8''([^;\s]+)/i);if(n)try{return decodeURIComponent(n[1])}catch{}return e.match(/filename="([^"]*)"/i)?.[1]}var M=5*1024*1024*1024;function I(e){return p(async n=>{let t=await f(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=m(t.key,"key");if(r instanceof Response)return r;let a=t.bucket?.trim()||e.defaultBucket,s=k(t.expiresIn),o=t.acl==="public-read"?"public-read":"private",i=t.contentType?.trim()||"application/octet-stream",u=typeof t.fileSize=="number"&&t.fileSize>0?Math.floor(t.fileSize):null;if(u!==null&&e.maxFileSize&&u>e.maxFileSize)return Response.json({message:`File size (${u} bytes) exceeds the maximum allowed size of ${e.maxFileSize} bytes`},{status:413});let d=await l(e.upload?.guard,{request:n,key:r,bucket:a,contentType:t.contentType,fileSize:u??void 0,metadata:t.metadata,acl:o});if(d)return d;let c={acl:o,"Content-Type":i};if(t.fileName&&(c["Content-Disposition"]=g(t.fileName)),t.metadata)for(let[y,S]of Object.entries(t.metadata))c[`x-amz-meta-${y}`]=S;let P=u??1,R=u??e.maxFileSize??M,{url:w,fields:h}=await createPresignedPost(e.s3,{Bucket:a,Key:r,Conditions:[["content-length-range",P,R]],Fields:c,Expires:s});return await e.upload?.onPresigned?.({request:n,key:r,bucket:a,contentType:t.contentType,metadata:t.metadata,acl:o,url:w,expiresIn:s}),Response.json({bucket:a,key:r,url:w,fields:h,expiresIn:s})})}async function x(e,n,t){try{return (await e.send(new GetObjectAclCommand({Bucket:n,Key:t}))).Grants?.some(s=>s.Grantee?.URI==="http://acs.amazonaws.com/groups/global/AllUsers"&&(s.Permission==="READ"||s.Permission==="FULL_CONTROL"))?"public-read":"private"}catch{return "private"}}function j(e){return p(async n=>{let t=await f(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=m(t.key,"key");if(r instanceof Response)return r;let a=t.bucket?.trim()||e.defaultBucket,s=await l(e.upload?.guard,{request:n,key:r,bucket:a});if(s)return s;let o=await e.s3.send(new HeadObjectCommand({Bucket:a,Key:r})),i=await x(e.s3,a,r),u=H(e.resolvePublicUrl,a,r,i),d=C(o.ContentDisposition),c={request:n,key:r,bucket:a,contentType:o.ContentType,contentLength:o.ContentLength??0,eTag:o.ETag?.replace(/"/g,""),metadata:o.Metadata,acl:i,fileName:d,publicUrl:u};return await e.upload?.onUploaded?.(c),Response.json({key:r,bucket:a,contentType:c.contentType,contentLength:c.contentLength,eTag:c.eTag,acl:i,fileName:d,publicUrl:u})})}function T(e){return p(async n=>{let{searchParams:t}=new URL(n.url),r=t.get("key")?.trim();if(!r)return Response.json({message:"key query parameter is required"},{status:400});let a=t.get("bucket")?.trim()||e.defaultBucket,s=k(t.get("expiresIn")),o=t.get("fileName")?.trim(),i=await l(e.download?.guard,{request:n,key:r,bucket:a,fileName:o||void 0});if(i)return i;try{await e.s3.send(new HeadObjectCommand({Bucket:a,Key:r}));}catch(d){let c=d?.name;if(c==="NoSuchKey"||c==="NotFound")return Response.json({message:"Object not found"},{status:404});throw d}let u=await getSignedUrl(e.s3,new GetObjectCommand({Bucket:a,Key:r,ResponseContentDisposition:o?g(o):"attachment"}),{expiresIn:s});return await e.download?.onPresigned?.({request:n,key:r,bucket:a,fileName:o||void 0,url:u,expiresIn:s}),Response.json({bucket:a,key:r,url:u,expiresIn:s})})}function N(e){return p(async n=>{let{searchParams:t}=new URL(n.url),r=t.get("key")?.trim();if(!r)return Response.json({message:"key query parameter is required"},{status:400});let a=t.get("bucket")?.trim()||e.defaultBucket,s=await l(e.delete?.guard,{request:n,key:r,bucket:a});if(s)return s;try{await e.s3.send(new HeadObjectCommand({Bucket:a,Key:r}));}catch{return Response.json({message:"Object not found"},{status:404})}return await e.s3.send(new DeleteObjectCommand({Bucket:a,Key:r})),await e.delete?.onDeleted?.({request:n,key:r,bucket:a}),Response.json({success:!0,bucket:a,key:r})})}function z(e){return p(async n=>{let t=await f(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=m(t.key,"key");if(r instanceof Response)return r;let a=t.bucket?.trim()||e.defaultBucket,s=t.acl==="public-read"?"public-read":"private",o=typeof t.fileSize=="number"&&t.fileSize>0?Math.floor(t.fileSize):void 0;if(o!==void 0&&e.maxFileSize&&o>e.maxFileSize)return Response.json({message:`File size (${o} bytes) exceeds the maximum allowed size of ${e.maxFileSize} bytes`},{status:413});let i=await l(e.multipart?.guard,{request:n,key:r,bucket:a,fileSize:o});if(i)return i;let{UploadId:u}=await e.s3.send(new CreateMultipartUploadCommand({Bucket:a,Key:r,ContentType:t.contentType,ContentDisposition:t.fileName?g(t.fileName):void 0,Metadata:t.metadata,ACL:s}));return await e.multipart?.onInit?.({request:n,key:r,bucket:a,uploadId:u,contentType:t.contentType,fileSize:o,metadata:t.metadata,acl:s}),Response.json({bucket:a,key:r,uploadId:u},{status:201})})}function U(e){return p(async n=>{let t=await f(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=m(t.key,"key");if(r instanceof Response)return r;let a=m(t.uploadId,"uploadId");if(a instanceof Response)return a;let s=Number(t.partNumber);if(!Number.isInteger(s)||s<=0)return Response.json({message:"partNumber must be a positive integer"},{status:400});let o=t.bucket?.trim()||e.defaultBucket,i=k(t.expiresIn),u=await l(e.multipart?.guard,{request:n,key:r,bucket:o});if(u)return u;let d=await getSignedUrl(e.s3,new UploadPartCommand({Bucket:o,Key:r,UploadId:a,PartNumber:s}),{expiresIn:i});return Response.json({presignedUrl:d,partNumber:s,uploadId:a,bucket:o,expiresIn:i})})}function O(e){return p(async n=>{let t=await f(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=m(t.key,"key");if(r instanceof Response)return r;let a=m(t.uploadId,"uploadId");if(a instanceof Response)return a;let s=(Array.isArray(t.parts)?t.parts:[]).map(({partNumber:y,eTag:S})=>({PartNumber:Number(y),ETag:String(S)})).filter(y=>Number.isInteger(y.PartNumber)&&y.ETag).sort((y,S)=>y.PartNumber-S.PartNumber);if(!s.length)return Response.json({message:"At least one valid part is required"},{status:400});let o=t.bucket?.trim()||e.defaultBucket,i=await l(e.multipart?.guard,{request:n,key:r,bucket:o});if(i)return i;await e.s3.send(new CompleteMultipartUploadCommand({Bucket:o,Key:r,UploadId:a,MultipartUpload:{Parts:s}}));let u=await e.s3.send(new HeadObjectCommand({Bucket:o,Key:r})),d=u.ContentLength??0,c=u.ContentType,P=u.ETag?.replace(/"/g,""),R=await x(e.s3,o,r),w=H(e.resolvePublicUrl,o,r,R),h=C(u.ContentDisposition);return e.maxFileSize&&d>e.maxFileSize?(await e.s3.send(new DeleteObjectCommand({Bucket:o,Key:r})).catch(()=>{}),Response.json({message:`File size (${d} bytes) exceeds the maximum allowed size of ${e.maxFileSize} bytes`},{status:422})):(await e.multipart?.onComplete?.({request:n,key:r,bucket:o,uploadId:a,contentLength:d,contentType:c,eTag:P,acl:R,fileName:h,publicUrl:w}),Response.json({bucket:o,key:r,uploadId:a,contentLength:d,contentType:c,eTag:P,acl:R,fileName:h,publicUrl:w}))})}function E(e){return p(async n=>{let t=await f(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=m(t.key,"key");if(r instanceof Response)return r;let a=m(t.uploadId,"uploadId");if(a instanceof Response)return a;let s=t.bucket?.trim()||e.defaultBucket,o=await l(e.multipart?.guard,{request:n,key:r,bucket:s});return o||(await e.s3.send(new AbortMultipartUploadCommand({Bucket:s,Key:r,UploadId:a})),await e.multipart?.onAbort?.({request:n,key:r,bucket:s,uploadId:a}),Response.json({bucket:s,key:r,uploadId:a,aborted:!0}))})}function B(e){return {presign:{upload:I(e),confirm:j(e),download:T(e)},multipart:{init:z(e),part:U(e),complete:O(e),abort:E(e)},delete:N(e)}}var b=()=>Response.json({message:"Not Found"},{status:404});function F(e){let n=B(e),t=e.basePath.replace(/\/$/,"");return async r=>{let a=await l(e.guard,{request:r});if(a)return a;let o=new URL(r.url).pathname.slice(t.length).replace(/^\//,""),i=r.method;return i==="POST"&&o==="presign/upload"?e.upload?.enabled?n.presign.upload(r):b():i==="POST"&&o==="presign/upload/confirm"?e.upload?.enabled?n.presign.confirm(r):b():i==="GET"&&o==="presign/download"?e.download?.enabled?n.presign.download(r):b():i==="DELETE"&&o==="delete"?e.delete?.enabled?n.delete(r):b():i==="POST"&&o==="presign/multipart/init"?e.multipart?.enabled?n.multipart.init(r):b():i==="POST"&&o==="presign/multipart/part"?e.multipart?.enabled?n.multipart.part(r):b():i==="POST"&&o==="presign/multipart/complete"?e.multipart?.enabled?n.multipart.complete(r):b():i==="POST"&&o==="presign/multipart/abort"?e.multipart?.enabled?n.multipart.abort(r):b():Response.json({message:"Not Found"},{status:404})}}function nt(e){return F(e)}
2
- export{nt as createRouteHandler};//# sourceMappingURL=next.js.map
1
+ import {createPresignedPost}from'@aws-sdk/s3-presigned-post';import {getSignedUrl}from'@aws-sdk/s3-request-presigner';import {PutObjectCommand,HeadObjectCommand,GetObjectCommand,DeleteObjectCommand,CreateMultipartUploadCommand,UploadPartCommand,ListPartsCommand,CompleteMultipartUploadCommand,AbortMultipartUploadCommand,GetObjectAclCommand}from'@aws-sdk/client-s3';async function b(e){try{let n=await e.json();return n&&typeof n=="object"?n:null}catch{return null}}function l(e,n){let t=typeof e=="string"?e.trim():"";return t||Response.json({message:`${n} is required`},{status:400})}function S(e){let n=Number(e);return Number.isFinite(n)&&n>0?Math.floor(n):600}function p(e){return async n=>{try{return await e(n)}catch(t){let r=t.code,s=r==="EAI_AGAIN"||r==="ECONNREFUSED"||r==="ECONNRESET"||r==="ETIMEDOUT"||r==="ENOTFOUND"?`S3 endpoint unreachable (${r}): check your endpoint URL and network connectivity`:t instanceof Error?t.message:"Internal server error";return console.error("[S3 API]",s),Response.json({message:s},{status:502})}}}async function d(e,n){if(!e)return null;try{return await e(n),null}catch(t){let r=t instanceof Error?t.message:"Forbidden",a=typeof t?.status=="number"?t.status:403;return Response.json({message:r},{status:a})}}function g(e){let n=e.replace(/[^\x20-\x7E]/g,"_").replace(/["\\]/g,"_"),t=encodeURIComponent(e);return `attachment; filename="${n}"; filename*=UTF-8''${t}`}function h(e,n,t,r){if(r!=="public-read"||!e)return;let a=e(n);if(a)return `${a.replace(/\/$/,"")}/${t}`}function T(e){if(!e)return;let n=e.match(/filename\*=UTF-8''([^;\s]+)/i);if(n)try{return decodeURIComponent(n[1])}catch{}return e.match(/filename="([^"]*)"/i)?.[1]}function M(e){return p(async n=>{let t=await b(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let a=t.bucket?.trim()||e.defaultBucket,s=S(t.expiresIn),o=t.acl==="public-read"?"public-read":"private",i=t.contentType?.trim()||"application/octet-stream",u=typeof t.fileSize=="number"&&t.fileSize>0?Math.floor(t.fileSize):null,f=await d(e.upload?.presignGuard,{request:n,key:r,bucket:a,contentType:t.contentType,fileSize:u??void 0,metadata:t.metadata,acl:o});if(f)return f;if((e.upload?.method??"post")==="put"){let w={"Content-Type":i};t.fileName&&(w["Content-Disposition"]=g(t.fileName));let R=await getSignedUrl(e.s3,new PutObjectCommand({Bucket:a,Key:r,ContentType:i,ACL:o,...t.fileName?{ContentDisposition:g(t.fileName)}:{}}),{expiresIn:s});return await e.upload?.onPresigned?.({request:n,key:r,bucket:a,contentType:t.contentType,metadata:t.metadata,acl:o,url:R,expiresIn:s}),Response.json({bucket:a,key:r,url:R,headers:w,expiresIn:s,method:"put"})}let C={acl:o,"Content-Type":i};if(t.fileName&&(C["Content-Disposition"]=g(t.fileName)),t.metadata)for(let[w,R]of Object.entries(t.metadata))C[`x-amz-meta-${w}`]=R;let y=u??1,H=u??void 0,{url:I,fields:x}=await createPresignedPost(e.s3,{Bucket:a,Key:r,Conditions:H!==void 0?[["content-length-range",y,H]]:[["content-length-range",y,Number.MAX_SAFE_INTEGER]],Fields:C,Expires:s});return await e.upload?.onPresigned?.({request:n,key:r,bucket:a,contentType:t.contentType,metadata:t.metadata,acl:o,url:I,expiresIn:s}),Response.json({bucket:a,key:r,url:I,fields:x,expiresIn:s,method:"post"})})}async function N(e,n,t){try{return (await e.send(new GetObjectAclCommand({Bucket:n,Key:t}))).Grants?.some(s=>s.Grantee?.URI==="http://acs.amazonaws.com/groups/global/AllUsers"&&(s.Permission==="READ"||s.Permission==="FULL_CONTROL"))?"public-read":"private"}catch{return "private"}}function v(e){return p(async n=>{let t=await b(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let a=t.bucket?.trim()||e.defaultBucket,s=await d(e.upload?.confirmGuard,{request:n,key:r,bucket:a});if(s)return s;let o=await e.s3.send(new HeadObjectCommand({Bucket:a,Key:r})),i=await N(e.s3,a,r),u=h(e.resolvePublicUrl,a,r,i),f=T(o.ContentDisposition),m={request:n,key:r,bucket:a,contentType:o.ContentType,contentLength:o.ContentLength??0,eTag:o.ETag?.replace(/"/g,""),metadata:o.Metadata,acl:i,fileName:f,publicUrl:u,versionId:o.VersionId,lastModified:o.LastModified?.toISOString()};return await e.upload?.onUploadConfirmed?.(m),Response.json({key:r,bucket:a,contentType:m.contentType,contentLength:m.contentLength,eTag:m.eTag,metadata:m.metadata??{},acl:i,fileName:f,publicUrl:u,versionId:m.versionId,lastModified:m.lastModified})})}function B(e){return p(async n=>{let{searchParams:t}=new URL(n.url),r=t.get("key")?.trim();if(!r)return Response.json({message:"key query parameter is required"},{status:400});let a=t.get("bucket")?.trim()||e.defaultBucket,s=S(t.get("expiresIn")),o=t.get("fileName")?.trim(),i=await d(e.download?.presignGuard,{request:n,key:r,bucket:a,fileName:o||void 0});if(i)return i;try{await e.s3.send(new HeadObjectCommand({Bucket:a,Key:r}));}catch(f){let m=f?.name;if(m==="NoSuchKey"||m==="NotFound")return Response.json({message:"Object not found"},{status:404});throw f}let u=await getSignedUrl(e.s3,new GetObjectCommand({Bucket:a,Key:r,ResponseContentDisposition:o?g(o):"attachment"}),{expiresIn:s});return await e.download?.onPresigned?.({request:n,key:r,bucket:a,fileName:o||void 0,url:u,expiresIn:s}),Response.json({bucket:a,key:r,url:u,expiresIn:s})})}function A(e){return p(async n=>{let{searchParams:t}=new URL(n.url),r=t.get("key")?.trim();if(!r)return Response.json({message:"key query parameter is required"},{status:400});let a=t.get("bucket")?.trim()||e.defaultBucket,s=await d(e.delete?.deleteGuard,{request:n,key:r,bucket:a});if(s)return s;try{await e.s3.send(new HeadObjectCommand({Bucket:a,Key:r}));}catch{return Response.json({message:"Object not found"},{status:404})}return await e.s3.send(new DeleteObjectCommand({Bucket:a,Key:r})),await e.delete?.onDeleted?.({request:n,key:r,bucket:a}),Response.json({success:!0,bucket:a,key:r})})}function D(e){return p(async n=>{let t=await b(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let a=t.bucket?.trim()||e.defaultBucket,s=t.acl==="public-read"?"public-read":"private",o=typeof t.fileSize=="number"&&t.fileSize>0?Math.floor(t.fileSize):void 0,i=await d(e.multipart?.initGuard,{request:n,key:r,bucket:a,fileSize:o});if(i)return i;let{UploadId:u}=await e.s3.send(new CreateMultipartUploadCommand({Bucket:a,Key:r,ContentType:t.contentType,ContentDisposition:t.fileName?g(t.fileName):void 0,Metadata:t.metadata,ACL:s}));return await e.multipart?.onInit?.({request:n,key:r,bucket:a,uploadId:u,contentType:t.contentType,fileSize:o,metadata:t.metadata,acl:s}),Response.json({bucket:a,key:r,uploadId:u},{status:201})})}function L(e){return p(async n=>{let t=await b(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let a=l(t.uploadId,"uploadId");if(a instanceof Response)return a;let s=Number(t.partNumber);if(!Number.isInteger(s)||s<=0)return Response.json({message:"partNumber must be a positive integer"},{status:400});let o=t.bucket?.trim()||e.defaultBucket,i=S(t.expiresIn),u=await d(e.multipart?.partGuard,{request:n,key:r,bucket:o});if(u)return u;let f=await getSignedUrl(e.s3,new UploadPartCommand({Bucket:o,Key:r,UploadId:a,PartNumber:s}),{expiresIn:i});return Response.json({presignedUrl:f,partNumber:s,uploadId:a,bucket:o,expiresIn:i})})}function F(e){return p(async n=>{let t=await b(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let a=l(t.uploadId,"uploadId");if(a instanceof Response)return a;let s=(Array.isArray(t.parts)?t.parts:[]).map(({partNumber:c})=>Number(c)).filter(c=>Number.isInteger(c)&&c>0).sort((c,P)=>c-P);if(!s.length)return Response.json({message:"At least one valid part is required"},{status:400});let o=t.bucket?.trim()||e.defaultBucket,i=await d(e.multipart?.completeGuard,{request:n,key:r,bucket:o});if(i)return i;let f=(await e.s3.send(new ListPartsCommand({Bucket:o,Key:r,UploadId:a}))).Parts??[],m=s.map(c=>{let P=f.find(J=>J.PartNumber===c);return {PartNumber:c,ETag:P?.ETag??""}}),C=await e.s3.send(new CompleteMultipartUploadCommand({Bucket:o,Key:r,UploadId:a,MultipartUpload:{Parts:m}})),y=await e.s3.send(new HeadObjectCommand({Bucket:o,Key:r}));for(let c=0;c<4&&!y.ContentLength;c++)await new Promise(P=>setTimeout(P,250*2**c)),y=await e.s3.send(new HeadObjectCommand({Bucket:o,Key:r}));let H=y.ContentLength??0,I=y.ContentType,x=(y.ETag??C.ETag??"").replace(/"/g,""),w=y.Metadata??{},R=y.VersionId,j=y.LastModified?.toISOString(),E=await N(e.s3,o,r),U=h(e.resolvePublicUrl,o,r,E),O=T(y.ContentDisposition);return await e.multipart?.onComplete?.({request:n,key:r,bucket:o,uploadId:a,contentLength:H,contentType:I,eTag:x,metadata:w,acl:E,fileName:O,publicUrl:U,versionId:R,lastModified:j}),Response.json({bucket:o,key:r,uploadId:a,contentLength:H,contentType:I,eTag:x,metadata:w,acl:E,fileName:O,publicUrl:U,versionId:R,lastModified:j})})}function K(e){return p(async n=>{let t=await b(n);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let a=l(t.uploadId,"uploadId");if(a instanceof Response)return a;let s=t.bucket?.trim()||e.defaultBucket,o=await d(e.multipart?.abortGuard,{request:n,key:r,bucket:s});return o||(await e.s3.send(new AbortMultipartUploadCommand({Bucket:s,Key:r,UploadId:a})),await e.multipart?.onAbort?.({request:n,key:r,bucket:s,uploadId:a}),Response.json({bucket:s,key:r,uploadId:a,aborted:!0}))})}function G(e){return {presign:{upload:M(e),confirm:v(e),download:B(e)},multipart:{init:D(e),part:L(e),complete:F(e),abort:K(e)},delete:A(e)}}var k=()=>Response.json({message:"Not Found"},{status:404});function $(e){let n=G(e),t=e.basePath.replace(/\/$/,"");return async r=>{let a=await d(e.guard,{request:r});if(a)return a;let o=new URL(r.url).pathname.slice(t.length).replace(/^\//,""),i=r.method;return i==="POST"&&o==="presign/upload"?e.upload?.enabled?n.presign.upload(r):k():i==="POST"&&o==="presign/upload/confirm"?e.upload?.enabled?n.presign.confirm(r):k():i==="GET"&&o==="presign/download"?e.download?.enabled?n.presign.download(r):k():i==="DELETE"&&o==="delete"?e.delete?.enabled?n.delete(r):k():i==="POST"&&o==="presign/multipart/init"?e.multipart?.enabled?n.multipart.init(r):k():i==="POST"&&o==="presign/multipart/part"?e.multipart?.enabled?n.multipart.part(r):k():i==="POST"&&o==="presign/multipart/complete"?e.multipart?.enabled?n.multipart.complete(r):k():i==="POST"&&o==="presign/multipart/abort"?e.multipart?.enabled?n.multipart.abort(r):k():Response.json({message:"Not Found"},{status:404})}}function ft(e){return $(e)}
2
+ export{ft as createRouteHandler};//# sourceMappingURL=next.js.map
3
3
  //# sourceMappingURL=next.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/internal-helpers.ts","../../src/helpers/build-content-disposition.ts","../../src/helpers/build-public-url.ts","../../src/helpers/parse-file-name.ts","../../src/handlers/presign/upload.ts","../../src/helpers/server/resolve-object-acl.ts","../../src/handlers/presign/confirm.ts","../../src/handlers/presign/download.ts","../../src/handlers/delete.ts","../../src/handlers/multipart/init.ts","../../src/handlers/multipart/part.ts","../../src/handlers/multipart/complete.ts","../../src/handlers/multipart/abort.ts","../../src/create-handlers.ts","../../src/router.ts","../../src/adapters/next.ts"],"names":["parseBody","request","body","requireString","value","name","trimmed","normalizeExpiresIn","withS3ErrorHandler","handler","err","message","runHook","hook","context","status","buildContentDisposition","fileName","ascii","encoded","buildPublicUrl","publicUrlBase","bucket","key","acl","parseFileName","contentDisposition","utf8Match","MAX_UPLOAD_SIZE","createUploadHandler","config","expiresIn","contentType","fileSize","guardResult","fields","k","v","rangeMin","rangeMax","url","signedFields","createPresignedPost","resolveObjectAcl","s3","GetObjectAclCommand","g","createConfirmHandler","head","HeadObjectCommand","publicUrl","createDownloadHandler","searchParams","getSignedUrl","GetObjectCommand","createDeleteHandler","DeleteObjectCommand","createMultipartInitHandler","UploadId","CreateMultipartUploadCommand","createMultipartPartHandler","uploadId","partNumber","presignedUrl","UploadPartCommand","createMultipartCompleteHandler","parts","eTag","p","a","b","CompleteMultipartUploadCommand","contentLength","createMultipartAbortHandler","AbortMultipartUploadCommand","createHandlers","disabled","createRouter","handlers","base","subpath","method","createRouteHandler"],"mappings":"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","file":"next.js","sourcesContent":["export async function parseBody<T extends Record<string, unknown>>(\n request: Request,\n): Promise<T | null> {\n try {\n const body = await request.json();\n return body && typeof body === \"object\" ? (body as T) : null;\n } catch {\n return null;\n }\n}\n\nexport function requireString(value: unknown, name: string): string | Response {\n const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n if (!trimmed) {\n return Response.json({ message: `${name} is required` }, { status: 400 });\n }\n return trimmed;\n}\n\nexport function normalizeExpiresIn(value: unknown): number {\n const n = Number(value);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 600;\n}\n\nexport function withS3ErrorHandler(\n handler: (request: Request) => Promise<Response>,\n) {\n return async (request: Request) => {\n try {\n return await handler(request);\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Internal server error\";\n console.error(\"[S3 API]\", message);\n return Response.json({ message }, { status: 500 });\n }\n };\n}\n\n/**\n * Run a server hook. Returns a Response if the hook rejects (throws),\n * or `null` if the hook passes (or is undefined).\n */\nexport async function runHook<T>(\n hook: ((context: T) => Promise<void> | void) | undefined,\n context: T,\n): Promise<Response | null> {\n if (!hook) return null;\n try {\n await hook(context);\n return null;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Forbidden\";\n const status =\n typeof (err as Record<string, unknown>)?.status === \"number\"\n ? ((err as Record<string, unknown>).status as number)\n : 403;\n return Response.json({ message }, { status });\n }\n}\n","/**\n * Builds a RFC 6266 `Content-Disposition` value for a file attachment.\n *\n * Includes both the ASCII-safe `filename` fallback and the RFC 5987\n * `filename*` parameter for full Unicode support, so the original file\n * name (including non-ASCII characters) is preserved across all browsers\n * and HTTP clients.\n *\n * @example\n * buildContentDisposition(\"résumé finale.pdf\")\n * // → 'attachment; filename=\"resume finale.pdf\"; filename*=UTF-8\\'\\'r%C3%A9sum%C3%A9%20finale.pdf'\n */\nexport function buildContentDisposition(fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7E]/g, \"_\").replace(/[\"\\\\]/g, \"_\");\n const encoded = encodeURIComponent(fileName);\n return `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n}\n","/**\n * Builds the public URL for an S3 object.\n *\n * Returns `undefined` when:\n * - the object is `\"private\"`, or\n * - `publicUrlBase` is not provided.\n *\n * @param publicUrlBase - A function that returns the base URL for a given bucket.\n * Matches the `publicUrlBase` field of `S3HandlerConfig`.\n */\nexport function buildPublicUrl(\n publicUrlBase: ((bucket: string) => string) | undefined,\n bucket: string,\n key: string,\n acl: \"public-read\" | \"private\",\n): string | undefined {\n if (acl !== \"public-read\" || !publicUrlBase) return undefined;\n const base = publicUrlBase(bucket).replace(/\\/$/, \"\");\n return `${base}/${key}`;\n}\n","/**\n * Parses the display file name from a `Content-Disposition` header value.\n *\n * Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over\n * the plain ASCII `filename=\"…\"` fallback.\n *\n * Returns `undefined` when no filename can be extracted.\n */\nexport function parseFileName(\n contentDisposition: string | undefined,\n): string | undefined {\n if (!contentDisposition) return undefined;\n\n const utf8Match = contentDisposition.match(/filename\\*=UTF-8''([^;\\s]+)/i);\n if (utf8Match) {\n try {\n return decodeURIComponent(utf8Match[1]);\n } catch {\n /* ignore malformed encoding */\n }\n }\n\n const asciiMatch = contentDisposition.match(/filename=\"([^\"]*)\"/i);\n return asciiMatch?.[1];\n}\n","import { createPresignedPost } from \"@aws-sdk/s3-presigned-post\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildContentDisposition } from \"../../helpers\";\n\n// 5 GiB — S3 single-object upload limit\nconst MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024;\n\ntype Payload = {\n key: string;\n contentType?: string;\n /**\n * Exact byte size of the file the client intends to upload.\n * When provided the presigned POST policy locks `content-length-range` to\n * `[fileSize, fileSize]`, so S3 rejects any upload that is not exactly this\n * size — even if the client tampers with the payload.\n * When omitted the range is `[1, maxFileSize ?? 5 GiB]`.\n */\n fileSize?: number;\n metadata?: Record<string, string>;\n bucket?: string;\n expiresIn?: number;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition: attachment; filename=\"...\"` on the S3 object. */\n fileName?: string;\n};\n\nexport function createUploadHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(body.expiresIn);\n const acl = body.acl === \"public-read\" ? \"public-read\" : \"private\";\n const contentType = body.contentType?.trim() || \"application/octet-stream\";\n const fileSize =\n typeof body.fileSize === \"number\" && body.fileSize > 0\n ? Math.floor(body.fileSize)\n : null;\n\n if (\n fileSize !== null &&\n config.maxFileSize &&\n fileSize > config.maxFileSize\n ) {\n return Response.json(\n {\n message: `File size (${fileSize} bytes) exceeds the maximum allowed size of ${config.maxFileSize} bytes`,\n },\n { status: 413 },\n );\n }\n\n const guardResult = await runHook(config.upload?.guard, {\n request,\n key,\n bucket,\n contentType: body.contentType,\n fileSize: fileSize ?? undefined,\n metadata: body.metadata,\n acl,\n });\n if (guardResult) return guardResult;\n\n // Build presigned POST fields. Fields are embedded in the signed policy\n // so the client cannot change them without invalidating the signature.\n // Note: the `acl` field is the correct presigned POST field per AWS spec.\n // Some S3-compatible providers ignore it; PutObjectAcl is called in the\n // confirm step as a reliable post-upload fallback.\n const fields: Record<string, string> = { acl, \"Content-Type\": contentType };\n\n if (body.fileName) {\n fields[\"Content-Disposition\"] = buildContentDisposition(body.fileName);\n }\n\n if (body.metadata) {\n for (const [k, v] of Object.entries(body.metadata)) {\n fields[`x-amz-meta-${k}`] = v;\n }\n }\n\n // `content-length-range` is enforced by S3 at the storage layer —\n // uploads outside this range are rejected before the object is stored.\n //\n // When the client declares fileSize: S3 locks the range to exactly that\n // number of bytes, so uploading a different-sized file is impossible.\n // When fileSize is not declared: falls back to [1, maxFileSize ?? 5 GiB].\n const rangeMin = fileSize ?? 1;\n const rangeMax = fileSize ?? config.maxFileSize ?? MAX_UPLOAD_SIZE;\n\n const { url, fields: signedFields } = await createPresignedPost(config.s3, {\n Bucket: bucket,\n Key: key,\n Conditions: [[\"content-length-range\", rangeMin, rangeMax]],\n Fields: fields,\n Expires: expiresIn,\n });\n\n await config.upload?.onPresigned?.({\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n acl,\n url,\n expiresIn,\n });\n\n return Response.json({ bucket, key, url, fields: signedFields, expiresIn });\n });\n}\n","import { GetObjectAclCommand, type S3Client } from \"@aws-sdk/client-s3\";\n\n/**\n * Determines the ACL of an S3 object by querying `GetObjectAcl`.\n *\n * Returns `\"public-read\"` when the `AllUsers` grantee has `READ` or\n * `FULL_CONTROL` permission; falls back to `\"private\"` when the provider\n * does not support the operation (e.g. Cloudflare R2).\n */\nexport async function resolveObjectAcl(\n s3: S3Client,\n bucket: string,\n key: string,\n): Promise<\"public-read\" | \"private\"> {\n try {\n const result = await s3.send(\n new GetObjectAclCommand({ Bucket: bucket, Key: key }),\n );\n const isPublic = result.Grants?.some(\n (g) =>\n g.Grantee?.URI === \"http://acs.amazonaws.com/groups/global/AllUsers\" &&\n (g.Permission === \"READ\" || g.Permission === \"FULL_CONTROL\"),\n );\n return isPublic ? \"public-read\" : \"private\";\n } catch {\n return \"private\";\n }\n}\n","import { HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildPublicUrl, parseFileName } from \"../../helpers\";\nimport { resolveObjectAcl } from \"../../helpers/server\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n};\n\nexport function createConfirmHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.upload?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n\n const acl = await resolveObjectAcl(config.s3, bucket, key);\n const publicUrl = buildPublicUrl(config.resolvePublicUrl, bucket, key, acl);\n const fileName = parseFileName(head.ContentDisposition);\n\n const context = {\n request,\n key,\n bucket,\n contentType: head.ContentType,\n contentLength: head.ContentLength ?? 0,\n eTag: head.ETag?.replace(/\"/g, \"\"),\n metadata: head.Metadata,\n acl,\n fileName,\n publicUrl,\n };\n\n await config.upload?.onUploaded?.(context);\n\n return Response.json({\n key,\n bucket,\n contentType: context.contentType,\n contentLength: context.contentLength,\n eTag: context.eTag,\n acl,\n fileName,\n publicUrl,\n });\n });\n}\n","import { GetObjectCommand, HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildContentDisposition } from \"../../helpers\";\n\nexport function createDownloadHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const { searchParams } = new URL(request.url);\n const key = searchParams.get(\"key\")?.trim();\n if (!key) {\n return Response.json(\n { message: \"key query parameter is required\" },\n { status: 400 },\n );\n }\n\n const bucket = searchParams.get(\"bucket\")?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(searchParams.get(\"expiresIn\"));\n const fileName = searchParams.get(\"fileName\")?.trim();\n\n const guardResult = await runHook(config.download?.guard, {\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n });\n if (guardResult) return guardResult;\n\n try {\n await config.s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n } catch (err: unknown) {\n const name = (err as { name?: string })?.name;\n if (name === \"NoSuchKey\" || name === \"NotFound\") {\n return Response.json({ message: \"Object not found\" }, { status: 404 });\n }\n throw err;\n }\n\n const url = await getSignedUrl(\n config.s3,\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n ResponseContentDisposition: fileName\n ? buildContentDisposition(fileName)\n : \"attachment\",\n }),\n { expiresIn },\n );\n\n await config.download?.onPresigned?.({\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n url,\n expiresIn,\n });\n\n return Response.json({ bucket, key, url, expiresIn });\n });\n}\n","import { DeleteObjectCommand, HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../types\";\nimport { runHook, withS3ErrorHandler } from \"../internal-helpers\";\n\nexport function createDeleteHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const { searchParams } = new URL(request.url);\n const key = searchParams.get(\"key\")?.trim();\n if (!key) {\n return Response.json(\n { message: \"key query parameter is required\" },\n { status: 400 },\n );\n }\n\n const bucket = searchParams.get(\"bucket\")?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.delete?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n try {\n await config.s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n } catch {\n return Response.json({ message: \"Object not found\" }, { status: 404 });\n }\n\n await config.s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));\n\n await config.delete?.onDeleted?.({ request, key, bucket });\n\n return Response.json({ success: true, bucket, key });\n });\n}\n","import { CreateMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildContentDisposition } from \"../../helpers\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n contentType?: string;\n /** Declared total byte size of the file being uploaded. */\n fileSize?: number;\n metadata?: Record<string, string>;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition: attachment; filename=\"...\"` on the S3 object. */\n fileName?: string;\n};\n\nexport function createMultipartInitHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const acl = body.acl === \"public-read\" ? \"public-read\" : \"private\";\n const fileSize =\n typeof body.fileSize === \"number\" && body.fileSize > 0\n ? Math.floor(body.fileSize)\n : undefined;\n\n if (\n fileSize !== undefined &&\n config.maxFileSize &&\n fileSize > config.maxFileSize\n ) {\n return Response.json(\n {\n message: `File size (${fileSize} bytes) exceeds the maximum allowed size of ${config.maxFileSize} bytes`,\n },\n { status: 413 },\n );\n }\n\n const guardResult = await runHook(config.multipart?.guard, {\n request,\n key,\n bucket,\n fileSize,\n });\n if (guardResult) return guardResult;\n\n const { UploadId } = await config.s3.send(\n new CreateMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n ContentType: body.contentType,\n ContentDisposition: body.fileName\n ? buildContentDisposition(body.fileName)\n : undefined,\n Metadata: body.metadata,\n ACL: acl,\n }),\n );\n\n await config.multipart?.onInit?.({\n request,\n key,\n bucket,\n uploadId: UploadId!,\n contentType: body.contentType,\n fileSize,\n metadata: body.metadata,\n acl,\n });\n\n return Response.json({ bucket, key, uploadId: UploadId }, { status: 201 });\n });\n}\n","import { UploadPartCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\n\ntype Payload = {\n key: string;\n uploadId: string;\n partNumber: number;\n bucket?: string;\n expiresIn?: number;\n};\n\nexport function createMultipartPartHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const partNumber = Number(body.partNumber);\n if (!Number.isInteger(partNumber) || partNumber <= 0) {\n return Response.json(\n { message: \"partNumber must be a positive integer\" },\n { status: 400 },\n );\n }\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(body.expiresIn);\n\n const guardResult = await runHook(config.multipart?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const presignedUrl = await getSignedUrl(\n config.s3,\n new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n }),\n { expiresIn },\n );\n\n return Response.json({\n presignedUrl,\n partNumber,\n uploadId,\n bucket,\n expiresIn,\n });\n });\n}\n","import {\n CompleteMultipartUploadCommand,\n HeadObjectCommand,\n DeleteObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildPublicUrl, parseFileName } from \"../../helpers\";\nimport { resolveObjectAcl } from \"../../helpers/server\";\n\ntype PartEntry = {\n partNumber: number;\n eTag: string;\n};\n\ntype Payload = {\n key: string;\n uploadId: string;\n bucket?: string;\n parts: PartEntry[];\n};\n\nexport function createMultipartCompleteHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const parts = (Array.isArray(body.parts) ? body.parts : [])\n .map(({ partNumber, eTag }) => ({\n PartNumber: Number(partNumber),\n ETag: String(eTag),\n }))\n .filter((p) => Number.isInteger(p.PartNumber) && p.ETag)\n .sort((a, b) => a.PartNumber - b.PartNumber);\n\n if (!parts.length) {\n return Response.json(\n { message: \"At least one valid part is required\" },\n { status: 400 },\n );\n }\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.multipart?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n await config.s3.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: { Parts: parts },\n }),\n );\n\n // Verify the actual uploaded object metadata via HeadObject.\n // This is the server's only opportunity to enforce maxFileSize for\n // multipart uploads, since individual parts bypass size checks.\n const head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n const contentLength = head.ContentLength ?? 0;\n const contentType = head.ContentType;\n const eTag = head.ETag?.replace(/\"/g, \"\");\n\n const acl = await resolveObjectAcl(config.s3, bucket, key);\n const publicUrl = buildPublicUrl(config.resolvePublicUrl, bucket, key, acl);\n const fileName = parseFileName(head.ContentDisposition);\n\n if (config.maxFileSize && contentLength > config.maxFileSize) {\n // Delete the object and reject the request. The client has uploaded\n // more data than the server policy allows.\n await config.s3\n .send(new DeleteObjectCommand({ Bucket: bucket, Key: key }))\n .catch(() => {});\n return Response.json(\n {\n message: `File size (${contentLength} bytes) exceeds the maximum allowed size of ${config.maxFileSize} bytes`,\n },\n { status: 422 },\n );\n }\n\n await config.multipart?.onComplete?.({\n request,\n key,\n bucket,\n uploadId,\n contentLength,\n contentType,\n eTag,\n acl,\n fileName,\n publicUrl,\n });\n\n return Response.json({\n bucket,\n key,\n uploadId,\n contentLength,\n contentType,\n eTag,\n acl,\n fileName,\n publicUrl,\n });\n });\n}\n","import { AbortMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\n\ntype Payload = {\n key: string;\n uploadId: string;\n bucket?: string;\n};\n\nexport function createMultipartAbortHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.multipart?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n await config.s3.send(\n new AbortMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n }),\n );\n\n await config.multipart?.onAbort?.({\n request,\n key,\n bucket,\n uploadId,\n });\n\n return Response.json({ bucket, key, uploadId, aborted: true });\n });\n}\n","import type { S3HandlerConfig, S3Handlers } from \"./types\";\nimport { createUploadHandler } from \"./handlers/presign/upload\";\nimport { createConfirmHandler } from \"./handlers/presign/confirm\";\nimport { createDownloadHandler } from \"./handlers/presign/download\";\nimport { createDeleteHandler } from \"./handlers/delete\";\nimport { createMultipartInitHandler } from \"./handlers/multipart/init\";\nimport { createMultipartPartHandler } from \"./handlers/multipart/part\";\nimport { createMultipartCompleteHandler } from \"./handlers/multipart/complete\";\nimport { createMultipartAbortHandler } from \"./handlers/multipart/abort\";\n\nexport function createHandlers(config: S3HandlerConfig): S3Handlers {\n return {\n presign: {\n upload: createUploadHandler(config),\n confirm: createConfirmHandler(config),\n download: createDownloadHandler(config),\n },\n multipart: {\n init: createMultipartInitHandler(config),\n part: createMultipartPartHandler(config),\n complete: createMultipartCompleteHandler(config),\n abort: createMultipartAbortHandler(config),\n },\n delete: createDeleteHandler(config),\n };\n}\n","import type { S3RouteHandlerConfig } from \"./types\";\nimport { createHandlers } from \"./create-handlers\";\nimport { runHook } from \"./internal-helpers\";\n\nconst disabled = () => Response.json({ message: \"Not Found\" }, { status: 404 });\n\nexport function createRouter(config: S3RouteHandlerConfig) {\n const handlers = createHandlers(config);\n const base = config.basePath.replace(/\\/$/, \"\");\n\n return async (request: Request): Promise<Response> => {\n // Global guard — runs before every request\n const guardResult = await runHook(config.guard, { request });\n if (guardResult) return guardResult;\n\n const url = new URL(request.url);\n const subpath = url.pathname.slice(base.length).replace(/^\\//, \"\");\n const method = request.method;\n\n if (method === \"POST\" && subpath === \"presign/upload\")\n return config.upload?.enabled\n ? handlers.presign.upload(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/upload/confirm\")\n return config.upload?.enabled\n ? handlers.presign.confirm(request)\n : disabled();\n if (method === \"GET\" && subpath === \"presign/download\")\n return config.download?.enabled\n ? handlers.presign.download(request)\n : disabled();\n if (method === \"DELETE\" && subpath === \"delete\")\n return config.delete?.enabled ? handlers.delete(request) : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/init\")\n return config.multipart?.enabled\n ? handlers.multipart.init(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/part\")\n return config.multipart?.enabled\n ? handlers.multipart.part(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/complete\")\n return config.multipart?.enabled\n ? handlers.multipart.complete(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/abort\")\n return config.multipart?.enabled\n ? handlers.multipart.abort(request)\n : disabled();\n\n return Response.json({ message: \"Not Found\" }, { status: 404 });\n };\n}\n","import { createRouter } from \"../router\";\nimport type { S3RouteHandlerConfig } from \"../types\";\n\n/**\n * Creates a Next.js App Router catch-all route handler.\n *\n * Usage in `app/api/s3/[...s3]/route.ts`:\n * ```ts\n * import { createRouteHandler } from \"@better-s3/server/next\";\n *\n * const handler = createRouteHandler({ s3, defaultBucket: \"my-bucket\", basePath: \"/api/s3\" });\n * export { handler as GET, handler as POST, handler as DELETE };\n * ```\n */\nexport function createRouteHandler(config: S3RouteHandlerConfig) {\n return createRouter(config);\n}\n"]}
1
+ {"version":3,"sources":["../../src/internal-helpers.ts","../../src/helpers/build-content-disposition.ts","../../src/helpers/build-public-url.ts","../../src/helpers/parse-file-name.ts","../../src/handlers/presign/upload.ts","../../src/helpers/server/resolve-object-acl.ts","../../src/handlers/presign/confirm.ts","../../src/handlers/presign/download.ts","../../src/handlers/delete.ts","../../src/handlers/multipart/init.ts","../../src/handlers/multipart/part.ts","../../src/handlers/multipart/complete.ts","../../src/handlers/multipart/abort.ts","../../src/create-handlers.ts","../../src/router.ts","../../src/adapters/next.ts"],"names":["parseBody","request","body","requireString","value","name","trimmed","normalizeExpiresIn","withS3ErrorHandler","handler","err","code","message","runHook","hook","context","status","buildContentDisposition","fileName","ascii","encoded","buildPublicUrl","publicUrlBase","bucket","key","acl","base","parseFileName","contentDisposition","utf8Match","createUploadHandler","config","expiresIn","contentType","fileSize","guardResult","putHeaders","url","getSignedUrl","PutObjectCommand","fields","k","v","rangeMin","rangeMax","signedFields","createPresignedPost","resolveObjectAcl","s3","GetObjectAclCommand","g","createConfirmHandler","head","HeadObjectCommand","publicUrl","createDownloadHandler","searchParams","GetObjectCommand","createDeleteHandler","DeleteObjectCommand","createMultipartInitHandler","UploadId","CreateMultipartUploadCommand","createMultipartPartHandler","uploadId","partNumber","presignedUrl","UploadPartCommand","createMultipartCompleteHandler","parts","n","a","b","listedParts","ListPartsCommand","completeParts","found","p","completeResult","CompleteMultipartUploadCommand","attempt","r","contentLength","eTag","metadata","versionId","lastModified","createMultipartAbortHandler","AbortMultipartUploadCommand","createHandlers","disabled","createRouter","handlers","subpath","method","createRouteHandler"],"mappings":"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","file":"next.js","sourcesContent":["export async function parseBody<T extends Record<string, unknown>>(\n request: Request,\n): Promise<T | null> {\n try {\n const body = await request.json();\n return body && typeof body === \"object\" ? (body as T) : null;\n } catch {\n return null;\n }\n}\n\nexport function requireString(value: unknown, name: string): string | Response {\n const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n if (!trimmed) {\n return Response.json({ message: `${name} is required` }, { status: 400 });\n }\n return trimmed;\n}\n\nexport function normalizeExpiresIn(value: unknown): number {\n const n = Number(value);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 600;\n}\n\nexport function withS3ErrorHandler(\n handler: (request: Request) => Promise<Response>,\n) {\n return async (request: Request) => {\n try {\n return await handler(request);\n } catch (err) {\n const code = (err as { code?: string }).code;\n const isNetworkError =\n code === \"EAI_AGAIN\" ||\n code === \"ECONNREFUSED\" ||\n code === \"ECONNRESET\" ||\n code === \"ETIMEDOUT\" ||\n code === \"ENOTFOUND\";\n\n const message = isNetworkError\n ? `S3 endpoint unreachable (${code}): check your endpoint URL and network connectivity`\n : err instanceof Error\n ? err.message\n : \"Internal server error\";\n\n console.error(\"[S3 API]\", message);\n return Response.json({ message }, { status: 502 });\n }\n };\n}\n\n/**\n * Run a server hook. Returns a Response if the hook rejects (throws),\n * or `null` if the hook passes (or is undefined).\n */\nexport async function runHook<T>(\n hook: ((context: T) => Promise<void> | void) | undefined,\n context: T,\n): Promise<Response | null> {\n if (!hook) return null;\n try {\n await hook(context);\n return null;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Forbidden\";\n const status =\n typeof (err as Record<string, unknown>)?.status === \"number\"\n ? ((err as Record<string, unknown>).status as number)\n : 403;\n return Response.json({ message }, { status });\n }\n}\n","/**\n * Builds a RFC 6266 `Content-Disposition` value for a file attachment.\n *\n * Includes both the ASCII-safe `filename` fallback and the RFC 5987\n * `filename*` parameter for full Unicode support, so the original file\n * name (including non-ASCII characters) is preserved across all browsers\n * and HTTP clients.\n *\n * @example\n * buildContentDisposition(\"résumé finale.pdf\")\n * // → 'attachment; filename=\"resume finale.pdf\"; filename*=UTF-8\\'\\'r%C3%A9sum%C3%A9%20finale.pdf'\n */\nexport function buildContentDisposition(fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7E]/g, \"_\").replace(/[\"\\\\]/g, \"_\");\n const encoded = encodeURIComponent(fileName);\n return `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n}\n","/**\n * Builds the public URL for an S3 object.\n *\n * Returns `undefined` when:\n * - the object is `\"private\"`,\n * - `publicUrlBase` is not provided, or\n * - `publicUrlBase` returns `undefined` for the given bucket.\n *\n * @param publicUrlBase - A function that returns the base URL for a given bucket,\n * or `undefined` when no public URL is available for that bucket.\n * Matches the `resolvePublicUrl` field of `S3HandlerConfig`.\n */\nexport function buildPublicUrl(\n publicUrlBase: ((bucket: string) => string | undefined) | undefined,\n bucket: string,\n key: string,\n acl: \"public-read\" | \"private\",\n): string | undefined {\n if (acl !== \"public-read\" || !publicUrlBase) return undefined;\n const base = publicUrlBase(bucket);\n if (!base) return undefined;\n return `${base.replace(/\\/$/, \"\")}/${key}`;\n}\n","/**\n * Parses the display file name from a `Content-Disposition` header value.\n *\n * Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over\n * the plain ASCII `filename=\"…\"` fallback.\n *\n * Returns `undefined` when no filename can be extracted.\n */\nexport function parseFileName(\n contentDisposition: string | undefined,\n): string | undefined {\n if (!contentDisposition) return undefined;\n\n const utf8Match = contentDisposition.match(/filename\\*=UTF-8''([^;\\s]+)/i);\n if (utf8Match) {\n try {\n return decodeURIComponent(utf8Match[1]);\n } catch {\n /* ignore malformed encoding */\n }\n }\n\n const asciiMatch = contentDisposition.match(/filename=\"([^\"]*)\"/i);\n return asciiMatch?.[1];\n}\n","import { createPresignedPost } from \"@aws-sdk/s3-presigned-post\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { PutObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildContentDisposition } from \"@/helpers\";\n\ntype Payload = {\n key: string;\n contentType?: string;\n /**\n * Exact byte size of the file the client intends to upload.\n * When provided the presigned POST policy locks `content-length-range` to\n * `[fileSize, fileSize]`, so S3 rejects any upload that is not exactly this\n * size — even if the client tampers with the payload.\n * When omitted the range is `[1, maxFileSize ?? 5 GiB]`.\n */\n fileSize?: number;\n metadata?: Record<string, string>;\n bucket?: string;\n expiresIn?: number;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition: attachment; filename=\"...\"` on the S3 object. */\n fileName?: string;\n};\n\nexport function createUploadHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(body.expiresIn);\n const acl = body.acl === \"public-read\" ? \"public-read\" : \"private\";\n const contentType = body.contentType?.trim() || \"application/octet-stream\";\n const fileSize =\n typeof body.fileSize === \"number\" && body.fileSize > 0\n ? Math.floor(body.fileSize)\n : null;\n\n const guardResult = await runHook(config.upload?.presignGuard, {\n request,\n key,\n bucket,\n contentType: body.contentType,\n fileSize: fileSize ?? undefined,\n metadata: body.metadata,\n acl,\n });\n if (guardResult) return guardResult;\n\n const method = config.upload?.method ?? \"post\";\n\n if (method === \"put\") {\n // Presigned PUT: only Content-Type and Content-Disposition are included\n // as signed headers. x-amz-meta-* headers are intentionally excluded.\n //\n // In browser environments, sending x-amz-meta-* on a cross-origin XHR\n // triggers a CORS preflight. Most S3/R2 CORS configs do not include\n // x-amz-meta-* in AllowedHeaders, so the preflight fails and the upload\n // never reaches S3. Content-Disposition is a standard header that is\n // widely allowed in CORS configs.\n //\n // If you need metadata on the object, use method: \"post\" (presigned POST\n // embeds metadata inside the signed form policy, which S3 applies at\n // the storage layer without CORS header constraints).\n // The metadata value from the request body is still forwarded to the\n // onPresigned hook for server-side use (e.g. storing in a database).\n const putHeaders: Record<string, string> = {\n \"Content-Type\": contentType,\n };\n if (body.fileName) {\n putHeaders[\"Content-Disposition\"] = buildContentDisposition(\n body.fileName,\n );\n }\n\n const url = await getSignedUrl(\n config.s3,\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: contentType,\n ACL: acl,\n ...(body.fileName\n ? { ContentDisposition: buildContentDisposition(body.fileName) }\n : {}),\n }),\n { expiresIn },\n );\n\n await config.upload?.onPresigned?.({\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n acl,\n url,\n expiresIn,\n });\n\n return Response.json({\n bucket,\n key,\n url,\n headers: putHeaders,\n expiresIn,\n method: \"put\",\n });\n }\n\n // Presigned POST: policy fields are embedded in the signed form and must\n // be appended to FormData before the file. S3 enforces them at the storage layer.\n const fields: Record<string, string> = { acl, \"Content-Type\": contentType };\n\n if (body.fileName) {\n fields[\"Content-Disposition\"] = buildContentDisposition(body.fileName);\n }\n\n if (body.metadata) {\n for (const [k, v] of Object.entries(body.metadata)) {\n fields[`x-amz-meta-${k}`] = v;\n }\n }\n\n const rangeMin = fileSize ?? 1;\n const rangeMax = fileSize ?? undefined;\n\n const { url, fields: signedFields } = await createPresignedPost(config.s3, {\n Bucket: bucket,\n Key: key,\n Conditions:\n rangeMax !== undefined\n ? [[\"content-length-range\", rangeMin, rangeMax]]\n : [[\"content-length-range\", rangeMin, Number.MAX_SAFE_INTEGER]],\n Fields: fields,\n Expires: expiresIn,\n });\n\n await config.upload?.onPresigned?.({\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n acl,\n url,\n expiresIn,\n });\n\n return Response.json({\n bucket,\n key,\n url,\n fields: signedFields,\n expiresIn,\n method: \"post\",\n });\n });\n}\n","import { GetObjectAclCommand, type S3Client } from \"@aws-sdk/client-s3\";\n\n/**\n * Determines the ACL of an S3 object by querying `GetObjectAcl`.\n *\n * Returns `\"public-read\"` when the `AllUsers` grantee has `READ` or\n * `FULL_CONTROL` permission; falls back to `\"private\"` when the provider\n * does not support the operation (e.g. Cloudflare R2).\n */\nexport async function resolveObjectAcl(\n s3: S3Client,\n bucket: string,\n key: string,\n): Promise<\"public-read\" | \"private\"> {\n try {\n const result = await s3.send(\n new GetObjectAclCommand({ Bucket: bucket, Key: key }),\n );\n const isPublic = result.Grants?.some(\n (g) =>\n g.Grantee?.URI === \"http://acs.amazonaws.com/groups/global/AllUsers\" &&\n (g.Permission === \"READ\" || g.Permission === \"FULL_CONTROL\"),\n );\n return isPublic ? \"public-read\" : \"private\";\n } catch {\n return \"private\";\n }\n}\n","import { HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildPublicUrl, parseFileName } from \"@/helpers\";\nimport { resolveObjectAcl } from \"@/helpers/server\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n};\n\nexport function createConfirmHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.upload?.confirmGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n\n const acl = await resolveObjectAcl(config.s3, bucket, key);\n const publicUrl = buildPublicUrl(config.resolvePublicUrl, bucket, key, acl);\n const fileName = parseFileName(head.ContentDisposition);\n\n const context = {\n request,\n key,\n bucket,\n contentType: head.ContentType,\n contentLength: head.ContentLength ?? 0,\n eTag: head.ETag?.replace(/\"/g, \"\"),\n metadata: head.Metadata,\n acl,\n fileName,\n publicUrl,\n versionId: head.VersionId,\n lastModified: head.LastModified?.toISOString(),\n };\n\n await config.upload?.onUploadConfirmed?.(context);\n\n return Response.json({\n key,\n bucket,\n contentType: context.contentType,\n contentLength: context.contentLength,\n eTag: context.eTag,\n metadata: context.metadata ?? {},\n acl,\n fileName,\n publicUrl,\n versionId: context.versionId,\n lastModified: context.lastModified,\n });\n });\n}\n","import { GetObjectCommand, HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildContentDisposition } from \"@/helpers\";\n\nexport function createDownloadHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const { searchParams } = new URL(request.url);\n const key = searchParams.get(\"key\")?.trim();\n if (!key) {\n return Response.json(\n { message: \"key query parameter is required\" },\n { status: 400 },\n );\n }\n\n const bucket = searchParams.get(\"bucket\")?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(searchParams.get(\"expiresIn\"));\n const fileName = searchParams.get(\"fileName\")?.trim();\n\n const guardResult = await runHook(config.download?.presignGuard, {\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n });\n if (guardResult) return guardResult;\n\n try {\n await config.s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n } catch (err: unknown) {\n const name = (err as { name?: string })?.name;\n if (name === \"NoSuchKey\" || name === \"NotFound\") {\n return Response.json({ message: \"Object not found\" }, { status: 404 });\n }\n throw err;\n }\n\n const url = await getSignedUrl(\n config.s3,\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n ResponseContentDisposition: fileName\n ? buildContentDisposition(fileName)\n : \"attachment\",\n }),\n { expiresIn },\n );\n\n await config.download?.onPresigned?.({\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n url,\n expiresIn,\n });\n\n return Response.json({ bucket, key, url, expiresIn });\n });\n}\n","import { DeleteObjectCommand, HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../types\";\nimport { runHook, withS3ErrorHandler } from \"../internal-helpers\";\n\nexport function createDeleteHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const { searchParams } = new URL(request.url);\n const key = searchParams.get(\"key\")?.trim();\n if (!key) {\n return Response.json(\n { message: \"key query parameter is required\" },\n { status: 400 },\n );\n }\n\n const bucket = searchParams.get(\"bucket\")?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.delete?.deleteGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n try {\n await config.s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n } catch {\n return Response.json({ message: \"Object not found\" }, { status: 404 });\n }\n\n await config.s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));\n\n await config.delete?.onDeleted?.({ request, key, bucket });\n\n return Response.json({ success: true, bucket, key });\n });\n}\n","import { CreateMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildContentDisposition } from \"@/helpers\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n contentType?: string;\n /** Declared total byte size of the file being uploaded. */\n fileSize?: number;\n metadata?: Record<string, string>;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition: attachment; filename=\"...\"` on the S3 object. */\n fileName?: string;\n};\n\nexport function createMultipartInitHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const acl = body.acl === \"public-read\" ? \"public-read\" : \"private\";\n const fileSize =\n typeof body.fileSize === \"number\" && body.fileSize > 0\n ? Math.floor(body.fileSize)\n : undefined;\n\n const guardResult = await runHook(config.multipart?.initGuard, {\n request,\n key,\n bucket,\n fileSize,\n });\n if (guardResult) return guardResult;\n\n const { UploadId } = await config.s3.send(\n new CreateMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n ContentType: body.contentType,\n ContentDisposition: body.fileName\n ? buildContentDisposition(body.fileName)\n : undefined,\n Metadata: body.metadata,\n ACL: acl,\n }),\n );\n\n await config.multipart?.onInit?.({\n request,\n key,\n bucket,\n uploadId: UploadId!,\n contentType: body.contentType,\n fileSize,\n metadata: body.metadata,\n acl,\n });\n\n return Response.json({ bucket, key, uploadId: UploadId }, { status: 201 });\n });\n}\n","import { UploadPartCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\n\ntype Payload = {\n key: string;\n uploadId: string;\n partNumber: number;\n bucket?: string;\n expiresIn?: number;\n};\n\nexport function createMultipartPartHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const partNumber = Number(body.partNumber);\n if (!Number.isInteger(partNumber) || partNumber <= 0) {\n return Response.json(\n { message: \"partNumber must be a positive integer\" },\n { status: 400 },\n );\n }\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(body.expiresIn);\n\n const guardResult = await runHook(config.multipart?.partGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const presignedUrl = await getSignedUrl(\n config.s3,\n new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n }),\n { expiresIn },\n );\n\n return Response.json({\n presignedUrl,\n partNumber,\n uploadId,\n bucket,\n expiresIn,\n });\n });\n}\n","import {\n CompleteMultipartUploadCommand,\n HeadObjectCommand,\n ListPartsCommand,\n} from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildPublicUrl, parseFileName } from \"@/helpers\";\nimport { resolveObjectAcl } from \"@/helpers/server\";\n\ntype PartEntry = {\n partNumber: number;\n};\n\ntype Payload = {\n key: string;\n uploadId: string;\n bucket?: string;\n parts: PartEntry[];\n};\n\nexport function createMultipartCompleteHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const parts = (Array.isArray(body.parts) ? body.parts : [])\n .map(({ partNumber }) => Number(partNumber))\n .filter((n) => Number.isInteger(n) && n > 0)\n .sort((a, b) => a - b);\n\n if (!parts.length) {\n return Response.json(\n { message: \"At least one valid part is required\" },\n { status: 400 },\n );\n }\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.multipart?.completeGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n // Fetch authoritative ETags from S3 — this works regardless of whether\n // the client's CORS config exposes the ETag header.\n const listed = await config.s3.send(\n new ListPartsCommand({ Bucket: bucket, Key: key, UploadId: uploadId }),\n );\n const listedParts = listed.Parts ?? [];\n\n const completeParts = parts.map((partNumber) => {\n const found = listedParts.find((p) => p.PartNumber === partNumber);\n return { PartNumber: partNumber, ETag: found?.ETag ?? \"\" };\n });\n\n const completeResult = await config.s3.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: { Parts: completeParts },\n }),\n );\n\n // Some S3-compatible providers finalize multipart uploads asynchronously,\n // so HeadObject may transiently return stale or empty metadata right after\n // CompleteMultipartUpload. Retry until contentLength is non-zero.\n let head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n for (let attempt = 0; attempt < 4 && !head.ContentLength; attempt++) {\n await new Promise((r) => setTimeout(r, 250 * 2 ** attempt));\n head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n }\n const contentLength = head.ContentLength ?? 0;\n const contentType = head.ContentType;\n const eTag = (head.ETag ?? completeResult.ETag ?? \"\").replace(/\"/g, \"\");\n const metadata = head.Metadata ?? {};\n const versionId = head.VersionId;\n const lastModified = head.LastModified?.toISOString();\n\n const acl = await resolveObjectAcl(config.s3, bucket, key);\n const publicUrl = buildPublicUrl(config.resolvePublicUrl, bucket, key, acl);\n const fileName = parseFileName(head.ContentDisposition);\n\n await config.multipart?.onComplete?.({\n request,\n key,\n bucket,\n uploadId,\n contentLength,\n contentType,\n eTag,\n metadata,\n acl,\n fileName,\n publicUrl,\n versionId,\n lastModified,\n });\n\n return Response.json({\n bucket,\n key,\n uploadId,\n contentLength,\n contentType,\n eTag,\n metadata,\n acl,\n fileName,\n publicUrl,\n versionId,\n lastModified,\n });\n });\n}\n","import { AbortMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\n\ntype Payload = {\n key: string;\n uploadId: string;\n bucket?: string;\n};\n\nexport function createMultipartAbortHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.multipart?.abortGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n await config.s3.send(\n new AbortMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n }),\n );\n\n await config.multipart?.onAbort?.({\n request,\n key,\n bucket,\n uploadId,\n });\n\n return Response.json({ bucket, key, uploadId, aborted: true });\n });\n}\n","import type { S3HandlerConfig, S3Handlers } from \"./types\";\nimport { createUploadHandler } from \"./handlers/presign/upload\";\nimport { createConfirmHandler } from \"./handlers/presign/confirm\";\nimport { createDownloadHandler } from \"./handlers/presign/download\";\nimport { createDeleteHandler } from \"./handlers/delete\";\nimport { createMultipartInitHandler } from \"./handlers/multipart/init\";\nimport { createMultipartPartHandler } from \"./handlers/multipart/part\";\nimport { createMultipartCompleteHandler } from \"./handlers/multipart/complete\";\nimport { createMultipartAbortHandler } from \"./handlers/multipart/abort\";\n\nexport function createHandlers(config: S3HandlerConfig): S3Handlers {\n return {\n presign: {\n upload: createUploadHandler(config),\n confirm: createConfirmHandler(config),\n download: createDownloadHandler(config),\n },\n multipart: {\n init: createMultipartInitHandler(config),\n part: createMultipartPartHandler(config),\n complete: createMultipartCompleteHandler(config),\n abort: createMultipartAbortHandler(config),\n },\n delete: createDeleteHandler(config),\n };\n}\n","import type { S3RouteHandlerConfig } from \"./types\";\nimport { createHandlers } from \"./create-handlers\";\nimport { runHook } from \"./internal-helpers\";\n\nconst disabled = () => Response.json({ message: \"Not Found\" }, { status: 404 });\n\nexport function createRouter(config: S3RouteHandlerConfig) {\n const handlers = createHandlers(config);\n const base = config.basePath.replace(/\\/$/, \"\");\n\n return async (request: Request): Promise<Response> => {\n // Global guard — runs before every request\n const guardResult = await runHook(config.guard, { request });\n if (guardResult) return guardResult;\n\n const url = new URL(request.url);\n const subpath = url.pathname.slice(base.length).replace(/^\\//, \"\");\n const method = request.method;\n\n if (method === \"POST\" && subpath === \"presign/upload\")\n return config.upload?.enabled\n ? handlers.presign.upload(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/upload/confirm\")\n return config.upload?.enabled\n ? handlers.presign.confirm(request)\n : disabled();\n if (method === \"GET\" && subpath === \"presign/download\")\n return config.download?.enabled\n ? handlers.presign.download(request)\n : disabled();\n if (method === \"DELETE\" && subpath === \"delete\")\n return config.delete?.enabled ? handlers.delete(request) : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/init\")\n return config.multipart?.enabled\n ? handlers.multipart.init(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/part\")\n return config.multipart?.enabled\n ? handlers.multipart.part(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/complete\")\n return config.multipart?.enabled\n ? handlers.multipart.complete(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/abort\")\n return config.multipart?.enabled\n ? handlers.multipart.abort(request)\n : disabled();\n\n return Response.json({ message: \"Not Found\" }, { status: 404 });\n };\n}\n","import { createRouter } from \"../router\";\nimport type { S3RouteHandlerConfig } from \"../types\";\n\n/**\n * Creates a Next.js App Router catch-all route handler.\n *\n * Usage in `app/api/s3/[...s3]/route.ts`:\n * ```ts\n * import { createRouteHandler } from \"@better-s3/server/next\";\n *\n * const handler = createRouteHandler({ s3, defaultBucket: \"my-bucket\", basePath: \"/api/s3\" });\n * export { handler as GET, handler as POST, handler as DELETE };\n * ```\n */\nexport function createRouteHandler(config: S3RouteHandlerConfig) {\n return createRouter(config);\n}\n"]}
package/dist/api.d.ts CHANGED
@@ -4,13 +4,19 @@ export type PresignResponse = {
4
4
  url: string;
5
5
  expiresIn: number;
6
6
  };
7
- export type PresignedPostResponse = {
7
+ export type UploadPresignResponse = {
8
8
  key: string;
9
9
  bucket: string;
10
10
  url: string;
11
- fields: Record<string, string>;
12
11
  expiresIn: number;
12
+ method: "post" | "put";
13
+ /** Present when `method` is `"post"`. Must be appended to FormData before the file. */
14
+ fields?: Record<string, string>;
15
+ /** Present when `method` is `"put"`. Must be set as request headers on the PUT request. */
16
+ headers?: Record<string, string>;
13
17
  };
18
+ /** @deprecated Use {@link UploadPresignResponse} instead. */
19
+ export type PresignedPostResponse = UploadPresignResponse;
14
20
  export type MultipartInitResponse = {
15
21
  key: string;
16
22
  bucket: string;
@@ -29,11 +35,17 @@ export type UploadConfirmResponse = {
29
35
  contentType?: string;
30
36
  contentLength: number;
31
37
  eTag?: string;
38
+ /** S3 object metadata (x-amz-meta-*). Always present; empty object when no metadata was stored. */
39
+ metadata: Record<string, string>;
32
40
  acl: "private" | "public-read";
33
41
  /** Display file name parsed from the object's Content-Disposition header. */
34
42
  fileName?: string;
35
43
  /** Public URL of the object. Only present when `acl` is `public-read` and `publicUrlBase` is configured on the server. */
36
44
  publicUrl?: string;
45
+ /** Object version ID. Only present when bucket versioning is enabled. */
46
+ versionId?: string;
47
+ /** Last-modified timestamp from HeadObject (ISO 8601 string). */
48
+ lastModified?: string;
37
49
  };
38
50
  export type S3Api = {
39
51
  upload: (payload: {
@@ -50,7 +62,7 @@ export type S3Api = {
50
62
  acl?: "private" | "public-read";
51
63
  /** Original file name. Stored as `Content-Disposition` on the S3 object. */
52
64
  fileName?: string;
53
- }) => Promise<PresignedPostResponse>;
65
+ }) => Promise<UploadPresignResponse>;
54
66
  confirm: (payload: {
55
67
  key: string;
56
68
  bucket?: string;
@@ -89,7 +101,6 @@ export type S3Api = {
89
101
  uploadId: string;
90
102
  parts: Array<{
91
103
  partNumber: number;
92
- eTag: string;
93
104
  }>;
94
105
  bucket?: string;
95
106
  }) => Promise<{
@@ -99,6 +110,12 @@ export type S3Api = {
99
110
  contentLength: number;
100
111
  contentType?: string;
101
112
  eTag?: string;
113
+ /** S3 object metadata (x-amz-meta-*). Always present; empty object when no metadata was stored. */
114
+ metadata: Record<string, string>;
115
+ /** Object version ID. Only present when bucket versioning is enabled. */
116
+ versionId?: string;
117
+ /** Last-modified timestamp from HeadObject (ISO 8601 string). */
118
+ lastModified?: string;
102
119
  }>;
103
120
  abort: (payload: {
104
121
  key: string;
@@ -2,10 +2,12 @@
2
2
  * Builds the public URL for an S3 object.
3
3
  *
4
4
  * Returns `undefined` when:
5
- * - the object is `"private"`, or
6
- * - `publicUrlBase` is not provided.
5
+ * - the object is `"private"`,
6
+ * - `publicUrlBase` is not provided, or
7
+ * - `publicUrlBase` returns `undefined` for the given bucket.
7
8
  *
8
- * @param publicUrlBase - A function that returns the base URL for a given bucket.
9
- * Matches the `publicUrlBase` field of `S3HandlerConfig`.
9
+ * @param publicUrlBase - A function that returns the base URL for a given bucket,
10
+ * or `undefined` when no public URL is available for that bucket.
11
+ * Matches the `resolvePublicUrl` field of `S3HandlerConfig`.
10
12
  */
11
- export declare function buildPublicUrl(publicUrlBase: ((bucket: string) => string) | undefined, bucket: string, key: string, acl: "public-read" | "private"): string | undefined;
13
+ export declare function buildPublicUrl(publicUrlBase: ((bucket: string) => string | undefined) | undefined, bucket: string, key: string, acl: "public-read" | "private"): string | undefined;
@@ -1,2 +1,2 @@
1
- function r(e){let n=e.replace(/[^\x20-\x7E]/g,"_").replace(/["\\]/g,"_"),t=encodeURIComponent(e);return `attachment; filename="${n}"; filename*=UTF-8''${t}`}function c(e,n,t,i){return i!=="public-read"||!e?void 0:`${e(n).replace(/\/$/,"")}/${t}`}function a(e){if(!e)return;let n=e.match(/filename\*=UTF-8''([^;\s]+)/i);if(n)try{return decodeURIComponent(n[1])}catch{}return e.match(/filename="([^"]*)"/i)?.[1]}export{r as buildContentDisposition,c as buildPublicUrl,a as parseFileName};//# sourceMappingURL=index.js.map
1
+ function c(e){let n=e.replace(/[^\x20-\x7E]/g,"_").replace(/["\\]/g,"_"),i=encodeURIComponent(e);return `attachment; filename="${n}"; filename*=UTF-8''${i}`}function d(e,n,i,r){if(r!=="public-read"||!e)return;let t=e(n);if(t)return `${t.replace(/\/$/,"")}/${i}`}function u(e){if(!e)return;let n=e.match(/filename\*=UTF-8''([^;\s]+)/i);if(n)try{return decodeURIComponent(n[1])}catch{}return e.match(/filename="([^"]*)"/i)?.[1]}export{c as buildContentDisposition,d as buildPublicUrl,u as parseFileName};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/helpers/build-content-disposition.ts","../../src/helpers/build-public-url.ts","../../src/helpers/parse-file-name.ts"],"names":["buildContentDisposition","fileName","ascii","encoded","buildPublicUrl","publicUrlBase","bucket","key","acl","parseFileName","contentDisposition","utf8Match"],"mappings":"AAYO,SAASA,EAAwBC,CAAAA,CAA0B,CAChE,IAAMC,CAAAA,CAAQD,EAAS,OAAA,CAAQ,eAAA,CAAiB,GAAG,CAAA,CAAE,QAAQ,QAAA,CAAU,GAAG,EACpEE,CAAAA,CAAU,kBAAA,CAAmBF,CAAQ,CAAA,CAC3C,OAAO,CAAA,sBAAA,EAAyBC,CAAK,uBAAuBC,CAAO,CAAA,CACrE,CCNO,SAASC,CAAAA,CACdC,EACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACoB,CACpB,OAAIA,CAAAA,GAAQ,aAAA,EAAiB,CAACH,CAAAA,CAAe,MAAA,CAEtC,GADMA,CAAAA,CAAcC,CAAM,CAAA,CAAE,OAAA,CAAQ,MAAO,EAAE,CACtC,IAAIC,CAAG,CAAA,CACvB,CCXO,SAASE,CAAAA,CACdC,CAAAA,CACoB,CACpB,GAAI,CAACA,CAAAA,CAAoB,OAEzB,IAAMC,CAAAA,CAAYD,EAAmB,KAAA,CAAM,8BAA8B,CAAA,CACzE,GAAIC,EACF,GAAI,CACF,OAAO,kBAAA,CAAmBA,CAAAA,CAAU,CAAC,CAAC,CACxC,CAAA,KAAQ,CAER,CAIF,OADmBD,CAAAA,CAAmB,MAAM,qBAAqB,CAAA,GAC7C,CAAC,CACvB","file":"index.js","sourcesContent":["/**\n * Builds a RFC 6266 `Content-Disposition` value for a file attachment.\n *\n * Includes both the ASCII-safe `filename` fallback and the RFC 5987\n * `filename*` parameter for full Unicode support, so the original file\n * name (including non-ASCII characters) is preserved across all browsers\n * and HTTP clients.\n *\n * @example\n * buildContentDisposition(\"résumé finale.pdf\")\n * // → 'attachment; filename=\"resume finale.pdf\"; filename*=UTF-8\\'\\'r%C3%A9sum%C3%A9%20finale.pdf'\n */\nexport function buildContentDisposition(fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7E]/g, \"_\").replace(/[\"\\\\]/g, \"_\");\n const encoded = encodeURIComponent(fileName);\n return `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n}\n","/**\n * Builds the public URL for an S3 object.\n *\n * Returns `undefined` when:\n * - the object is `\"private\"`, or\n * - `publicUrlBase` is not provided.\n *\n * @param publicUrlBase - A function that returns the base URL for a given bucket.\n * Matches the `publicUrlBase` field of `S3HandlerConfig`.\n */\nexport function buildPublicUrl(\n publicUrlBase: ((bucket: string) => string) | undefined,\n bucket: string,\n key: string,\n acl: \"public-read\" | \"private\",\n): string | undefined {\n if (acl !== \"public-read\" || !publicUrlBase) return undefined;\n const base = publicUrlBase(bucket).replace(/\\/$/, \"\");\n return `${base}/${key}`;\n}\n","/**\n * Parses the display file name from a `Content-Disposition` header value.\n *\n * Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over\n * the plain ASCII `filename=\"…\"` fallback.\n *\n * Returns `undefined` when no filename can be extracted.\n */\nexport function parseFileName(\n contentDisposition: string | undefined,\n): string | undefined {\n if (!contentDisposition) return undefined;\n\n const utf8Match = contentDisposition.match(/filename\\*=UTF-8''([^;\\s]+)/i);\n if (utf8Match) {\n try {\n return decodeURIComponent(utf8Match[1]);\n } catch {\n /* ignore malformed encoding */\n }\n }\n\n const asciiMatch = contentDisposition.match(/filename=\"([^\"]*)\"/i);\n return asciiMatch?.[1];\n}\n"]}
1
+ {"version":3,"sources":["../../src/helpers/build-content-disposition.ts","../../src/helpers/build-public-url.ts","../../src/helpers/parse-file-name.ts"],"names":["buildContentDisposition","fileName","ascii","encoded","buildPublicUrl","publicUrlBase","bucket","key","acl","base","parseFileName","contentDisposition","utf8Match"],"mappings":"AAYO,SAASA,EAAwBC,CAAAA,CAA0B,CAChE,IAAMC,CAAAA,CAAQD,CAAAA,CAAS,QAAQ,eAAA,CAAiB,GAAG,EAAE,OAAA,CAAQ,QAAA,CAAU,GAAG,CAAA,CACpEE,CAAAA,CAAU,mBAAmBF,CAAQ,CAAA,CAC3C,OAAO,CAAA,sBAAA,EAAyBC,CAAK,CAAA,oBAAA,EAAuBC,CAAO,EACrE,CCJO,SAASC,EACdC,CAAAA,CACAC,CAAAA,CACAC,EACAC,CAAAA,CACoB,CACpB,GAAIA,CAAAA,GAAQ,aAAA,EAAiB,CAACH,CAAAA,CAAe,OAC7C,IAAMI,CAAAA,CAAOJ,CAAAA,CAAcC,CAAM,CAAA,CACjC,GAAKG,CAAAA,CACL,OAAO,GAAGA,CAAAA,CAAK,OAAA,CAAQ,MAAO,EAAE,CAAC,IAAIF,CAAG,CAAA,CAC1C,CCdO,SAASG,CAAAA,CACdC,EACoB,CACpB,GAAI,CAACA,CAAAA,CAAoB,OAEzB,IAAMC,CAAAA,CAAYD,CAAAA,CAAmB,MAAM,8BAA8B,CAAA,CACzE,GAAIC,CAAAA,CACF,GAAI,CACF,OAAO,kBAAA,CAAmBA,EAAU,CAAC,CAAC,CACxC,CAAA,KAAQ,CAER,CAIF,OADmBD,CAAAA,CAAmB,MAAM,qBAAqB,CAAA,GAC7C,CAAC,CACvB","file":"index.js","sourcesContent":["/**\n * Builds a RFC 6266 `Content-Disposition` value for a file attachment.\n *\n * Includes both the ASCII-safe `filename` fallback and the RFC 5987\n * `filename*` parameter for full Unicode support, so the original file\n * name (including non-ASCII characters) is preserved across all browsers\n * and HTTP clients.\n *\n * @example\n * buildContentDisposition(\"résumé finale.pdf\")\n * // → 'attachment; filename=\"resume finale.pdf\"; filename*=UTF-8\\'\\'r%C3%A9sum%C3%A9%20finale.pdf'\n */\nexport function buildContentDisposition(fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7E]/g, \"_\").replace(/[\"\\\\]/g, \"_\");\n const encoded = encodeURIComponent(fileName);\n return `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n}\n","/**\n * Builds the public URL for an S3 object.\n *\n * Returns `undefined` when:\n * - the object is `\"private\"`,\n * - `publicUrlBase` is not provided, or\n * - `publicUrlBase` returns `undefined` for the given bucket.\n *\n * @param publicUrlBase - A function that returns the base URL for a given bucket,\n * or `undefined` when no public URL is available for that bucket.\n * Matches the `resolvePublicUrl` field of `S3HandlerConfig`.\n */\nexport function buildPublicUrl(\n publicUrlBase: ((bucket: string) => string | undefined) | undefined,\n bucket: string,\n key: string,\n acl: \"public-read\" | \"private\",\n): string | undefined {\n if (acl !== \"public-read\" || !publicUrlBase) return undefined;\n const base = publicUrlBase(bucket);\n if (!base) return undefined;\n return `${base.replace(/\\/$/, \"\")}/${key}`;\n}\n","/**\n * Parses the display file name from a `Content-Disposition` header value.\n *\n * Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over\n * the plain ASCII `filename=\"…\"` fallback.\n *\n * Returns `undefined` when no filename can be extracted.\n */\nexport function parseFileName(\n contentDisposition: string | undefined,\n): string | undefined {\n if (!contentDisposition) return undefined;\n\n const utf8Match = contentDisposition.match(/filename\\*=UTF-8''([^;\\s]+)/i);\n if (utf8Match) {\n try {\n return decodeURIComponent(utf8Match[1]);\n } catch {\n /* ignore malformed encoding */\n }\n }\n\n const asciiMatch = contentDisposition.match(/filename=\"([^\"]*)\"/i);\n return asciiMatch?.[1];\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -8,7 +8,9 @@ export { createMultipartInitHandler } from "./handlers/multipart/init";
8
8
  export { createMultipartPartHandler } from "./handlers/multipart/part";
9
9
  export { createMultipartCompleteHandler } from "./handlers/multipart/complete";
10
10
  export { createMultipartAbortHandler } from "./handlers/multipart/abort";
11
- export { createS3Api, type S3Api, type PresignResponse, type PresignedPostResponse, type MultipartInitResponse, type MultipartPartResponse, type UploadConfirmResponse, } from "./api";
11
+ export { createS3Api, type S3Api, type PresignResponse, type UploadPresignResponse,
12
+ /** @deprecated Use {@link UploadPresignResponse} instead. */
13
+ type PresignedPostResponse, type MultipartInitResponse, type MultipartPartResponse, type UploadConfirmResponse, } from "./api";
12
14
  export { createS3Api as createPresignApi, type S3Api as PresignApi, } from "./api";
13
15
  export { validateFile } from "./helpers/validate-file";
14
16
  export type { S3HandlerConfig, S3RouteHandlerConfig, S3Handler, S3Handlers, HookContext, UploadHookContext, UploadSuccessContext, UploadCompleteContext, DownloadHookContext, DownloadSuccessContext, DeleteHookContext, MultipartHookContext, MultipartInitSuccessContext, MultipartCompleteSuccessContext, } from "./types";
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import {createPresignedPost}from'@aws-sdk/s3-presigned-post';import {HeadObjectCommand,GetObjectCommand,DeleteObjectCommand,CreateMultipartUploadCommand,UploadPartCommand,CompleteMultipartUploadCommand,AbortMultipartUploadCommand,GetObjectAclCommand}from'@aws-sdk/client-s3';import {getSignedUrl}from'@aws-sdk/s3-request-presigner';async function g(e){try{let n=await e.json();return n&&typeof n=="object"?n:null}catch{return null}}function m(e,n){let r=typeof e=="string"?e.trim():"";return r||Response.json({message:`${n} is required`},{status:400})}function k(e){let n=Number(e);return Number.isFinite(n)&&n>0?Math.floor(n):600}function l(e){return async n=>{try{return await e(n)}catch(r){let t=r instanceof Error?r.message:"Internal server error";return console.error("[S3 API]",t),Response.json({message:t},{status:500})}}}async function u(e,n){if(!e)return null;try{return await e(n),null}catch(r){let t=r instanceof Error?r.message:"Forbidden",a=typeof r?.status=="number"?r.status:403;return Response.json({message:t},{status:a})}}function b(e){let n=e.replace(/[^\x20-\x7E]/g,"_").replace(/["\\]/g,"_"),r=encodeURIComponent(e);return `attachment; filename="${n}"; filename*=UTF-8''${r}`}function w(e,n,r,t){return t!=="public-read"||!e?void 0:`${e(n).replace(/\/$/,"")}/${r}`}function P(e){if(!e)return;let n=e.match(/filename\*=UTF-8''([^;\s]+)/i);if(n)try{return decodeURIComponent(n[1])}catch{}return e.match(/filename="([^"]*)"/i)?.[1]}var B=5*1024*1024*1024;function I(e){return l(async n=>{let r=await g(n);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=m(r.key,"key");if(t instanceof Response)return t;let a=r.bucket?.trim()||e.defaultBucket,s=k(r.expiresIn),o=r.acl==="public-read"?"public-read":"private",i=r.contentType?.trim()||"application/octet-stream",p=typeof r.fileSize=="number"&&r.fileSize>0?Math.floor(r.fileSize):null;if(p!==null&&e.maxFileSize&&p>e.maxFileSize)return Response.json({message:`File size (${p} bytes) exceeds the maximum allowed size of ${e.maxFileSize} bytes`},{status:413});let d=await u(e.upload?.guard,{request:n,key:t,bucket:a,contentType:r.contentType,fileSize:p??void 0,metadata:r.metadata,acl:o});if(d)return d;let c={acl:o,"Content-Type":i};if(r.fileName&&(c["Content-Disposition"]=b(r.fileName)),r.metadata)for(let[y,S]of Object.entries(r.metadata))c[`x-amz-meta-${y}`]=S;let H=p??1,R=p??e.maxFileSize??B,{url:x,fields:h}=await createPresignedPost(e.s3,{Bucket:a,Key:t,Conditions:[["content-length-range",H,R]],Fields:c,Expires:s});return await e.upload?.onPresigned?.({request:n,key:t,bucket:a,contentType:r.contentType,metadata:r.metadata,acl:o,url:x,expiresIn:s}),Response.json({bucket:a,key:t,url:x,fields:h,expiresIn:s})})}async function C(e,n,r){try{return (await e.send(new GetObjectAclCommand({Bucket:n,Key:r}))).Grants?.some(s=>s.Grantee?.URI==="http://acs.amazonaws.com/groups/global/AllUsers"&&(s.Permission==="READ"||s.Permission==="FULL_CONTROL"))?"public-read":"private"}catch{return "private"}}function T(e){return l(async n=>{let r=await g(n);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=m(r.key,"key");if(t instanceof Response)return t;let a=r.bucket?.trim()||e.defaultBucket,s=await u(e.upload?.guard,{request:n,key:t,bucket:a});if(s)return s;let o=await e.s3.send(new HeadObjectCommand({Bucket:a,Key:t})),i=await C(e.s3,a,t),p=w(e.resolvePublicUrl,a,t,i),d=P(o.ContentDisposition),c={request:n,key:t,bucket:a,contentType:o.ContentType,contentLength:o.ContentLength??0,eTag:o.ETag?.replace(/"/g,""),metadata:o.Metadata,acl:i,fileName:d,publicUrl:p};return await e.upload?.onUploaded?.(c),Response.json({key:t,bucket:a,contentType:c.contentType,contentLength:c.contentLength,eTag:c.eTag,acl:i,fileName:d,publicUrl:p})})}function N(e){return l(async n=>{let{searchParams:r}=new URL(n.url),t=r.get("key")?.trim();if(!t)return Response.json({message:"key query parameter is required"},{status:400});let a=r.get("bucket")?.trim()||e.defaultBucket,s=k(r.get("expiresIn")),o=r.get("fileName")?.trim(),i=await u(e.download?.guard,{request:n,key:t,bucket:a,fileName:o||void 0});if(i)return i;try{await e.s3.send(new HeadObjectCommand({Bucket:a,Key:t}));}catch(d){let c=d?.name;if(c==="NoSuchKey"||c==="NotFound")return Response.json({message:"Object not found"},{status:404});throw d}let p=await getSignedUrl(e.s3,new GetObjectCommand({Bucket:a,Key:t,ResponseContentDisposition:o?b(o):"attachment"}),{expiresIn:s});return await e.download?.onPresigned?.({request:n,key:t,bucket:a,fileName:o||void 0,url:p,expiresIn:s}),Response.json({bucket:a,key:t,url:p,expiresIn:s})})}function j(e){return l(async n=>{let{searchParams:r}=new URL(n.url),t=r.get("key")?.trim();if(!t)return Response.json({message:"key query parameter is required"},{status:400});let a=r.get("bucket")?.trim()||e.defaultBucket,s=await u(e.delete?.guard,{request:n,key:t,bucket:a});if(s)return s;try{await e.s3.send(new HeadObjectCommand({Bucket:a,Key:t}));}catch{return Response.json({message:"Object not found"},{status:404})}return await e.s3.send(new DeleteObjectCommand({Bucket:a,Key:t})),await e.delete?.onDeleted?.({request:n,key:t,bucket:a}),Response.json({success:!0,bucket:a,key:t})})}function U(e){return l(async n=>{let r=await g(n);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=m(r.key,"key");if(t instanceof Response)return t;let a=r.bucket?.trim()||e.defaultBucket,s=r.acl==="public-read"?"public-read":"private",o=typeof r.fileSize=="number"&&r.fileSize>0?Math.floor(r.fileSize):void 0;if(o!==void 0&&e.maxFileSize&&o>e.maxFileSize)return Response.json({message:`File size (${o} bytes) exceeds the maximum allowed size of ${e.maxFileSize} bytes`},{status:413});let i=await u(e.multipart?.guard,{request:n,key:t,bucket:a,fileSize:o});if(i)return i;let{UploadId:p}=await e.s3.send(new CreateMultipartUploadCommand({Bucket:a,Key:t,ContentType:r.contentType,ContentDisposition:r.fileName?b(r.fileName):void 0,Metadata:r.metadata,ACL:s}));return await e.multipart?.onInit?.({request:n,key:t,bucket:a,uploadId:p,contentType:r.contentType,fileSize:o,metadata:r.metadata,acl:s}),Response.json({bucket:a,key:t,uploadId:p},{status:201})})}function z(e){return l(async n=>{let r=await g(n);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=m(r.key,"key");if(t instanceof Response)return t;let a=m(r.uploadId,"uploadId");if(a instanceof Response)return a;let s=Number(r.partNumber);if(!Number.isInteger(s)||s<=0)return Response.json({message:"partNumber must be a positive integer"},{status:400});let o=r.bucket?.trim()||e.defaultBucket,i=k(r.expiresIn),p=await u(e.multipart?.guard,{request:n,key:t,bucket:o});if(p)return p;let d=await getSignedUrl(e.s3,new UploadPartCommand({Bucket:o,Key:t,UploadId:a,PartNumber:s}),{expiresIn:i});return Response.json({presignedUrl:d,partNumber:s,uploadId:a,bucket:o,expiresIn:i})})}function M(e){return l(async n=>{let r=await g(n);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=m(r.key,"key");if(t instanceof Response)return t;let a=m(r.uploadId,"uploadId");if(a instanceof Response)return a;let s=(Array.isArray(r.parts)?r.parts:[]).map(({partNumber:y,eTag:S})=>({PartNumber:Number(y),ETag:String(S)})).filter(y=>Number.isInteger(y.PartNumber)&&y.ETag).sort((y,S)=>y.PartNumber-S.PartNumber);if(!s.length)return Response.json({message:"At least one valid part is required"},{status:400});let o=r.bucket?.trim()||e.defaultBucket,i=await u(e.multipart?.guard,{request:n,key:t,bucket:o});if(i)return i;await e.s3.send(new CompleteMultipartUploadCommand({Bucket:o,Key:t,UploadId:a,MultipartUpload:{Parts:s}}));let p=await e.s3.send(new HeadObjectCommand({Bucket:o,Key:t})),d=p.ContentLength??0,c=p.ContentType,H=p.ETag?.replace(/"/g,""),R=await C(e.s3,o,t),x=w(e.resolvePublicUrl,o,t,R),h=P(p.ContentDisposition);return e.maxFileSize&&d>e.maxFileSize?(await e.s3.send(new DeleteObjectCommand({Bucket:o,Key:t})).catch(()=>{}),Response.json({message:`File size (${d} bytes) exceeds the maximum allowed size of ${e.maxFileSize} bytes`},{status:422})):(await e.multipart?.onComplete?.({request:n,key:t,bucket:o,uploadId:a,contentLength:d,contentType:c,eTag:H,acl:R,fileName:h,publicUrl:x}),Response.json({bucket:o,key:t,uploadId:a,contentLength:d,contentType:c,eTag:H,acl:R,fileName:h,publicUrl:x}))})}function F(e){return l(async n=>{let r=await g(n);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=m(r.key,"key");if(t instanceof Response)return t;let a=m(r.uploadId,"uploadId");if(a instanceof Response)return a;let s=r.bucket?.trim()||e.defaultBucket,o=await u(e.multipart?.guard,{request:n,key:t,bucket:s});return o||(await e.s3.send(new AbortMultipartUploadCommand({Bucket:s,Key:t,UploadId:a})),await e.multipart?.onAbort?.({request:n,key:t,bucket:s,uploadId:a}),Response.json({bucket:s,key:t,uploadId:a,aborted:!0}))})}function E(e){return {presign:{upload:I(e),confirm:T(e),download:N(e)},multipart:{init:U(e),part:z(e),complete:M(e),abort:F(e)},delete:j(e)}}var f=()=>Response.json({message:"Not Found"},{status:404});function Y(e){let n=E(e),r=e.basePath.replace(/\/$/,"");return async t=>{let a=await u(e.guard,{request:t});if(a)return a;let o=new URL(t.url).pathname.slice(r.length).replace(/^\//,""),i=t.method;return i==="POST"&&o==="presign/upload"?e.upload?.enabled?n.presign.upload(t):f():i==="POST"&&o==="presign/upload/confirm"?e.upload?.enabled?n.presign.confirm(t):f():i==="GET"&&o==="presign/download"?e.download?.enabled?n.presign.download(t):f():i==="DELETE"&&o==="delete"?e.delete?.enabled?n.delete(t):f():i==="POST"&&o==="presign/multipart/init"?e.multipart?.enabled?n.multipart.init(t):f():i==="POST"&&o==="presign/multipart/part"?e.multipart?.enabled?n.multipart.part(t):f():i==="POST"&&o==="presign/multipart/complete"?e.multipart?.enabled?n.multipart.complete(t):f():i==="POST"&&o==="presign/multipart/abort"?e.multipart?.enabled?n.multipart.abort(t):f():Response.json({message:"Not Found"},{status:404})}}function O(e="/api/s3"){let n=e.replace(/\/$/,""),r=async(a,s)=>{let o=await fetch(a,s);if(!o.ok){let i=await o.json().catch(()=>({}));throw new Error(i.message??o.statusText)}return o.json()},t=(a,s)=>r(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});return {upload(a){return t(`${n}/presign/upload`,a)},confirm(a){return t(`${n}/presign/upload/confirm`,a)},download(a,s){let o=new URLSearchParams({key:a});if(s?.fileName){let i=s.fileName.replace(/["\\\r\n]/g,"_");o.set("fileName",i);}return s?.bucket&&o.set("bucket",s.bucket),r(`${n}/presign/download?${o}`)},delete(a,s){let o=new URLSearchParams({key:a});return s?.bucket&&o.set("bucket",s.bucket),r(`${n}/delete?${o}`,{method:"DELETE"})},multipart:{init(a){return t(`${n}/presign/multipart/init`,a)},signPart(a){return t(`${n}/presign/multipart/part`,a)},complete(a){return t(`${n}/presign/multipart/complete`,a)},abort(a){return t(`${n}/presign/multipart/abort`,a)}}}}function ee(e,n){if(n.accept?.length&&!n.accept.some(t=>t.startsWith(".")?e.name.toLowerCase().endsWith(t.toLowerCase()):t.endsWith("/*")?e.type.startsWith(t.replace("/*","/")):e.type===t)){let t=e.name.includes(".")?e.name.split(".").pop():null;return `File type "${t?`.${t}`:e.type||"unknown"}" is not allowed`}return e.size===0?"File is empty":n.maxFileSize&&e.size>n.maxFileSize?`File size exceeds ${(n.maxFileSize/1048576).toFixed(1)} MB limit`:null}
2
- export{T as createConfirmHandler,j as createDeleteHandler,N as createDownloadHandler,E as createHandlers,F as createMultipartAbortHandler,M as createMultipartCompleteHandler,U as createMultipartInitHandler,z as createMultipartPartHandler,O as createPresignApi,Y as createRouter,O as createS3Api,I as createUploadHandler,ee as validateFile};//# sourceMappingURL=index.js.map
1
+ import {createPresignedPost}from'@aws-sdk/s3-presigned-post';import {getSignedUrl}from'@aws-sdk/s3-request-presigner';import {PutObjectCommand,HeadObjectCommand,GetObjectCommand,DeleteObjectCommand,CreateMultipartUploadCommand,UploadPartCommand,ListPartsCommand,CompleteMultipartUploadCommand,AbortMultipartUploadCommand,GetObjectAclCommand}from'@aws-sdk/client-s3';async function y(e){try{let o=await e.json();return o&&typeof o=="object"?o:null}catch{return null}}function d(e,o){let r=typeof e=="string"?e.trim():"";return r||Response.json({message:`${o} is required`},{status:400})}function S(e){let o=Number(e);return Number.isFinite(o)&&o>0?Math.floor(o):600}function u(e){return async o=>{try{return await e(o)}catch(r){let t=r.code,s=t==="EAI_AGAIN"||t==="ECONNREFUSED"||t==="ECONNRESET"||t==="ETIMEDOUT"||t==="ENOTFOUND"?`S3 endpoint unreachable (${t}): check your endpoint URL and network connectivity`:r instanceof Error?r.message:"Internal server error";return console.error("[S3 API]",s),Response.json({message:s},{status:502})}}}async function p(e,o){if(!e)return null;try{return await e(o),null}catch(r){let t=r instanceof Error?r.message:"Forbidden",n=typeof r?.status=="number"?r.status:403;return Response.json({message:t},{status:n})}}function b(e){let o=e.replace(/[^\x20-\x7E]/g,"_").replace(/["\\]/g,"_"),r=encodeURIComponent(e);return `attachment; filename="${o}"; filename*=UTF-8''${r}`}function I(e,o,r,t){if(t!=="public-read"||!e)return;let n=e(o);if(n)return `${n.replace(/\/$/,"")}/${r}`}function h(e){if(!e)return;let o=e.match(/filename\*=UTF-8''([^;\s]+)/i);if(o)try{return decodeURIComponent(o[1])}catch{}return e.match(/filename="([^"]*)"/i)?.[1]}function M(e){return u(async o=>{let r=await y(o);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=d(r.key,"key");if(t instanceof Response)return t;let n=r.bucket?.trim()||e.defaultBucket,s=S(r.expiresIn),a=r.acl==="public-read"?"public-read":"private",i=r.contentType?.trim()||"application/octet-stream",l=typeof r.fileSize=="number"&&r.fileSize>0?Math.floor(r.fileSize):null,g=await p(e.upload?.presignGuard,{request:o,key:t,bucket:n,contentType:r.contentType,fileSize:l??void 0,metadata:r.metadata,acl:a});if(g)return g;if((e.upload?.method??"post")==="put"){let R={"Content-Type":i};r.fileName&&(R["Content-Disposition"]=b(r.fileName));let w=await getSignedUrl(e.s3,new PutObjectCommand({Bucket:n,Key:t,ContentType:i,ACL:a,...r.fileName?{ContentDisposition:b(r.fileName)}:{}}),{expiresIn:s});return await e.upload?.onPresigned?.({request:o,key:t,bucket:n,contentType:r.contentType,metadata:r.metadata,acl:a,url:w,expiresIn:s}),Response.json({bucket:n,key:t,url:w,headers:R,expiresIn:s,method:"put"})}let C={acl:a,"Content-Type":i};if(r.fileName&&(C["Content-Disposition"]=b(r.fileName)),r.metadata)for(let[R,w]of Object.entries(r.metadata))C[`x-amz-meta-${R}`]=w;let f=l??1,P=l??void 0,{url:x,fields:N}=await createPresignedPost(e.s3,{Bucket:n,Key:t,Conditions:P!==void 0?[["content-length-range",f,P]]:[["content-length-range",f,Number.MAX_SAFE_INTEGER]],Fields:C,Expires:s});return await e.upload?.onPresigned?.({request:o,key:t,bucket:n,contentType:r.contentType,metadata:r.metadata,acl:a,url:x,expiresIn:s}),Response.json({bucket:n,key:t,url:x,fields:N,expiresIn:s,method:"post"})})}async function T(e,o,r){try{return (await e.send(new GetObjectAclCommand({Bucket:o,Key:r}))).Grants?.some(s=>s.Grantee?.URI==="http://acs.amazonaws.com/groups/global/AllUsers"&&(s.Permission==="READ"||s.Permission==="FULL_CONTROL"))?"public-read":"private"}catch{return "private"}}function E(e){return u(async o=>{let r=await y(o);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=d(r.key,"key");if(t instanceof Response)return t;let n=r.bucket?.trim()||e.defaultBucket,s=await p(e.upload?.confirmGuard,{request:o,key:t,bucket:n});if(s)return s;let a=await e.s3.send(new HeadObjectCommand({Bucket:n,Key:t})),i=await T(e.s3,n,t),l=I(e.resolvePublicUrl,n,t,i),g=h(a.ContentDisposition),c={request:o,key:t,bucket:n,contentType:a.ContentType,contentLength:a.ContentLength??0,eTag:a.ETag?.replace(/"/g,""),metadata:a.Metadata,acl:i,fileName:g,publicUrl:l,versionId:a.VersionId,lastModified:a.LastModified?.toISOString()};return await e.upload?.onUploadConfirmed?.(c),Response.json({key:t,bucket:n,contentType:c.contentType,contentLength:c.contentLength,eTag:c.eTag,metadata:c.metadata??{},acl:i,fileName:g,publicUrl:l,versionId:c.versionId,lastModified:c.lastModified})})}function j(e){return u(async o=>{let{searchParams:r}=new URL(o.url),t=r.get("key")?.trim();if(!t)return Response.json({message:"key query parameter is required"},{status:400});let n=r.get("bucket")?.trim()||e.defaultBucket,s=S(r.get("expiresIn")),a=r.get("fileName")?.trim(),i=await p(e.download?.presignGuard,{request:o,key:t,bucket:n,fileName:a||void 0});if(i)return i;try{await e.s3.send(new HeadObjectCommand({Bucket:n,Key:t}));}catch(g){let c=g?.name;if(c==="NoSuchKey"||c==="NotFound")return Response.json({message:"Object not found"},{status:404});throw g}let l=await getSignedUrl(e.s3,new GetObjectCommand({Bucket:n,Key:t,ResponseContentDisposition:a?b(a):"attachment"}),{expiresIn:s});return await e.download?.onPresigned?.({request:o,key:t,bucket:n,fileName:a||void 0,url:l,expiresIn:s}),Response.json({bucket:n,key:t,url:l,expiresIn:s})})}function O(e){return u(async o=>{let{searchParams:r}=new URL(o.url),t=r.get("key")?.trim();if(!t)return Response.json({message:"key query parameter is required"},{status:400});let n=r.get("bucket")?.trim()||e.defaultBucket,s=await p(e.delete?.deleteGuard,{request:o,key:t,bucket:n});if(s)return s;try{await e.s3.send(new HeadObjectCommand({Bucket:n,Key:t}));}catch{return Response.json({message:"Object not found"},{status:404})}return await e.s3.send(new DeleteObjectCommand({Bucket:n,Key:t})),await e.delete?.onDeleted?.({request:o,key:t,bucket:n}),Response.json({success:!0,bucket:n,key:t})})}function v(e){return u(async o=>{let r=await y(o);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=d(r.key,"key");if(t instanceof Response)return t;let n=r.bucket?.trim()||e.defaultBucket,s=r.acl==="public-read"?"public-read":"private",a=typeof r.fileSize=="number"&&r.fileSize>0?Math.floor(r.fileSize):void 0,i=await p(e.multipart?.initGuard,{request:o,key:t,bucket:n,fileSize:a});if(i)return i;let{UploadId:l}=await e.s3.send(new CreateMultipartUploadCommand({Bucket:n,Key:t,ContentType:r.contentType,ContentDisposition:r.fileName?b(r.fileName):void 0,Metadata:r.metadata,ACL:s}));return await e.multipart?.onInit?.({request:o,key:t,bucket:n,uploadId:l,contentType:r.contentType,fileSize:a,metadata:r.metadata,acl:s}),Response.json({bucket:n,key:t,uploadId:l},{status:201})})}function A(e){return u(async o=>{let r=await y(o);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=d(r.key,"key");if(t instanceof Response)return t;let n=d(r.uploadId,"uploadId");if(n instanceof Response)return n;let s=Number(r.partNumber);if(!Number.isInteger(s)||s<=0)return Response.json({message:"partNumber must be a positive integer"},{status:400});let a=r.bucket?.trim()||e.defaultBucket,i=S(r.expiresIn),l=await p(e.multipart?.partGuard,{request:o,key:t,bucket:a});if(l)return l;let g=await getSignedUrl(e.s3,new UploadPartCommand({Bucket:a,Key:t,UploadId:n,PartNumber:s}),{expiresIn:i});return Response.json({presignedUrl:g,partNumber:s,uploadId:n,bucket:a,expiresIn:i})})}function B(e){return u(async o=>{let r=await y(o);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=d(r.key,"key");if(t instanceof Response)return t;let n=d(r.uploadId,"uploadId");if(n instanceof Response)return n;let s=(Array.isArray(r.parts)?r.parts:[]).map(({partNumber:m})=>Number(m)).filter(m=>Number.isInteger(m)&&m>0).sort((m,H)=>m-H);if(!s.length)return Response.json({message:"At least one valid part is required"},{status:400});let a=r.bucket?.trim()||e.defaultBucket,i=await p(e.multipart?.completeGuard,{request:o,key:t,bucket:a});if(i)return i;let g=(await e.s3.send(new ListPartsCommand({Bucket:a,Key:t,UploadId:n}))).Parts??[],c=s.map(m=>{let H=g.find(J=>J.PartNumber===m);return {PartNumber:m,ETag:H?.ETag??""}}),C=await e.s3.send(new CompleteMultipartUploadCommand({Bucket:a,Key:t,UploadId:n,MultipartUpload:{Parts:c}})),f=await e.s3.send(new HeadObjectCommand({Bucket:a,Key:t}));for(let m=0;m<4&&!f.ContentLength;m++)await new Promise(H=>setTimeout(H,250*2**m)),f=await e.s3.send(new HeadObjectCommand({Bucket:a,Key:t}));let P=f.ContentLength??0,x=f.ContentType,N=(f.ETag??C.ETag??"").replace(/"/g,""),R=f.Metadata??{},w=f.VersionId,D=f.LastModified?.toISOString(),U=await T(e.s3,a,t),z=I(e.resolvePublicUrl,a,t,U),$=h(f.ContentDisposition);return await e.multipart?.onComplete?.({request:o,key:t,bucket:a,uploadId:n,contentLength:P,contentType:x,eTag:N,metadata:R,acl:U,fileName:$,publicUrl:z,versionId:w,lastModified:D}),Response.json({bucket:a,key:t,uploadId:n,contentLength:P,contentType:x,eTag:N,metadata:R,acl:U,fileName:$,publicUrl:z,versionId:w,lastModified:D})})}function F(e){return u(async o=>{let r=await y(o);if(!r)return Response.json({message:"Invalid JSON payload"},{status:400});let t=d(r.key,"key");if(t instanceof Response)return t;let n=d(r.uploadId,"uploadId");if(n instanceof Response)return n;let s=r.bucket?.trim()||e.defaultBucket,a=await p(e.multipart?.abortGuard,{request:o,key:t,bucket:s});return a||(await e.s3.send(new AbortMultipartUploadCommand({Bucket:s,Key:t,UploadId:n})),await e.multipart?.onAbort?.({request:o,key:t,bucket:s,uploadId:n}),Response.json({bucket:s,key:t,uploadId:n,aborted:!0}))})}function L(e){return {presign:{upload:M(e),confirm:E(e),download:j(e)},multipart:{init:v(e),part:A(e),complete:B(e),abort:F(e)},delete:O(e)}}var k=()=>Response.json({message:"Not Found"},{status:404});function pe(e){let o=L(e),r=e.basePath.replace(/\/$/,"");return async t=>{let n=await p(e.guard,{request:t});if(n)return n;let a=new URL(t.url).pathname.slice(r.length).replace(/^\//,""),i=t.method;return i==="POST"&&a==="presign/upload"?e.upload?.enabled?o.presign.upload(t):k():i==="POST"&&a==="presign/upload/confirm"?e.upload?.enabled?o.presign.confirm(t):k():i==="GET"&&a==="presign/download"?e.download?.enabled?o.presign.download(t):k():i==="DELETE"&&a==="delete"?e.delete?.enabled?o.delete(t):k():i==="POST"&&a==="presign/multipart/init"?e.multipart?.enabled?o.multipart.init(t):k():i==="POST"&&a==="presign/multipart/part"?e.multipart?.enabled?o.multipart.part(t):k():i==="POST"&&a==="presign/multipart/complete"?e.multipart?.enabled?o.multipart.complete(t):k():i==="POST"&&a==="presign/multipart/abort"?e.multipart?.enabled?o.multipart.abort(t):k():Response.json({message:"Not Found"},{status:404})}}function G(e="/api/s3"){let o=e.replace(/\/$/,""),r=async(n,s)=>{let a=await fetch(n,s);if(!a.ok){let i=await a.json().catch(()=>({}));throw new Error(i.message??a.statusText)}return a.json()},t=(n,s)=>r(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});return {upload(n){return t(`${o}/presign/upload`,n)},confirm(n){return t(`${o}/presign/upload/confirm`,n)},download(n,s){let a=new URLSearchParams({key:n});if(s?.fileName){let i=s.fileName.replace(/["\\\r\n]/g,"_");a.set("fileName",i);}return s?.bucket&&a.set("bucket",s.bucket),r(`${o}/presign/download?${a}`)},delete(n,s){let a=new URLSearchParams({key:n});return s?.bucket&&a.set("bucket",s.bucket),r(`${o}/delete?${a}`,{method:"DELETE"})},multipart:{init(n){return t(`${o}/presign/multipart/init`,n)},signPart(n){return t(`${o}/presign/multipart/part`,n)},complete(n){return t(`${o}/presign/multipart/complete`,n)},abort(n){return t(`${o}/presign/multipart/abort`,n)}}}}function le(e,o){if(o.accept?.length&&!o.accept.some(t=>t.startsWith(".")?e.name.toLowerCase().endsWith(t.toLowerCase()):t.endsWith("/*")?e.type.startsWith(t.replace("/*","/")):e.type===t)){let t=e.name.includes(".")?e.name.split(".").pop():null;return `File type "${t?`.${t}`:e.type||"unknown"}" is not allowed`}return e.size===0?"File is empty":o.maxFileSize&&e.size>o.maxFileSize?`File size exceeds ${(o.maxFileSize/1048576).toFixed(1)} MB limit`:null}
2
+ export{E as createConfirmHandler,O as createDeleteHandler,j as createDownloadHandler,L as createHandlers,F as createMultipartAbortHandler,B as createMultipartCompleteHandler,v as createMultipartInitHandler,A as createMultipartPartHandler,G as createPresignApi,pe as createRouter,G as createS3Api,M as createUploadHandler,le as validateFile};//# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal-helpers.ts","../src/helpers/build-content-disposition.ts","../src/helpers/build-public-url.ts","../src/helpers/parse-file-name.ts","../src/handlers/presign/upload.ts","../src/helpers/server/resolve-object-acl.ts","../src/handlers/presign/confirm.ts","../src/handlers/presign/download.ts","../src/handlers/delete.ts","../src/handlers/multipart/init.ts","../src/handlers/multipart/part.ts","../src/handlers/multipart/complete.ts","../src/handlers/multipart/abort.ts","../src/create-handlers.ts","../src/router.ts","../src/api.ts","../src/helpers/validate-file.ts"],"names":["parseBody","request","body","requireString","value","name","trimmed","normalizeExpiresIn","withS3ErrorHandler","handler","err","message","runHook","hook","context","status","buildContentDisposition","fileName","ascii","encoded","buildPublicUrl","publicUrlBase","bucket","key","acl","parseFileName","contentDisposition","utf8Match","MAX_UPLOAD_SIZE","createUploadHandler","config","expiresIn","contentType","fileSize","guardResult","fields","k","v","rangeMin","rangeMax","url","signedFields","createPresignedPost","resolveObjectAcl","s3","GetObjectAclCommand","g","createConfirmHandler","head","HeadObjectCommand","publicUrl","createDownloadHandler","searchParams","getSignedUrl","GetObjectCommand","createDeleteHandler","DeleteObjectCommand","createMultipartInitHandler","UploadId","CreateMultipartUploadCommand","createMultipartPartHandler","uploadId","partNumber","presignedUrl","UploadPartCommand","createMultipartCompleteHandler","parts","eTag","p","a","b","CompleteMultipartUploadCommand","contentLength","createMultipartAbortHandler","AbortMultipartUploadCommand","createHandlers","disabled","createRouter","handlers","base","subpath","method","createS3Api","basePath","json","init","res","post","payload","options","params","safe","validateFile","file","type","ext"],"mappings":"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","file":"index.js","sourcesContent":["export async function parseBody<T extends Record<string, unknown>>(\n request: Request,\n): Promise<T | null> {\n try {\n const body = await request.json();\n return body && typeof body === \"object\" ? (body as T) : null;\n } catch {\n return null;\n }\n}\n\nexport function requireString(value: unknown, name: string): string | Response {\n const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n if (!trimmed) {\n return Response.json({ message: `${name} is required` }, { status: 400 });\n }\n return trimmed;\n}\n\nexport function normalizeExpiresIn(value: unknown): number {\n const n = Number(value);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 600;\n}\n\nexport function withS3ErrorHandler(\n handler: (request: Request) => Promise<Response>,\n) {\n return async (request: Request) => {\n try {\n return await handler(request);\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Internal server error\";\n console.error(\"[S3 API]\", message);\n return Response.json({ message }, { status: 500 });\n }\n };\n}\n\n/**\n * Run a server hook. Returns a Response if the hook rejects (throws),\n * or `null` if the hook passes (or is undefined).\n */\nexport async function runHook<T>(\n hook: ((context: T) => Promise<void> | void) | undefined,\n context: T,\n): Promise<Response | null> {\n if (!hook) return null;\n try {\n await hook(context);\n return null;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Forbidden\";\n const status =\n typeof (err as Record<string, unknown>)?.status === \"number\"\n ? ((err as Record<string, unknown>).status as number)\n : 403;\n return Response.json({ message }, { status });\n }\n}\n","/**\n * Builds a RFC 6266 `Content-Disposition` value for a file attachment.\n *\n * Includes both the ASCII-safe `filename` fallback and the RFC 5987\n * `filename*` parameter for full Unicode support, so the original file\n * name (including non-ASCII characters) is preserved across all browsers\n * and HTTP clients.\n *\n * @example\n * buildContentDisposition(\"résumé finale.pdf\")\n * // → 'attachment; filename=\"resume finale.pdf\"; filename*=UTF-8\\'\\'r%C3%A9sum%C3%A9%20finale.pdf'\n */\nexport function buildContentDisposition(fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7E]/g, \"_\").replace(/[\"\\\\]/g, \"_\");\n const encoded = encodeURIComponent(fileName);\n return `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n}\n","/**\n * Builds the public URL for an S3 object.\n *\n * Returns `undefined` when:\n * - the object is `\"private\"`, or\n * - `publicUrlBase` is not provided.\n *\n * @param publicUrlBase - A function that returns the base URL for a given bucket.\n * Matches the `publicUrlBase` field of `S3HandlerConfig`.\n */\nexport function buildPublicUrl(\n publicUrlBase: ((bucket: string) => string) | undefined,\n bucket: string,\n key: string,\n acl: \"public-read\" | \"private\",\n): string | undefined {\n if (acl !== \"public-read\" || !publicUrlBase) return undefined;\n const base = publicUrlBase(bucket).replace(/\\/$/, \"\");\n return `${base}/${key}`;\n}\n","/**\n * Parses the display file name from a `Content-Disposition` header value.\n *\n * Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over\n * the plain ASCII `filename=\"…\"` fallback.\n *\n * Returns `undefined` when no filename can be extracted.\n */\nexport function parseFileName(\n contentDisposition: string | undefined,\n): string | undefined {\n if (!contentDisposition) return undefined;\n\n const utf8Match = contentDisposition.match(/filename\\*=UTF-8''([^;\\s]+)/i);\n if (utf8Match) {\n try {\n return decodeURIComponent(utf8Match[1]);\n } catch {\n /* ignore malformed encoding */\n }\n }\n\n const asciiMatch = contentDisposition.match(/filename=\"([^\"]*)\"/i);\n return asciiMatch?.[1];\n}\n","import { createPresignedPost } from \"@aws-sdk/s3-presigned-post\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildContentDisposition } from \"../../helpers\";\n\n// 5 GiB — S3 single-object upload limit\nconst MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024;\n\ntype Payload = {\n key: string;\n contentType?: string;\n /**\n * Exact byte size of the file the client intends to upload.\n * When provided the presigned POST policy locks `content-length-range` to\n * `[fileSize, fileSize]`, so S3 rejects any upload that is not exactly this\n * size — even if the client tampers with the payload.\n * When omitted the range is `[1, maxFileSize ?? 5 GiB]`.\n */\n fileSize?: number;\n metadata?: Record<string, string>;\n bucket?: string;\n expiresIn?: number;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition: attachment; filename=\"...\"` on the S3 object. */\n fileName?: string;\n};\n\nexport function createUploadHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(body.expiresIn);\n const acl = body.acl === \"public-read\" ? \"public-read\" : \"private\";\n const contentType = body.contentType?.trim() || \"application/octet-stream\";\n const fileSize =\n typeof body.fileSize === \"number\" && body.fileSize > 0\n ? Math.floor(body.fileSize)\n : null;\n\n if (\n fileSize !== null &&\n config.maxFileSize &&\n fileSize > config.maxFileSize\n ) {\n return Response.json(\n {\n message: `File size (${fileSize} bytes) exceeds the maximum allowed size of ${config.maxFileSize} bytes`,\n },\n { status: 413 },\n );\n }\n\n const guardResult = await runHook(config.upload?.guard, {\n request,\n key,\n bucket,\n contentType: body.contentType,\n fileSize: fileSize ?? undefined,\n metadata: body.metadata,\n acl,\n });\n if (guardResult) return guardResult;\n\n // Build presigned POST fields. Fields are embedded in the signed policy\n // so the client cannot change them without invalidating the signature.\n // Note: the `acl` field is the correct presigned POST field per AWS spec.\n // Some S3-compatible providers ignore it; PutObjectAcl is called in the\n // confirm step as a reliable post-upload fallback.\n const fields: Record<string, string> = { acl, \"Content-Type\": contentType };\n\n if (body.fileName) {\n fields[\"Content-Disposition\"] = buildContentDisposition(body.fileName);\n }\n\n if (body.metadata) {\n for (const [k, v] of Object.entries(body.metadata)) {\n fields[`x-amz-meta-${k}`] = v;\n }\n }\n\n // `content-length-range` is enforced by S3 at the storage layer —\n // uploads outside this range are rejected before the object is stored.\n //\n // When the client declares fileSize: S3 locks the range to exactly that\n // number of bytes, so uploading a different-sized file is impossible.\n // When fileSize is not declared: falls back to [1, maxFileSize ?? 5 GiB].\n const rangeMin = fileSize ?? 1;\n const rangeMax = fileSize ?? config.maxFileSize ?? MAX_UPLOAD_SIZE;\n\n const { url, fields: signedFields } = await createPresignedPost(config.s3, {\n Bucket: bucket,\n Key: key,\n Conditions: [[\"content-length-range\", rangeMin, rangeMax]],\n Fields: fields,\n Expires: expiresIn,\n });\n\n await config.upload?.onPresigned?.({\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n acl,\n url,\n expiresIn,\n });\n\n return Response.json({ bucket, key, url, fields: signedFields, expiresIn });\n });\n}\n","import { GetObjectAclCommand, type S3Client } from \"@aws-sdk/client-s3\";\n\n/**\n * Determines the ACL of an S3 object by querying `GetObjectAcl`.\n *\n * Returns `\"public-read\"` when the `AllUsers` grantee has `READ` or\n * `FULL_CONTROL` permission; falls back to `\"private\"` when the provider\n * does not support the operation (e.g. Cloudflare R2).\n */\nexport async function resolveObjectAcl(\n s3: S3Client,\n bucket: string,\n key: string,\n): Promise<\"public-read\" | \"private\"> {\n try {\n const result = await s3.send(\n new GetObjectAclCommand({ Bucket: bucket, Key: key }),\n );\n const isPublic = result.Grants?.some(\n (g) =>\n g.Grantee?.URI === \"http://acs.amazonaws.com/groups/global/AllUsers\" &&\n (g.Permission === \"READ\" || g.Permission === \"FULL_CONTROL\"),\n );\n return isPublic ? \"public-read\" : \"private\";\n } catch {\n return \"private\";\n }\n}\n","import { HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildPublicUrl, parseFileName } from \"../../helpers\";\nimport { resolveObjectAcl } from \"../../helpers/server\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n};\n\nexport function createConfirmHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.upload?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n\n const acl = await resolveObjectAcl(config.s3, bucket, key);\n const publicUrl = buildPublicUrl(config.resolvePublicUrl, bucket, key, acl);\n const fileName = parseFileName(head.ContentDisposition);\n\n const context = {\n request,\n key,\n bucket,\n contentType: head.ContentType,\n contentLength: head.ContentLength ?? 0,\n eTag: head.ETag?.replace(/\"/g, \"\"),\n metadata: head.Metadata,\n acl,\n fileName,\n publicUrl,\n };\n\n await config.upload?.onUploaded?.(context);\n\n return Response.json({\n key,\n bucket,\n contentType: context.contentType,\n contentLength: context.contentLength,\n eTag: context.eTag,\n acl,\n fileName,\n publicUrl,\n });\n });\n}\n","import { GetObjectCommand, HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildContentDisposition } from \"../../helpers\";\n\nexport function createDownloadHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const { searchParams } = new URL(request.url);\n const key = searchParams.get(\"key\")?.trim();\n if (!key) {\n return Response.json(\n { message: \"key query parameter is required\" },\n { status: 400 },\n );\n }\n\n const bucket = searchParams.get(\"bucket\")?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(searchParams.get(\"expiresIn\"));\n const fileName = searchParams.get(\"fileName\")?.trim();\n\n const guardResult = await runHook(config.download?.guard, {\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n });\n if (guardResult) return guardResult;\n\n try {\n await config.s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n } catch (err: unknown) {\n const name = (err as { name?: string })?.name;\n if (name === \"NoSuchKey\" || name === \"NotFound\") {\n return Response.json({ message: \"Object not found\" }, { status: 404 });\n }\n throw err;\n }\n\n const url = await getSignedUrl(\n config.s3,\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n ResponseContentDisposition: fileName\n ? buildContentDisposition(fileName)\n : \"attachment\",\n }),\n { expiresIn },\n );\n\n await config.download?.onPresigned?.({\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n url,\n expiresIn,\n });\n\n return Response.json({ bucket, key, url, expiresIn });\n });\n}\n","import { DeleteObjectCommand, HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../types\";\nimport { runHook, withS3ErrorHandler } from \"../internal-helpers\";\n\nexport function createDeleteHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const { searchParams } = new URL(request.url);\n const key = searchParams.get(\"key\")?.trim();\n if (!key) {\n return Response.json(\n { message: \"key query parameter is required\" },\n { status: 400 },\n );\n }\n\n const bucket = searchParams.get(\"bucket\")?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.delete?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n try {\n await config.s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n } catch {\n return Response.json({ message: \"Object not found\" }, { status: 404 });\n }\n\n await config.s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));\n\n await config.delete?.onDeleted?.({ request, key, bucket });\n\n return Response.json({ success: true, bucket, key });\n });\n}\n","import { CreateMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildContentDisposition } from \"../../helpers\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n contentType?: string;\n /** Declared total byte size of the file being uploaded. */\n fileSize?: number;\n metadata?: Record<string, string>;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition: attachment; filename=\"...\"` on the S3 object. */\n fileName?: string;\n};\n\nexport function createMultipartInitHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const acl = body.acl === \"public-read\" ? \"public-read\" : \"private\";\n const fileSize =\n typeof body.fileSize === \"number\" && body.fileSize > 0\n ? Math.floor(body.fileSize)\n : undefined;\n\n if (\n fileSize !== undefined &&\n config.maxFileSize &&\n fileSize > config.maxFileSize\n ) {\n return Response.json(\n {\n message: `File size (${fileSize} bytes) exceeds the maximum allowed size of ${config.maxFileSize} bytes`,\n },\n { status: 413 },\n );\n }\n\n const guardResult = await runHook(config.multipart?.guard, {\n request,\n key,\n bucket,\n fileSize,\n });\n if (guardResult) return guardResult;\n\n const { UploadId } = await config.s3.send(\n new CreateMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n ContentType: body.contentType,\n ContentDisposition: body.fileName\n ? buildContentDisposition(body.fileName)\n : undefined,\n Metadata: body.metadata,\n ACL: acl,\n }),\n );\n\n await config.multipart?.onInit?.({\n request,\n key,\n bucket,\n uploadId: UploadId!,\n contentType: body.contentType,\n fileSize,\n metadata: body.metadata,\n acl,\n });\n\n return Response.json({ bucket, key, uploadId: UploadId }, { status: 201 });\n });\n}\n","import { UploadPartCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\n\ntype Payload = {\n key: string;\n uploadId: string;\n partNumber: number;\n bucket?: string;\n expiresIn?: number;\n};\n\nexport function createMultipartPartHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const partNumber = Number(body.partNumber);\n if (!Number.isInteger(partNumber) || partNumber <= 0) {\n return Response.json(\n { message: \"partNumber must be a positive integer\" },\n { status: 400 },\n );\n }\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(body.expiresIn);\n\n const guardResult = await runHook(config.multipart?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const presignedUrl = await getSignedUrl(\n config.s3,\n new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n }),\n { expiresIn },\n );\n\n return Response.json({\n presignedUrl,\n partNumber,\n uploadId,\n bucket,\n expiresIn,\n });\n });\n}\n","import {\n CompleteMultipartUploadCommand,\n HeadObjectCommand,\n DeleteObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\nimport { buildPublicUrl, parseFileName } from \"../../helpers\";\nimport { resolveObjectAcl } from \"../../helpers/server\";\n\ntype PartEntry = {\n partNumber: number;\n eTag: string;\n};\n\ntype Payload = {\n key: string;\n uploadId: string;\n bucket?: string;\n parts: PartEntry[];\n};\n\nexport function createMultipartCompleteHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const parts = (Array.isArray(body.parts) ? body.parts : [])\n .map(({ partNumber, eTag }) => ({\n PartNumber: Number(partNumber),\n ETag: String(eTag),\n }))\n .filter((p) => Number.isInteger(p.PartNumber) && p.ETag)\n .sort((a, b) => a.PartNumber - b.PartNumber);\n\n if (!parts.length) {\n return Response.json(\n { message: \"At least one valid part is required\" },\n { status: 400 },\n );\n }\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.multipart?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n await config.s3.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: { Parts: parts },\n }),\n );\n\n // Verify the actual uploaded object metadata via HeadObject.\n // This is the server's only opportunity to enforce maxFileSize for\n // multipart uploads, since individual parts bypass size checks.\n const head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n const contentLength = head.ContentLength ?? 0;\n const contentType = head.ContentType;\n const eTag = head.ETag?.replace(/\"/g, \"\");\n\n const acl = await resolveObjectAcl(config.s3, bucket, key);\n const publicUrl = buildPublicUrl(config.resolvePublicUrl, bucket, key, acl);\n const fileName = parseFileName(head.ContentDisposition);\n\n if (config.maxFileSize && contentLength > config.maxFileSize) {\n // Delete the object and reject the request. The client has uploaded\n // more data than the server policy allows.\n await config.s3\n .send(new DeleteObjectCommand({ Bucket: bucket, Key: key }))\n .catch(() => {});\n return Response.json(\n {\n message: `File size (${contentLength} bytes) exceeds the maximum allowed size of ${config.maxFileSize} bytes`,\n },\n { status: 422 },\n );\n }\n\n await config.multipart?.onComplete?.({\n request,\n key,\n bucket,\n uploadId,\n contentLength,\n contentType,\n eTag,\n acl,\n fileName,\n publicUrl,\n });\n\n return Response.json({\n bucket,\n key,\n uploadId,\n contentLength,\n contentType,\n eTag,\n acl,\n fileName,\n publicUrl,\n });\n });\n}\n","import { AbortMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../internal-helpers\";\n\ntype Payload = {\n key: string;\n uploadId: string;\n bucket?: string;\n};\n\nexport function createMultipartAbortHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.multipart?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n await config.s3.send(\n new AbortMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n }),\n );\n\n await config.multipart?.onAbort?.({\n request,\n key,\n bucket,\n uploadId,\n });\n\n return Response.json({ bucket, key, uploadId, aborted: true });\n });\n}\n","import type { S3HandlerConfig, S3Handlers } from \"./types\";\nimport { createUploadHandler } from \"./handlers/presign/upload\";\nimport { createConfirmHandler } from \"./handlers/presign/confirm\";\nimport { createDownloadHandler } from \"./handlers/presign/download\";\nimport { createDeleteHandler } from \"./handlers/delete\";\nimport { createMultipartInitHandler } from \"./handlers/multipart/init\";\nimport { createMultipartPartHandler } from \"./handlers/multipart/part\";\nimport { createMultipartCompleteHandler } from \"./handlers/multipart/complete\";\nimport { createMultipartAbortHandler } from \"./handlers/multipart/abort\";\n\nexport function createHandlers(config: S3HandlerConfig): S3Handlers {\n return {\n presign: {\n upload: createUploadHandler(config),\n confirm: createConfirmHandler(config),\n download: createDownloadHandler(config),\n },\n multipart: {\n init: createMultipartInitHandler(config),\n part: createMultipartPartHandler(config),\n complete: createMultipartCompleteHandler(config),\n abort: createMultipartAbortHandler(config),\n },\n delete: createDeleteHandler(config),\n };\n}\n","import type { S3RouteHandlerConfig } from \"./types\";\nimport { createHandlers } from \"./create-handlers\";\nimport { runHook } from \"./internal-helpers\";\n\nconst disabled = () => Response.json({ message: \"Not Found\" }, { status: 404 });\n\nexport function createRouter(config: S3RouteHandlerConfig) {\n const handlers = createHandlers(config);\n const base = config.basePath.replace(/\\/$/, \"\");\n\n return async (request: Request): Promise<Response> => {\n // Global guard — runs before every request\n const guardResult = await runHook(config.guard, { request });\n if (guardResult) return guardResult;\n\n const url = new URL(request.url);\n const subpath = url.pathname.slice(base.length).replace(/^\\//, \"\");\n const method = request.method;\n\n if (method === \"POST\" && subpath === \"presign/upload\")\n return config.upload?.enabled\n ? handlers.presign.upload(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/upload/confirm\")\n return config.upload?.enabled\n ? handlers.presign.confirm(request)\n : disabled();\n if (method === \"GET\" && subpath === \"presign/download\")\n return config.download?.enabled\n ? handlers.presign.download(request)\n : disabled();\n if (method === \"DELETE\" && subpath === \"delete\")\n return config.delete?.enabled ? handlers.delete(request) : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/init\")\n return config.multipart?.enabled\n ? handlers.multipart.init(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/part\")\n return config.multipart?.enabled\n ? handlers.multipart.part(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/complete\")\n return config.multipart?.enabled\n ? handlers.multipart.complete(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/abort\")\n return config.multipart?.enabled\n ? handlers.multipart.abort(request)\n : disabled();\n\n return Response.json({ message: \"Not Found\" }, { status: 404 });\n };\n}\n","export type PresignResponse = {\n key: string;\n bucket: string;\n url: string;\n expiresIn: number;\n};\n\nexport type PresignedPostResponse = {\n key: string;\n bucket: string;\n url: string;\n fields: Record<string, string>;\n expiresIn: number;\n};\n\nexport type MultipartInitResponse = {\n key: string;\n bucket: string;\n uploadId: string;\n};\n\nexport type MultipartPartResponse = {\n presignedUrl: string;\n partNumber: number;\n uploadId: string;\n bucket: string;\n expiresIn: number;\n};\n\nexport type UploadConfirmResponse = {\n key: string;\n bucket: string;\n contentType?: string;\n contentLength: number;\n eTag?: string;\n acl: \"private\" | \"public-read\";\n /** Display file name parsed from the object's Content-Disposition header. */\n fileName?: string;\n /** Public URL of the object. Only present when `acl` is `public-read` and `publicUrlBase` is configured on the server. */\n publicUrl?: string;\n};\n\nexport type S3Api = {\n upload: (payload: {\n key: string;\n contentType?: string;\n /**\n * Exact byte size of the file. When provided the presigned POST policy\n * locks `content-length-range` to `[fileSize, fileSize]` so S3 rejects\n * uploads of any other size at the storage layer.\n */\n fileSize?: number;\n metadata?: Record<string, string>;\n bucket?: string;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition` on the S3 object. */\n fileName?: string;\n }) => Promise<PresignedPostResponse>;\n confirm: (payload: {\n key: string;\n bucket?: string;\n }) => Promise<UploadConfirmResponse>;\n download: (\n key: string,\n options?: { fileName?: string; bucket?: string },\n ) => Promise<PresignResponse>;\n delete: (\n key: string,\n options?: { bucket?: string },\n ) => Promise<{ success: boolean; bucket: string; key: string }>;\n multipart: {\n init: (payload: {\n key: string;\n contentType?: string;\n /** Declared total byte size of the file. Used for quota/guard checks and stored in `onInit` context. */\n fileSize?: number;\n metadata?: Record<string, string>;\n bucket?: string;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition` on the S3 object. */\n fileName?: string;\n }) => Promise<MultipartInitResponse>;\n signPart: (payload: {\n key: string;\n uploadId: string;\n partNumber: number;\n bucket?: string;\n }) => Promise<MultipartPartResponse>;\n complete: (payload: {\n key: string;\n uploadId: string;\n parts: Array<{ partNumber: number; eTag: string }>;\n bucket?: string;\n }) => Promise<{\n key: string;\n bucket: string;\n uploadId: string;\n contentLength: number;\n contentType?: string;\n eTag?: string;\n }>;\n abort: (payload: {\n key: string;\n uploadId: string;\n bucket?: string;\n }) => Promise<{ aborted: boolean }>;\n };\n};\n\nexport function createS3Api(basePath = \"/api/s3\"): S3Api {\n const base = basePath.replace(/\\/$/, \"\");\n\n const json = async <T>(url: string, init?: RequestInit): Promise<T> => {\n const res = await fetch(url, init);\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error((body as { message?: string }).message ?? res.statusText);\n }\n return res.json() as Promise<T>;\n };\n\n const post = <T>(url: string, body: unknown): Promise<T> =>\n json<T>(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n return {\n upload(payload) {\n return post<PresignedPostResponse>(`${base}/presign/upload`, payload);\n },\n\n confirm(payload) {\n return post<UploadConfirmResponse>(\n `${base}/presign/upload/confirm`,\n payload,\n );\n },\n\n download(key, options?) {\n const params = new URLSearchParams({ key });\n if (options?.fileName) {\n const safe = options.fileName.replace(/[\"\\\\\\r\\n]/g, \"_\");\n params.set(\"fileName\", safe);\n }\n if (options?.bucket) params.set(\"bucket\", options.bucket);\n return json<PresignResponse>(`${base}/presign/download?${params}`);\n },\n\n delete(key, options?) {\n const params = new URLSearchParams({ key });\n if (options?.bucket) params.set(\"bucket\", options.bucket);\n return json<{ success: boolean; bucket: string; key: string }>(\n `${base}/delete?${params}`,\n { method: \"DELETE\" },\n );\n },\n\n multipart: {\n init(payload) {\n return post<MultipartInitResponse>(\n `${base}/presign/multipart/init`,\n payload,\n );\n },\n\n signPart(payload) {\n return post<MultipartPartResponse>(\n `${base}/presign/multipart/part`,\n payload,\n );\n },\n\n complete(payload) {\n return post<{\n key: string;\n bucket: string;\n uploadId: string;\n contentLength: number;\n contentType?: string;\n eTag?: string;\n }>(`${base}/presign/multipart/complete`, payload);\n },\n\n abort(payload) {\n return post<{ aborted: boolean }>(\n `${base}/presign/multipart/abort`,\n payload,\n );\n },\n },\n };\n}\n","export function validateFile(\n file: File,\n options: { accept?: string[]; maxFileSize?: number },\n): string | null {\n if (options.accept?.length) {\n const allowed = options.accept.some((type) => {\n // Extension check: \".pdf\", \".jpg\", etc.\n if (type.startsWith(\".\")) {\n return file.name.toLowerCase().endsWith(type.toLowerCase());\n }\n // Wildcard MIME: \"image/*\"\n if (type.endsWith(\"/*\")) {\n return file.type.startsWith(type.replace(\"/*\", \"/\"));\n }\n // Exact MIME: \"application/pdf\"\n return file.type === type;\n });\n if (!allowed) {\n const ext = file.name.includes(\".\") ? file.name.split(\".\").pop() : null;\n return `File type \"${ext ? `.${ext}` : file.type || \"unknown\"}\" is not allowed`;\n }\n }\n\n if (file.size === 0) {\n return \"File is empty\";\n }\n\n if (options.maxFileSize && file.size > options.maxFileSize) {\n const maxMB = (options.maxFileSize / (1024 * 1024)).toFixed(1);\n return `File size exceeds ${maxMB} MB limit`;\n }\n\n return null;\n}\n"]}
1
+ {"version":3,"sources":["../src/internal-helpers.ts","../src/helpers/build-content-disposition.ts","../src/helpers/build-public-url.ts","../src/helpers/parse-file-name.ts","../src/handlers/presign/upload.ts","../src/helpers/server/resolve-object-acl.ts","../src/handlers/presign/confirm.ts","../src/handlers/presign/download.ts","../src/handlers/delete.ts","../src/handlers/multipart/init.ts","../src/handlers/multipart/part.ts","../src/handlers/multipart/complete.ts","../src/handlers/multipart/abort.ts","../src/create-handlers.ts","../src/router.ts","../src/api.ts","../src/helpers/validate-file.ts"],"names":["parseBody","request","body","requireString","value","name","trimmed","normalizeExpiresIn","n","withS3ErrorHandler","handler","err","code","message","runHook","hook","context","status","buildContentDisposition","fileName","ascii","encoded","buildPublicUrl","publicUrlBase","bucket","key","acl","base","parseFileName","contentDisposition","utf8Match","createUploadHandler","config","expiresIn","contentType","fileSize","guardResult","putHeaders","url","getSignedUrl","PutObjectCommand","fields","k","v","rangeMin","rangeMax","signedFields","createPresignedPost","resolveObjectAcl","s3","GetObjectAclCommand","g","createConfirmHandler","head","HeadObjectCommand","publicUrl","createDownloadHandler","searchParams","GetObjectCommand","createDeleteHandler","DeleteObjectCommand","createMultipartInitHandler","UploadId","CreateMultipartUploadCommand","createMultipartPartHandler","uploadId","partNumber","presignedUrl","UploadPartCommand","createMultipartCompleteHandler","parts","a","b","listedParts","ListPartsCommand","completeParts","found","p","completeResult","CompleteMultipartUploadCommand","attempt","r","contentLength","eTag","metadata","versionId","lastModified","createMultipartAbortHandler","AbortMultipartUploadCommand","createHandlers","disabled","createRouter","handlers","subpath","method","createS3Api","basePath","json","init","res","post","payload","options","params","safe","validateFile","file","type","ext"],"mappings":"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","file":"index.js","sourcesContent":["export async function parseBody<T extends Record<string, unknown>>(\n request: Request,\n): Promise<T | null> {\n try {\n const body = await request.json();\n return body && typeof body === \"object\" ? (body as T) : null;\n } catch {\n return null;\n }\n}\n\nexport function requireString(value: unknown, name: string): string | Response {\n const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n if (!trimmed) {\n return Response.json({ message: `${name} is required` }, { status: 400 });\n }\n return trimmed;\n}\n\nexport function normalizeExpiresIn(value: unknown): number {\n const n = Number(value);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 600;\n}\n\nexport function withS3ErrorHandler(\n handler: (request: Request) => Promise<Response>,\n) {\n return async (request: Request) => {\n try {\n return await handler(request);\n } catch (err) {\n const code = (err as { code?: string }).code;\n const isNetworkError =\n code === \"EAI_AGAIN\" ||\n code === \"ECONNREFUSED\" ||\n code === \"ECONNRESET\" ||\n code === \"ETIMEDOUT\" ||\n code === \"ENOTFOUND\";\n\n const message = isNetworkError\n ? `S3 endpoint unreachable (${code}): check your endpoint URL and network connectivity`\n : err instanceof Error\n ? err.message\n : \"Internal server error\";\n\n console.error(\"[S3 API]\", message);\n return Response.json({ message }, { status: 502 });\n }\n };\n}\n\n/**\n * Run a server hook. Returns a Response if the hook rejects (throws),\n * or `null` if the hook passes (or is undefined).\n */\nexport async function runHook<T>(\n hook: ((context: T) => Promise<void> | void) | undefined,\n context: T,\n): Promise<Response | null> {\n if (!hook) return null;\n try {\n await hook(context);\n return null;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Forbidden\";\n const status =\n typeof (err as Record<string, unknown>)?.status === \"number\"\n ? ((err as Record<string, unknown>).status as number)\n : 403;\n return Response.json({ message }, { status });\n }\n}\n","/**\n * Builds a RFC 6266 `Content-Disposition` value for a file attachment.\n *\n * Includes both the ASCII-safe `filename` fallback and the RFC 5987\n * `filename*` parameter for full Unicode support, so the original file\n * name (including non-ASCII characters) is preserved across all browsers\n * and HTTP clients.\n *\n * @example\n * buildContentDisposition(\"résumé finale.pdf\")\n * // → 'attachment; filename=\"resume finale.pdf\"; filename*=UTF-8\\'\\'r%C3%A9sum%C3%A9%20finale.pdf'\n */\nexport function buildContentDisposition(fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7E]/g, \"_\").replace(/[\"\\\\]/g, \"_\");\n const encoded = encodeURIComponent(fileName);\n return `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n}\n","/**\n * Builds the public URL for an S3 object.\n *\n * Returns `undefined` when:\n * - the object is `\"private\"`,\n * - `publicUrlBase` is not provided, or\n * - `publicUrlBase` returns `undefined` for the given bucket.\n *\n * @param publicUrlBase - A function that returns the base URL for a given bucket,\n * or `undefined` when no public URL is available for that bucket.\n * Matches the `resolvePublicUrl` field of `S3HandlerConfig`.\n */\nexport function buildPublicUrl(\n publicUrlBase: ((bucket: string) => string | undefined) | undefined,\n bucket: string,\n key: string,\n acl: \"public-read\" | \"private\",\n): string | undefined {\n if (acl !== \"public-read\" || !publicUrlBase) return undefined;\n const base = publicUrlBase(bucket);\n if (!base) return undefined;\n return `${base.replace(/\\/$/, \"\")}/${key}`;\n}\n","/**\n * Parses the display file name from a `Content-Disposition` header value.\n *\n * Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over\n * the plain ASCII `filename=\"…\"` fallback.\n *\n * Returns `undefined` when no filename can be extracted.\n */\nexport function parseFileName(\n contentDisposition: string | undefined,\n): string | undefined {\n if (!contentDisposition) return undefined;\n\n const utf8Match = contentDisposition.match(/filename\\*=UTF-8''([^;\\s]+)/i);\n if (utf8Match) {\n try {\n return decodeURIComponent(utf8Match[1]);\n } catch {\n /* ignore malformed encoding */\n }\n }\n\n const asciiMatch = contentDisposition.match(/filename=\"([^\"]*)\"/i);\n return asciiMatch?.[1];\n}\n","import { createPresignedPost } from \"@aws-sdk/s3-presigned-post\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { PutObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildContentDisposition } from \"@/helpers\";\n\ntype Payload = {\n key: string;\n contentType?: string;\n /**\n * Exact byte size of the file the client intends to upload.\n * When provided the presigned POST policy locks `content-length-range` to\n * `[fileSize, fileSize]`, so S3 rejects any upload that is not exactly this\n * size — even if the client tampers with the payload.\n * When omitted the range is `[1, maxFileSize ?? 5 GiB]`.\n */\n fileSize?: number;\n metadata?: Record<string, string>;\n bucket?: string;\n expiresIn?: number;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition: attachment; filename=\"...\"` on the S3 object. */\n fileName?: string;\n};\n\nexport function createUploadHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(body.expiresIn);\n const acl = body.acl === \"public-read\" ? \"public-read\" : \"private\";\n const contentType = body.contentType?.trim() || \"application/octet-stream\";\n const fileSize =\n typeof body.fileSize === \"number\" && body.fileSize > 0\n ? Math.floor(body.fileSize)\n : null;\n\n const guardResult = await runHook(config.upload?.presignGuard, {\n request,\n key,\n bucket,\n contentType: body.contentType,\n fileSize: fileSize ?? undefined,\n metadata: body.metadata,\n acl,\n });\n if (guardResult) return guardResult;\n\n const method = config.upload?.method ?? \"post\";\n\n if (method === \"put\") {\n // Presigned PUT: only Content-Type and Content-Disposition are included\n // as signed headers. x-amz-meta-* headers are intentionally excluded.\n //\n // In browser environments, sending x-amz-meta-* on a cross-origin XHR\n // triggers a CORS preflight. Most S3/R2 CORS configs do not include\n // x-amz-meta-* in AllowedHeaders, so the preflight fails and the upload\n // never reaches S3. Content-Disposition is a standard header that is\n // widely allowed in CORS configs.\n //\n // If you need metadata on the object, use method: \"post\" (presigned POST\n // embeds metadata inside the signed form policy, which S3 applies at\n // the storage layer without CORS header constraints).\n // The metadata value from the request body is still forwarded to the\n // onPresigned hook for server-side use (e.g. storing in a database).\n const putHeaders: Record<string, string> = {\n \"Content-Type\": contentType,\n };\n if (body.fileName) {\n putHeaders[\"Content-Disposition\"] = buildContentDisposition(\n body.fileName,\n );\n }\n\n const url = await getSignedUrl(\n config.s3,\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: contentType,\n ACL: acl,\n ...(body.fileName\n ? { ContentDisposition: buildContentDisposition(body.fileName) }\n : {}),\n }),\n { expiresIn },\n );\n\n await config.upload?.onPresigned?.({\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n acl,\n url,\n expiresIn,\n });\n\n return Response.json({\n bucket,\n key,\n url,\n headers: putHeaders,\n expiresIn,\n method: \"put\",\n });\n }\n\n // Presigned POST: policy fields are embedded in the signed form and must\n // be appended to FormData before the file. S3 enforces them at the storage layer.\n const fields: Record<string, string> = { acl, \"Content-Type\": contentType };\n\n if (body.fileName) {\n fields[\"Content-Disposition\"] = buildContentDisposition(body.fileName);\n }\n\n if (body.metadata) {\n for (const [k, v] of Object.entries(body.metadata)) {\n fields[`x-amz-meta-${k}`] = v;\n }\n }\n\n const rangeMin = fileSize ?? 1;\n const rangeMax = fileSize ?? undefined;\n\n const { url, fields: signedFields } = await createPresignedPost(config.s3, {\n Bucket: bucket,\n Key: key,\n Conditions:\n rangeMax !== undefined\n ? [[\"content-length-range\", rangeMin, rangeMax]]\n : [[\"content-length-range\", rangeMin, Number.MAX_SAFE_INTEGER]],\n Fields: fields,\n Expires: expiresIn,\n });\n\n await config.upload?.onPresigned?.({\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n acl,\n url,\n expiresIn,\n });\n\n return Response.json({\n bucket,\n key,\n url,\n fields: signedFields,\n expiresIn,\n method: \"post\",\n });\n });\n}\n","import { GetObjectAclCommand, type S3Client } from \"@aws-sdk/client-s3\";\n\n/**\n * Determines the ACL of an S3 object by querying `GetObjectAcl`.\n *\n * Returns `\"public-read\"` when the `AllUsers` grantee has `READ` or\n * `FULL_CONTROL` permission; falls back to `\"private\"` when the provider\n * does not support the operation (e.g. Cloudflare R2).\n */\nexport async function resolveObjectAcl(\n s3: S3Client,\n bucket: string,\n key: string,\n): Promise<\"public-read\" | \"private\"> {\n try {\n const result = await s3.send(\n new GetObjectAclCommand({ Bucket: bucket, Key: key }),\n );\n const isPublic = result.Grants?.some(\n (g) =>\n g.Grantee?.URI === \"http://acs.amazonaws.com/groups/global/AllUsers\" &&\n (g.Permission === \"READ\" || g.Permission === \"FULL_CONTROL\"),\n );\n return isPublic ? \"public-read\" : \"private\";\n } catch {\n return \"private\";\n }\n}\n","import { HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildPublicUrl, parseFileName } from \"@/helpers\";\nimport { resolveObjectAcl } from \"@/helpers/server\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n};\n\nexport function createConfirmHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.upload?.confirmGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n\n const acl = await resolveObjectAcl(config.s3, bucket, key);\n const publicUrl = buildPublicUrl(config.resolvePublicUrl, bucket, key, acl);\n const fileName = parseFileName(head.ContentDisposition);\n\n const context = {\n request,\n key,\n bucket,\n contentType: head.ContentType,\n contentLength: head.ContentLength ?? 0,\n eTag: head.ETag?.replace(/\"/g, \"\"),\n metadata: head.Metadata,\n acl,\n fileName,\n publicUrl,\n versionId: head.VersionId,\n lastModified: head.LastModified?.toISOString(),\n };\n\n await config.upload?.onUploadConfirmed?.(context);\n\n return Response.json({\n key,\n bucket,\n contentType: context.contentType,\n contentLength: context.contentLength,\n eTag: context.eTag,\n metadata: context.metadata ?? {},\n acl,\n fileName,\n publicUrl,\n versionId: context.versionId,\n lastModified: context.lastModified,\n });\n });\n}\n","import { GetObjectCommand, HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildContentDisposition } from \"@/helpers\";\n\nexport function createDownloadHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const { searchParams } = new URL(request.url);\n const key = searchParams.get(\"key\")?.trim();\n if (!key) {\n return Response.json(\n { message: \"key query parameter is required\" },\n { status: 400 },\n );\n }\n\n const bucket = searchParams.get(\"bucket\")?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(searchParams.get(\"expiresIn\"));\n const fileName = searchParams.get(\"fileName\")?.trim();\n\n const guardResult = await runHook(config.download?.presignGuard, {\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n });\n if (guardResult) return guardResult;\n\n try {\n await config.s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n } catch (err: unknown) {\n const name = (err as { name?: string })?.name;\n if (name === \"NoSuchKey\" || name === \"NotFound\") {\n return Response.json({ message: \"Object not found\" }, { status: 404 });\n }\n throw err;\n }\n\n const url = await getSignedUrl(\n config.s3,\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n ResponseContentDisposition: fileName\n ? buildContentDisposition(fileName)\n : \"attachment\",\n }),\n { expiresIn },\n );\n\n await config.download?.onPresigned?.({\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n url,\n expiresIn,\n });\n\n return Response.json({ bucket, key, url, expiresIn });\n });\n}\n","import { DeleteObjectCommand, HeadObjectCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../types\";\nimport { runHook, withS3ErrorHandler } from \"../internal-helpers\";\n\nexport function createDeleteHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const { searchParams } = new URL(request.url);\n const key = searchParams.get(\"key\")?.trim();\n if (!key) {\n return Response.json(\n { message: \"key query parameter is required\" },\n { status: 400 },\n );\n }\n\n const bucket = searchParams.get(\"bucket\")?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.delete?.deleteGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n try {\n await config.s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n } catch {\n return Response.json({ message: \"Object not found\" }, { status: 404 });\n }\n\n await config.s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));\n\n await config.delete?.onDeleted?.({ request, key, bucket });\n\n return Response.json({ success: true, bucket, key });\n });\n}\n","import { CreateMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildContentDisposition } from \"@/helpers\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n contentType?: string;\n /** Declared total byte size of the file being uploaded. */\n fileSize?: number;\n metadata?: Record<string, string>;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition: attachment; filename=\"...\"` on the S3 object. */\n fileName?: string;\n};\n\nexport function createMultipartInitHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const acl = body.acl === \"public-read\" ? \"public-read\" : \"private\";\n const fileSize =\n typeof body.fileSize === \"number\" && body.fileSize > 0\n ? Math.floor(body.fileSize)\n : undefined;\n\n const guardResult = await runHook(config.multipart?.initGuard, {\n request,\n key,\n bucket,\n fileSize,\n });\n if (guardResult) return guardResult;\n\n const { UploadId } = await config.s3.send(\n new CreateMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n ContentType: body.contentType,\n ContentDisposition: body.fileName\n ? buildContentDisposition(body.fileName)\n : undefined,\n Metadata: body.metadata,\n ACL: acl,\n }),\n );\n\n await config.multipart?.onInit?.({\n request,\n key,\n bucket,\n uploadId: UploadId!,\n contentType: body.contentType,\n fileSize,\n metadata: body.metadata,\n acl,\n });\n\n return Response.json({ bucket, key, uploadId: UploadId }, { status: 201 });\n });\n}\n","import { UploadPartCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\n\ntype Payload = {\n key: string;\n uploadId: string;\n partNumber: number;\n bucket?: string;\n expiresIn?: number;\n};\n\nexport function createMultipartPartHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const partNumber = Number(body.partNumber);\n if (!Number.isInteger(partNumber) || partNumber <= 0) {\n return Response.json(\n { message: \"partNumber must be a positive integer\" },\n { status: 400 },\n );\n }\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n const expiresIn = normalizeExpiresIn(body.expiresIn);\n\n const guardResult = await runHook(config.multipart?.partGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const presignedUrl = await getSignedUrl(\n config.s3,\n new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n }),\n { expiresIn },\n );\n\n return Response.json({\n presignedUrl,\n partNumber,\n uploadId,\n bucket,\n expiresIn,\n });\n });\n}\n","import {\n CompleteMultipartUploadCommand,\n HeadObjectCommand,\n ListPartsCommand,\n} from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\nimport { buildPublicUrl, parseFileName } from \"@/helpers\";\nimport { resolveObjectAcl } from \"@/helpers/server\";\n\ntype PartEntry = {\n partNumber: number;\n};\n\ntype Payload = {\n key: string;\n uploadId: string;\n bucket?: string;\n parts: PartEntry[];\n};\n\nexport function createMultipartCompleteHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const parts = (Array.isArray(body.parts) ? body.parts : [])\n .map(({ partNumber }) => Number(partNumber))\n .filter((n) => Number.isInteger(n) && n > 0)\n .sort((a, b) => a - b);\n\n if (!parts.length) {\n return Response.json(\n { message: \"At least one valid part is required\" },\n { status: 400 },\n );\n }\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.multipart?.completeGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n // Fetch authoritative ETags from S3 — this works regardless of whether\n // the client's CORS config exposes the ETag header.\n const listed = await config.s3.send(\n new ListPartsCommand({ Bucket: bucket, Key: key, UploadId: uploadId }),\n );\n const listedParts = listed.Parts ?? [];\n\n const completeParts = parts.map((partNumber) => {\n const found = listedParts.find((p) => p.PartNumber === partNumber);\n return { PartNumber: partNumber, ETag: found?.ETag ?? \"\" };\n });\n\n const completeResult = await config.s3.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: { Parts: completeParts },\n }),\n );\n\n // Some S3-compatible providers finalize multipart uploads asynchronously,\n // so HeadObject may transiently return stale or empty metadata right after\n // CompleteMultipartUpload. Retry until contentLength is non-zero.\n let head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n for (let attempt = 0; attempt < 4 && !head.ContentLength; attempt++) {\n await new Promise((r) => setTimeout(r, 250 * 2 ** attempt));\n head = await config.s3.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key }),\n );\n }\n const contentLength = head.ContentLength ?? 0;\n const contentType = head.ContentType;\n const eTag = (head.ETag ?? completeResult.ETag ?? \"\").replace(/\"/g, \"\");\n const metadata = head.Metadata ?? {};\n const versionId = head.VersionId;\n const lastModified = head.LastModified?.toISOString();\n\n const acl = await resolveObjectAcl(config.s3, bucket, key);\n const publicUrl = buildPublicUrl(config.resolvePublicUrl, bucket, key, acl);\n const fileName = parseFileName(head.ContentDisposition);\n\n await config.multipart?.onComplete?.({\n request,\n key,\n bucket,\n uploadId,\n contentLength,\n contentType,\n eTag,\n metadata,\n acl,\n fileName,\n publicUrl,\n versionId,\n lastModified,\n });\n\n return Response.json({\n bucket,\n key,\n uploadId,\n contentLength,\n contentType,\n eTag,\n metadata,\n acl,\n fileName,\n publicUrl,\n versionId,\n lastModified,\n });\n });\n}\n","import { AbortMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"@/types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"@/internal-helpers\";\n\ntype Payload = {\n key: string;\n uploadId: string;\n bucket?: string;\n};\n\nexport function createMultipartAbortHandler(config: S3HandlerConfig) {\n return withS3ErrorHandler(async (request: Request) => {\n const body = await parseBody<Payload>(request);\n if (!body) {\n return Response.json(\n { message: \"Invalid JSON payload\" },\n { status: 400 },\n );\n }\n\n const key = requireString(body.key, \"key\");\n if (key instanceof Response) return key;\n\n const uploadId = requireString(body.uploadId, \"uploadId\");\n if (uploadId instanceof Response) return uploadId;\n\n const bucket = body.bucket?.trim() || config.defaultBucket;\n\n const guardResult = await runHook(config.multipart?.abortGuard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n await config.s3.send(\n new AbortMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n }),\n );\n\n await config.multipart?.onAbort?.({\n request,\n key,\n bucket,\n uploadId,\n });\n\n return Response.json({ bucket, key, uploadId, aborted: true });\n });\n}\n","import type { S3HandlerConfig, S3Handlers } from \"./types\";\nimport { createUploadHandler } from \"./handlers/presign/upload\";\nimport { createConfirmHandler } from \"./handlers/presign/confirm\";\nimport { createDownloadHandler } from \"./handlers/presign/download\";\nimport { createDeleteHandler } from \"./handlers/delete\";\nimport { createMultipartInitHandler } from \"./handlers/multipart/init\";\nimport { createMultipartPartHandler } from \"./handlers/multipart/part\";\nimport { createMultipartCompleteHandler } from \"./handlers/multipart/complete\";\nimport { createMultipartAbortHandler } from \"./handlers/multipart/abort\";\n\nexport function createHandlers(config: S3HandlerConfig): S3Handlers {\n return {\n presign: {\n upload: createUploadHandler(config),\n confirm: createConfirmHandler(config),\n download: createDownloadHandler(config),\n },\n multipart: {\n init: createMultipartInitHandler(config),\n part: createMultipartPartHandler(config),\n complete: createMultipartCompleteHandler(config),\n abort: createMultipartAbortHandler(config),\n },\n delete: createDeleteHandler(config),\n };\n}\n","import type { S3RouteHandlerConfig } from \"./types\";\nimport { createHandlers } from \"./create-handlers\";\nimport { runHook } from \"./internal-helpers\";\n\nconst disabled = () => Response.json({ message: \"Not Found\" }, { status: 404 });\n\nexport function createRouter(config: S3RouteHandlerConfig) {\n const handlers = createHandlers(config);\n const base = config.basePath.replace(/\\/$/, \"\");\n\n return async (request: Request): Promise<Response> => {\n // Global guard — runs before every request\n const guardResult = await runHook(config.guard, { request });\n if (guardResult) return guardResult;\n\n const url = new URL(request.url);\n const subpath = url.pathname.slice(base.length).replace(/^\\//, \"\");\n const method = request.method;\n\n if (method === \"POST\" && subpath === \"presign/upload\")\n return config.upload?.enabled\n ? handlers.presign.upload(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/upload/confirm\")\n return config.upload?.enabled\n ? handlers.presign.confirm(request)\n : disabled();\n if (method === \"GET\" && subpath === \"presign/download\")\n return config.download?.enabled\n ? handlers.presign.download(request)\n : disabled();\n if (method === \"DELETE\" && subpath === \"delete\")\n return config.delete?.enabled ? handlers.delete(request) : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/init\")\n return config.multipart?.enabled\n ? handlers.multipart.init(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/part\")\n return config.multipart?.enabled\n ? handlers.multipart.part(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/complete\")\n return config.multipart?.enabled\n ? handlers.multipart.complete(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/abort\")\n return config.multipart?.enabled\n ? handlers.multipart.abort(request)\n : disabled();\n\n return Response.json({ message: \"Not Found\" }, { status: 404 });\n };\n}\n","export type PresignResponse = {\n key: string;\n bucket: string;\n url: string;\n expiresIn: number;\n};\n\nexport type UploadPresignResponse = {\n key: string;\n bucket: string;\n url: string;\n expiresIn: number;\n method: \"post\" | \"put\";\n /** Present when `method` is `\"post\"`. Must be appended to FormData before the file. */\n fields?: Record<string, string>;\n /** Present when `method` is `\"put\"`. Must be set as request headers on the PUT request. */\n headers?: Record<string, string>;\n};\n\n/** @deprecated Use {@link UploadPresignResponse} instead. */\nexport type PresignedPostResponse = UploadPresignResponse;\n\nexport type MultipartInitResponse = {\n key: string;\n bucket: string;\n uploadId: string;\n};\n\nexport type MultipartPartResponse = {\n presignedUrl: string;\n partNumber: number;\n uploadId: string;\n bucket: string;\n expiresIn: number;\n};\n\nexport type UploadConfirmResponse = {\n key: string;\n bucket: string;\n contentType?: string;\n contentLength: number;\n eTag?: string;\n /** S3 object metadata (x-amz-meta-*). Always present; empty object when no metadata was stored. */\n metadata: Record<string, string>;\n acl: \"private\" | \"public-read\";\n /** Display file name parsed from the object's Content-Disposition header. */\n fileName?: string;\n /** Public URL of the object. Only present when `acl` is `public-read` and `publicUrlBase` is configured on the server. */\n publicUrl?: string;\n /** Object version ID. Only present when bucket versioning is enabled. */\n versionId?: string;\n /** Last-modified timestamp from HeadObject (ISO 8601 string). */\n lastModified?: string;\n};\n\nexport type S3Api = {\n upload: (payload: {\n key: string;\n contentType?: string;\n /**\n * Exact byte size of the file. When provided the presigned POST policy\n * locks `content-length-range` to `[fileSize, fileSize]` so S3 rejects\n * uploads of any other size at the storage layer.\n */\n fileSize?: number;\n metadata?: Record<string, string>;\n bucket?: string;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition` on the S3 object. */\n fileName?: string;\n }) => Promise<UploadPresignResponse>;\n confirm: (payload: {\n key: string;\n bucket?: string;\n }) => Promise<UploadConfirmResponse>;\n download: (\n key: string,\n options?: { fileName?: string; bucket?: string },\n ) => Promise<PresignResponse>;\n delete: (\n key: string,\n options?: { bucket?: string },\n ) => Promise<{ success: boolean; bucket: string; key: string }>;\n multipart: {\n init: (payload: {\n key: string;\n contentType?: string;\n /** Declared total byte size of the file. Used for quota/guard checks and stored in `onInit` context. */\n fileSize?: number;\n metadata?: Record<string, string>;\n bucket?: string;\n acl?: \"private\" | \"public-read\";\n /** Original file name. Stored as `Content-Disposition` on the S3 object. */\n fileName?: string;\n }) => Promise<MultipartInitResponse>;\n signPart: (payload: {\n key: string;\n uploadId: string;\n partNumber: number;\n bucket?: string;\n }) => Promise<MultipartPartResponse>;\n complete: (payload: {\n key: string;\n uploadId: string;\n parts: Array<{ partNumber: number }>;\n bucket?: string;\n }) => Promise<{\n key: string;\n bucket: string;\n uploadId: string;\n contentLength: number;\n contentType?: string;\n eTag?: string;\n /** S3 object metadata (x-amz-meta-*). Always present; empty object when no metadata was stored. */\n metadata: Record<string, string>;\n /** Object version ID. Only present when bucket versioning is enabled. */\n versionId?: string;\n /** Last-modified timestamp from HeadObject (ISO 8601 string). */\n lastModified?: string;\n }>;\n abort: (payload: {\n key: string;\n uploadId: string;\n bucket?: string;\n }) => Promise<{ aborted: boolean }>;\n };\n};\n\nexport function createS3Api(basePath = \"/api/s3\"): S3Api {\n const base = basePath.replace(/\\/$/, \"\");\n\n const json = async <T>(url: string, init?: RequestInit): Promise<T> => {\n const res = await fetch(url, init);\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error((body as { message?: string }).message ?? res.statusText);\n }\n return res.json() as Promise<T>;\n };\n\n const post = <T>(url: string, body: unknown): Promise<T> =>\n json<T>(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n return {\n upload(payload) {\n return post<UploadPresignResponse>(`${base}/presign/upload`, payload);\n },\n\n confirm(payload) {\n return post<UploadConfirmResponse>(\n `${base}/presign/upload/confirm`,\n payload,\n );\n },\n\n download(key, options?) {\n const params = new URLSearchParams({ key });\n if (options?.fileName) {\n const safe = options.fileName.replace(/[\"\\\\\\r\\n]/g, \"_\");\n params.set(\"fileName\", safe);\n }\n if (options?.bucket) params.set(\"bucket\", options.bucket);\n return json<PresignResponse>(`${base}/presign/download?${params}`);\n },\n\n delete(key, options?) {\n const params = new URLSearchParams({ key });\n if (options?.bucket) params.set(\"bucket\", options.bucket);\n return json<{ success: boolean; bucket: string; key: string }>(\n `${base}/delete?${params}`,\n { method: \"DELETE\" },\n );\n },\n\n multipart: {\n init(payload) {\n return post<MultipartInitResponse>(\n `${base}/presign/multipart/init`,\n payload,\n );\n },\n\n signPart(payload) {\n return post<MultipartPartResponse>(\n `${base}/presign/multipart/part`,\n payload,\n );\n },\n\n complete(payload) {\n return post<{\n key: string;\n bucket: string;\n uploadId: string;\n contentLength: number;\n contentType?: string;\n eTag?: string;\n metadata: Record<string, string>;\n }>(`${base}/presign/multipart/complete`, payload);\n },\n\n abort(payload) {\n return post<{ aborted: boolean }>(\n `${base}/presign/multipart/abort`,\n payload,\n );\n },\n },\n };\n}\n","export function validateFile(\n file: File,\n options: { accept?: string[]; maxFileSize?: number },\n): string | null {\n if (options.accept?.length) {\n const allowed = options.accept.some((type) => {\n // Extension check: \".pdf\", \".jpg\", etc.\n if (type.startsWith(\".\")) {\n return file.name.toLowerCase().endsWith(type.toLowerCase());\n }\n // Wildcard MIME: \"image/*\"\n if (type.endsWith(\"/*\")) {\n return file.type.startsWith(type.replace(\"/*\", \"/\"));\n }\n // Exact MIME: \"application/pdf\"\n return file.type === type;\n });\n if (!allowed) {\n const ext = file.name.includes(\".\") ? file.name.split(\".\").pop() : null;\n return `File type \"${ext ? `.${ext}` : file.type || \"unknown\"}\" is not allowed`;\n }\n }\n\n if (file.size === 0) {\n return \"File is empty\";\n }\n\n if (options.maxFileSize && file.size > options.maxFileSize) {\n const maxMB = (options.maxFileSize / (1024 * 1024)).toFixed(1);\n return `File size exceeds ${maxMB} MB limit`;\n }\n\n return null;\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -2,11 +2,13 @@ import type { S3Client } from "@aws-sdk/client-s3";
2
2
  export type HookContext = {
3
3
  request: Request;
4
4
  };
5
+ /** Values are **client-declared** — not verified by S3. */
5
6
  export type UploadHookContext = HookContext & {
6
7
  key: string;
7
8
  bucket: string;
9
+ /** Client-declared MIME type. */
8
10
  contentType?: string;
9
- /** Declared byte size of the file the client intends to upload. */
11
+ /** Client-declared file size in bytes. Can be spoofed use for pre-checks only. */
10
12
  fileSize?: number;
11
13
  metadata?: Record<string, string>;
12
14
  acl?: "private" | "public-read";
@@ -15,11 +17,15 @@ export type UploadSuccessContext = UploadHookContext & {
15
17
  url: string;
16
18
  expiresIn: number;
17
19
  };
20
+ /** `contentLength` and `eTag` are **verified by S3** via `HeadObject`. */
18
21
  export type UploadCompleteContext = HookContext & {
19
22
  key: string;
20
23
  bucket: string;
24
+ /** MIME type from HeadObject. */
21
25
  contentType?: string;
26
+ /** Verified file size in bytes from HeadObject. */
22
27
  contentLength: number;
28
+ /** ETag from HeadObject. */
23
29
  eTag?: string;
24
30
  metadata?: Record<string, string>;
25
31
  acl: "private" | "public-read";
@@ -27,6 +33,10 @@ export type UploadCompleteContext = HookContext & {
27
33
  fileName?: string;
28
34
  /** Public URL of the object. Only present when `acl` is `public-read` and `publicUrlBase` is configured. */
29
35
  publicUrl?: string;
36
+ /** Object version ID. Only present when bucket versioning is enabled. */
37
+ versionId?: string;
38
+ /** Last-modified timestamp from HeadObject (ISO 8601 string). */
39
+ lastModified?: string;
30
40
  };
31
41
  export type DownloadHookContext = HookContext & {
32
42
  key: string;
@@ -63,77 +73,94 @@ export type MultipartCompleteSuccessContext = MultipartHookContext & {
63
73
  contentLength: number;
64
74
  contentType?: string;
65
75
  eTag?: string;
76
+ metadata: Record<string, string>;
66
77
  acl: "private" | "public-read";
67
78
  /** Display file name parsed from the object's Content-Disposition header. */
68
79
  fileName?: string;
69
80
  /** Public URL of the object. Only present when `acl` is `public-read` and `publicUrlBase` is configured. */
70
81
  publicUrl?: string;
82
+ /** Object version ID. Only present when bucket versioning is enabled. */
83
+ versionId?: string;
84
+ /** Last-modified timestamp from HeadObject (ISO 8601 string). */
85
+ lastModified?: string;
71
86
  };
72
87
  export type S3HandlerConfig = {
73
88
  /** The S3 client instance used to communicate with your bucket. */
74
89
  s3: S3Client;
75
90
  /** Default bucket used when no `bucket` is specified in the request payload. */
76
91
  defaultBucket: string;
77
- /**
78
- * Maximum file size in bytes. Requests declaring a larger `fileSize` are
79
- * rejected before any presigned URL is generated.
80
- */
81
- maxFileSize?: number;
82
92
  /**
83
93
  * Resolves the public base URL for a bucket. The returned string is combined
84
94
  * with the object key to produce `publicUrl` on upload/multipart responses
85
- * when the object ACL is `public-read`. Omit if all objects are private.
95
+ * when the object ACL is `public-read`. Return `undefined` for unknown buckets
96
+ * or omit entirely if all objects are private.
86
97
  *
87
98
  * ```ts
88
- * // Single bucket (virtual-hosted style)
89
- * resolvePublicUrl: (bucket) => `https://${bucket}.s3.eu-west-1.amazonaws.com`
90
- *
91
- * // Single bucket (Cloudflare R2 / fixed domain)
92
- * resolvePublicUrl: (_bucket) => `https://pub-xxxxx.r2.dev`
99
+ * // Single bucket (path-style or virtual-hosted)
100
+ * resolvePublicUrl: (_bucket) => "https://cdn.example.com"
93
101
  *
94
102
  * // Multiple buckets — each mapped to its own domain
95
103
  * resolvePublicUrl: (bucket) =>
96
- * ({ avatars: "https://cdn.myapp.com", media: "https://media.myapp.com" })[bucket]
97
- * ?? `https://${bucket}.s3.amazonaws.com`
104
+ * ({ avatars: "https://cdn.example.com", media: "https://media.example.com" })[bucket]
98
105
  * ```
99
106
  */
100
- resolvePublicUrl?: (bucket: string) => string;
101
- /** Runs before every request. Throw to reject (`403` by default). */
107
+ resolvePublicUrl?: (bucket: string) => string | undefined;
108
+ /**
109
+ * Runs before every request. Return normally to allow, throw to reject.
110
+ * Attach `status` to the thrown error to set the HTTP response code (default `403`).
111
+ */
102
112
  guard?: (context: HookContext) => Promise<void> | void;
103
113
  upload?: {
104
114
  /** Enable the upload endpoints. @default false */
105
115
  enabled?: boolean;
106
- /** Runs before the presigned URL is generated. Throw to reject (`403` by default). */
107
- guard?: (context: UploadHookContext) => Promise<void> | void;
108
- /** Fires after the presigned URL is issued (file not yet uploaded). */
116
+ /**
117
+ * Presign method for simple (non-multipart) uploads.
118
+ * Use `"put"` for S3-compatible providers that do not support presigned POST (e.g. Cloudflare R2).
119
+ * @default "post"
120
+ */
121
+ method?: "post" | "put";
122
+ /** Runs before presigned URL generation. Values are **client-declared**. */
123
+ presignGuard?: (context: UploadHookContext) => Promise<void> | void;
124
+ /** Fires after presigned URL is issued. File not yet uploaded. */
109
125
  onPresigned?: (context: UploadSuccessContext) => Promise<void> | void;
110
- /** Fires after upload confirmation. `contentLength` and `eTag` are verified S3 values. */
111
- onUploaded?: (context: UploadCompleteContext) => Promise<void> | void;
126
+ /** Runs before upload confirmation. */
127
+ confirmGuard?: (context: HookContext & {
128
+ key: string;
129
+ bucket: string;
130
+ }) => Promise<void> | void;
131
+ /** Fires after upload confirmation. Values are **verified by S3** via `HeadObject`. */
132
+ onUploadConfirmed?: (context: UploadCompleteContext) => Promise<void> | void;
112
133
  };
113
134
  download?: {
114
135
  /** Enable the download endpoint. @default false */
115
136
  enabled?: boolean;
116
- /** Runs before the presigned download URL is generated. Throw to reject (`403` by default). */
117
- guard?: (context: DownloadHookContext) => Promise<void> | void;
118
- /** Fires after the presigned download URL is issued. */
137
+ /** Runs before presigned GET URL generation. Values are **client-declared**. */
138
+ presignGuard?: (context: DownloadHookContext) => Promise<void> | void;
139
+ /** Fires after presigned GET URL is issued. */
119
140
  onPresigned?: (context: DownloadSuccessContext) => Promise<void> | void;
120
141
  };
121
142
  delete?: {
122
143
  /** Enable the delete endpoint. @default false */
123
144
  enabled?: boolean;
124
- /** Runs before the object is deleted. Throw to reject (`403` by default). */
125
- guard?: (context: DeleteHookContext) => Promise<void> | void;
126
- /** Fires after the object is successfully deleted. */
145
+ /** Runs before object deletion. Values are **client-declared**. */
146
+ deleteGuard?: (context: DeleteHookContext) => Promise<void> | void;
147
+ /** Fires after object is successfully deleted. */
127
148
  onDeleted?: (context: DeleteHookContext) => Promise<void> | void;
128
149
  };
129
150
  multipart?: {
130
151
  /** Enable all multipart upload endpoints. @default false */
131
152
  enabled?: boolean;
132
- /** Runs before every multipart operation. Throw to reject (`403` by default). */
133
- guard?: (context: MultipartHookContext) => Promise<void> | void;
153
+ /** Runs before `CreateMultipartUpload`. Values are **client-declared**. */
154
+ initGuard?: (context: MultipartHookContext) => Promise<void> | void;
155
+ /** Runs before `UploadPart` presign. */
156
+ partGuard?: (context: MultipartHookContext) => Promise<void> | void;
157
+ /** Runs before `CompleteMultipartUpload`. */
158
+ completeGuard?: (context: MultipartHookContext) => Promise<void> | void;
159
+ /** Runs before `AbortMultipartUpload`. */
160
+ abortGuard?: (context: MultipartHookContext) => Promise<void> | void;
134
161
  /** Fires after `CreateMultipartUpload`. */
135
162
  onInit?: (context: MultipartInitSuccessContext) => Promise<void> | void;
136
- /** Fires after `CompleteMultipartUpload`. `contentLength` and `eTag` are verified S3 values. */
163
+ /** Fires after `CompleteMultipartUpload`. Values are **verified by S3** via `HeadObject`. */
137
164
  onComplete?: (context: MultipartCompleteSuccessContext) => Promise<void> | void;
138
165
  /** Fires after `AbortMultipartUpload`. */
139
166
  onAbort?: (context: MultipartHookContext & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-s3/server",
3
- "version": "3.2.1",
3
+ "version": "3.1044.0",
4
4
  "description": "Framework-agnostic S3 server handlers — presigned uploads, downloads, deletes, and multipart operations",
5
5
  "keywords": [
6
6
  "s3",
@@ -46,16 +46,15 @@
46
46
  "dist"
47
47
  ],
48
48
  "dependencies": {
49
- "@aws-sdk/s3-presigned-post": "^3.1041.0",
50
- "@aws-sdk/s3-request-presigner": "^3.1041.0"
49
+ "@aws-sdk/s3-presigned-post": "^3.1044.0",
50
+ "@aws-sdk/s3-request-presigner": "^3.1044.0"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@aws-sdk/client-s3": ">=3"
54
54
  },
55
55
  "devDependencies": {
56
- "@aws-sdk/client-s3": "^3.1041.0",
57
- "@aws-sdk/s3-presigned-post": "^3.1032.0",
58
- "@aws-sdk/s3-request-presigner": "^3.1029.0",
56
+ "@aws-sdk/client-s3": "^3.1044.0",
57
+ "tsc-alias": "^1.8.17",
59
58
  "tsup": "^8.5.1",
60
59
  "typescript": "^6.0.3"
61
60
  },
@@ -63,7 +62,7 @@
63
62
  "access": "public"
64
63
  },
65
64
  "scripts": {
66
- "build": "tsup --config tsup.config.ts && tsc --emitDeclarationOnly",
65
+ "build": "tsup --config tsup.config.ts && tsc --emitDeclarationOnly && tsc-alias",
67
66
  "check-types": "tsc --noEmit"
68
67
  }
69
68
  }