@better-s3/server 2.1.0 → 2.3.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
@@ -57,17 +57,31 @@ createRouteHandler({
57
57
  if (!session) throw new Error("Unauthorized");
58
58
  },
59
59
  upload: {
60
- onSuccess: async ({ request, key, contentType }) => { /* ... */ },
61
- onComplete: async ({ key, contentType, contentLength, eTag }) => { /* ... */ },
60
+ onSuccess: async ({ request, key, contentType }) => {
61
+ /* ... */
62
+ },
63
+ onComplete: async ({ key, contentType, contentLength, eTag }) => {
64
+ /* ... */
65
+ },
62
66
  },
63
67
  delete: {
64
- guard: async ({ request, key }) => { /* ownership check */ },
65
- onSuccess: async ({ key }) => { /* remove DB record */ },
68
+ guard: async ({ request, key }) => {
69
+ /* ownership check */
70
+ },
71
+ onSuccess: async ({ key }) => {
72
+ /* remove DB record */
73
+ },
66
74
  },
67
75
  multipart: {
68
- onInit: async ({ key, uploadId }) => { /* ... */ },
69
- onComplete: async ({ key, uploadId }) => { /* ... */ },
70
- onAbort: async ({ key, uploadId }) => { /* ... */ },
76
+ onInit: async ({ key, uploadId }) => {
77
+ /* ... */
78
+ },
79
+ onComplete: async ({ key, uploadId }) => {
80
+ /* ... */
81
+ },
82
+ onAbort: async ({ key, uploadId }) => {
83
+ /* ... */
84
+ },
71
85
  },
72
86
  },
73
87
  });
@@ -77,27 +91,30 @@ createRouteHandler({
77
91
 
78
92
  ## API Routes
79
93
 
80
- | Method | Path | Description |
81
- | --- | --- | --- |
82
- | `POST` | `/presign/upload` | Presigned upload URL |
83
- | `POST` | `/presign/upload/confirm` | Confirm upload |
84
- | `GET` | `/presign/download` | Presigned download URL |
85
- | `DELETE` | `/delete` | Delete object |
86
- | `POST` | `/presign/multipart/init` | Init multipart |
87
- | `POST` | `/presign/multipart/part` | Sign part |
88
- | `POST` | `/presign/multipart/complete` | Complete multipart |
89
- | `POST` | `/presign/multipart/abort` | Abort multipart |
94
+ | Method | Path | Description |
95
+ | -------- | ----------------------------- | ---------------------- |
96
+ | `POST` | `/presign/upload` | Presigned upload URL |
97
+ | `POST` | `/presign/upload/confirm` | Confirm upload |
98
+ | `GET` | `/presign/download` | Presigned download URL |
99
+ | `DELETE` | `/delete` | Delete object |
100
+ | `POST` | `/presign/multipart/init` | Init multipart |
101
+ | `POST` | `/presign/multipart/part` | Sign part |
102
+ | `POST` | `/presign/multipart/complete` | Complete multipart |
103
+ | `POST` | `/presign/multipart/abort` | Abort multipart |
90
104
 
91
- ## Presign API Client
105
+ ## S3 API Client
92
106
 
93
107
  Client-side helper for calling the server endpoints (used internally by `@better-s3/react`):
94
108
 
95
109
  ```ts
96
- import { createPresignApi } from "@better-s3/server";
110
+ import { createS3Api } from "@better-s3/server";
97
111
 
98
- const api = createPresignApi("/api/s3");
112
+ const api = createS3Api("/api/s3");
99
113
 
100
- const { url } = await api.upload({ key: "photo.jpg", contentType: "image/jpeg" });
114
+ const { url } = await api.upload({
115
+ key: "photo.jpg",
116
+ contentType: "image/jpeg",
117
+ });
101
118
  const { url: downloadUrl } = await api.download("photo.jpg");
102
119
  await api.delete("photo.jpg");
103
120
  ```
package/dist/index.d.ts CHANGED
@@ -8,7 +8,8 @@ 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 { createPresignApi, type PresignApi, type PresignResponse, type MultipartInitResponse, type MultipartPartResponse, type UploadConfirmResponse, } from "./presign-api";
11
+ export { createS3Api, type S3Api, type PresignResponse, type MultipartInitResponse, type MultipartPartResponse, type UploadConfirmResponse, } from "./presign-api";
12
+ export { createS3Api as createPresignApi, type S3Api as PresignApi, } from "./presign-api";
12
13
  export { validateFile } from "./validate";
13
14
  export type { S3HandlerConfig, S3RouteHandlerConfig, S3Handler, S3Handlers, S3ServerHooks, HookContext, UploadHookContext, UploadSuccessContext, UploadCompleteContext, DownloadHookContext, DownloadSuccessContext, DeleteHookContext, MultipartHookContext, MultipartInitSuccessContext, MultipartCompleteSuccessContext, } from "./types";
14
15
  export { parseBody, requireString, normalizeExpiresIn, withS3ErrorHandler, runHook, } from "./helpers";
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import {PutObjectCommand,HeadObjectCommand,GetObjectCommand,DeleteObjectCommand,CreateMultipartUploadCommand,UploadPartCommand,CompleteMultipartUploadCommand,AbortMultipartUploadCommand}from'@aws-sdk/client-s3';import {getSignedUrl}from'@aws-sdk/s3-request-presigner';async function d(t){try{let r=await t.json();return r&&typeof r=="object"?r:null}catch{return null}}function l(t,r){let n=typeof t=="string"?t.trim():"";return n||Response.json({message:`${r} is required`},{status:400})}function c(t){let r=Number(t);return Number.isFinite(r)&&r>0?Math.floor(r):600}function u(t){return async r=>{try{return await t(r)}catch(n){let e=n instanceof Error?n.message:"Internal server error";return console.error("[S3 API]",e),Response.json({message:e},{status:500})}}}async function p(t,r){if(!t)return null;try{return await t(r),null}catch(n){let e=n instanceof Error?n.message:"Forbidden",o=typeof n?.status=="number"?n.status:403;return Response.json({message:e},{status:o})}}function k(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=n.bucket?.trim()||t.defaultBucket,s=c(n.expiresIn),a=await p(t.hooks?.upload?.guard,{request:r,key:e,bucket:o,contentType:n.contentType,metadata:n.metadata});if(a)return a;let i=await getSignedUrl(t.s3,new PutObjectCommand({Bucket:o,Key:e,ContentType:n.contentType,Metadata:n.metadata}),{expiresIn:s});return await t.hooks?.upload?.onSuccess?.({request:r,key:e,bucket:o,contentType:n.contentType,metadata:n.metadata,url:i,expiresIn:s}),Response.json({bucket:o,key:e,url:i,expiresIn:s})})}function b(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=n.bucket?.trim()||t.defaultBucket,s=await p(t.hooks?.upload?.guard,{request:r,key:e,bucket:o});if(s)return s;let a=await t.s3.send(new HeadObjectCommand({Bucket:o,Key:e})),i={request:r,key:e,bucket:o,contentType:a.ContentType,contentLength:a.ContentLength??0,eTag:a.ETag?.replace(/"/g,""),metadata:a.Metadata};return await t.hooks?.upload?.onComplete?.(i),Response.json({key:e,bucket:o,contentType:i.contentType,contentLength:i.contentLength,eTag:i.eTag})})}function R(t){return u(async r=>{let{searchParams:n}=new URL(r.url),e=n.get("key")?.trim();if(!e)return Response.json({message:"key query parameter is required"},{status:400});let o=n.get("bucket")?.trim()||t.defaultBucket,s=c(n.get("expiresIn")),a=n.get("fileName")?.trim(),i=await p(t.hooks?.download?.guard,{request:r,key:e,bucket:o,fileName:a||void 0});if(i)return i;let m=await getSignedUrl(t.s3,new GetObjectCommand({Bucket:o,Key:e,ResponseContentDisposition:`attachment${a?`; filename="${a}"`:""}`}),{expiresIn:s});return await t.hooks?.download?.onSuccess?.({request:r,key:e,bucket:o,fileName:a||void 0,url:m,expiresIn:s}),Response.json({bucket:o,key:e,url:m,expiresIn:s})})}function w(t){return u(async r=>{let{searchParams:n}=new URL(r.url),e=n.get("key")?.trim();if(!e)return Response.json({message:"key query parameter is required"},{status:400});let o=n.get("bucket")?.trim()||t.defaultBucket,s=await p(t.hooks?.delete?.guard,{request:r,key:e,bucket:o});if(s)return s;try{await t.s3.send(new HeadObjectCommand({Bucket:o,Key:e}));}catch{return Response.json({message:`Object "${e}" not found`},{status:404})}return await t.s3.send(new DeleteObjectCommand({Bucket:o,Key:e})),await t.hooks?.delete?.onSuccess?.({request:r,key:e,bucket:o}),Response.json({success:!0,bucket:o,key:e})})}function H(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=n.bucket?.trim()||t.defaultBucket,s=await p(t.hooks?.multipart?.guard,{request:r,key:e,bucket:o});if(s)return s;let{UploadId:a}=await t.s3.send(new CreateMultipartUploadCommand({Bucket:o,Key:e,ContentType:n.contentType,Metadata:n.metadata}));return await t.hooks?.multipart?.onInit?.({request:r,key:e,bucket:o,uploadId:a,contentType:n.contentType,metadata:n.metadata}),Response.json({bucket:o,key:e,uploadId:a},{status:201})})}function S(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=l(n.uploadId,"uploadId");if(o instanceof Response)return o;let s=Number(n.partNumber);if(!Number.isInteger(s)||s<=0)return Response.json({message:"partNumber must be a positive integer"},{status:400});let a=n.bucket?.trim()||t.defaultBucket,i=c(n.expiresIn),m=await p(t.hooks?.multipart?.guard,{request:r,key:e,bucket:a});if(m)return m;let y=await getSignedUrl(t.s3,new UploadPartCommand({Bucket:a,Key:e,UploadId:o,PartNumber:s}),{expiresIn:i});return Response.json({presignedUrl:y,partNumber:s,uploadId:o,bucket:a,expiresIn:i})})}function x(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=l(n.uploadId,"uploadId");if(o instanceof Response)return o;let s=(Array.isArray(n.parts)?n.parts:[]).map(({partNumber:g,eTag:f})=>({PartNumber:Number(g),ETag:String(f)})).filter(g=>Number.isInteger(g.PartNumber)&&g.ETag).sort((g,f)=>g.PartNumber-f.PartNumber);if(!s.length)return Response.json({message:"At least one valid part is required"},{status:400});let a=n.bucket?.trim()||t.defaultBucket,i=await p(t.hooks?.multipart?.guard,{request:r,key:e,bucket:a});if(i)return i;let{Location:m,ETag:y}=await t.s3.send(new CompleteMultipartUploadCommand({Bucket:a,Key:e,UploadId:o,MultipartUpload:{Parts:s}}));return await t.hooks?.multipart?.onComplete?.({request:r,key:e,bucket:a,uploadId:o}),Response.json({bucket:a,key:e,uploadId:o,location:m,eTag:y})})}function P(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=l(n.uploadId,"uploadId");if(o instanceof Response)return o;let s=n.bucket?.trim()||t.defaultBucket,a=await p(t.hooks?.multipart?.guard,{request:r,key:e,bucket:s});return a||(await t.s3.send(new AbortMultipartUploadCommand({Bucket:s,Key:e,UploadId:o})),await t.hooks?.multipart?.onAbort?.({request:r,key:e,bucket:s,uploadId:o}),Response.json({bucket:s,key:e,uploadId:o,aborted:!0}))})}function C(t){return {presign:{upload:k(t),confirm:b(t),download:R(t)},multipart:{init:H(t),part:S(t),complete:x(t),abort:P(t)},delete:w(t)}}function L(t){let r=C(t),n=t.basePath.replace(/\/$/,"");return async e=>{let o=await p(t.hooks?.guard,{request:e});if(o)return o;let a=new URL(e.url).pathname.slice(n.length).replace(/^\//,""),i=e.method;return i==="POST"&&a==="presign/upload"?r.presign.upload(e):i==="POST"&&a==="presign/upload/confirm"?r.presign.confirm(e):i==="GET"&&a==="presign/download"?r.presign.download(e):i==="DELETE"&&a==="delete"?r.delete(e):i==="POST"&&a==="presign/multipart/init"?r.multipart.init(e):i==="POST"&&a==="presign/multipart/part"?r.multipart.part(e):i==="POST"&&a==="presign/multipart/complete"?r.multipart.complete(e):i==="POST"&&a==="presign/multipart/abort"?r.multipart.abort(e):Response.json({message:"Not Found"},{status:404})}}function F(t="/api/s3"){let r=t.replace(/\/$/,""),n=async(o,s)=>{let a=await fetch(o,s);if(!a.ok){let i=await a.json().catch(()=>({}));throw new Error(i.message??a.statusText)}return a.json()},e=(o,s)=>n(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});return {upload(o){return e(`${r}/presign/upload`,o)},confirm(o){return e(`${r}/presign/upload/confirm`,o)},download(o,s){let a=new URLSearchParams({key:o});if(s?.fileName){let i=s.fileName.replace(/["\\\r\n]/g,"_");a.set("fileName",i);}return s?.bucket&&a.set("bucket",s.bucket),n(`${r}/presign/download?${a}`)},delete(o,s){let a=new URLSearchParams({key:o});return s?.bucket&&a.set("bucket",s.bucket),n(`${r}/delete?${a}`,{method:"DELETE"})},multipart:{init(o){return e(`${r}/presign/multipart/init`,o)},signPart(o){return e(`${r}/presign/multipart/part`,o)},complete(o){return e(`${r}/presign/multipart/complete`,o)},abort(o){return e(`${r}/presign/multipart/abort`,o)}}}}function v(t,r){if(r.accept?.length&&!r.accept.some(e=>e.startsWith(".")?t.name.toLowerCase().endsWith(e.toLowerCase()):e.endsWith("/*")?t.type.startsWith(e.replace("/*","/")):t.type===e)){let e=t.name.includes(".")?t.name.split(".").pop():null;return `File type "${e?`.${e}`:t.type||"unknown"}" is not allowed`}return t.size===0?"File is empty":r.maxFileSize&&t.size>r.maxFileSize?`File size exceeds ${(r.maxFileSize/1048576).toFixed(1)} MB limit`:null}
2
- export{b as createConfirmHandler,w as createDeleteHandler,R as createDownloadHandler,C as createHandlers,P as createMultipartAbortHandler,x as createMultipartCompleteHandler,H as createMultipartInitHandler,S as createMultipartPartHandler,F as createPresignApi,L as createRouter,k as createUploadHandler,c as normalizeExpiresIn,d as parseBody,l as requireString,p as runHook,v as validateFile,u as withS3ErrorHandler};//# sourceMappingURL=index.js.map
1
+ import {PutObjectCommand,HeadObjectCommand,GetObjectCommand,DeleteObjectCommand,CreateMultipartUploadCommand,UploadPartCommand,CompleteMultipartUploadCommand,AbortMultipartUploadCommand}from'@aws-sdk/client-s3';import {getSignedUrl}from'@aws-sdk/s3-request-presigner';async function d(t){try{let r=await t.json();return r&&typeof r=="object"?r:null}catch{return null}}function l(t,r){let n=typeof t=="string"?t.trim():"";return n||Response.json({message:`${r} is required`},{status:400})}function c(t){let r=Number(t);return Number.isFinite(r)&&r>0?Math.floor(r):600}function u(t){return async r=>{try{return await t(r)}catch(n){let e=n instanceof Error?n.message:"Internal server error";return console.error("[S3 API]",e),Response.json({message:e},{status:500})}}}async function p(t,r){if(!t)return null;try{return await t(r),null}catch(n){let e=n instanceof Error?n.message:"Forbidden",o=typeof n?.status=="number"?n.status:403;return Response.json({message:e},{status:o})}}function k(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=n.bucket?.trim()||t.defaultBucket,s=c(n.expiresIn),a=await p(t.hooks?.upload?.guard,{request:r,key:e,bucket:o,contentType:n.contentType,metadata:n.metadata});if(a)return a;let i=await getSignedUrl(t.s3,new PutObjectCommand({Bucket:o,Key:e,ContentType:n.contentType,Metadata:n.metadata}),{expiresIn:s});return await t.hooks?.upload?.onSuccess?.({request:r,key:e,bucket:o,contentType:n.contentType,metadata:n.metadata,url:i,expiresIn:s}),Response.json({bucket:o,key:e,url:i,expiresIn:s})})}function b(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=n.bucket?.trim()||t.defaultBucket,s=await p(t.hooks?.upload?.guard,{request:r,key:e,bucket:o});if(s)return s;let a=await t.s3.send(new HeadObjectCommand({Bucket:o,Key:e})),i={request:r,key:e,bucket:o,contentType:a.ContentType,contentLength:a.ContentLength??0,eTag:a.ETag?.replace(/"/g,""),metadata:a.Metadata};return await t.hooks?.upload?.onComplete?.(i),Response.json({key:e,bucket:o,contentType:i.contentType,contentLength:i.contentLength,eTag:i.eTag})})}function R(t){return u(async r=>{let{searchParams:n}=new URL(r.url),e=n.get("key")?.trim();if(!e)return Response.json({message:"key query parameter is required"},{status:400});let o=n.get("bucket")?.trim()||t.defaultBucket,s=c(n.get("expiresIn")),a=n.get("fileName")?.trim(),i=await p(t.hooks?.download?.guard,{request:r,key:e,bucket:o,fileName:a||void 0});if(i)return i;let m=await getSignedUrl(t.s3,new GetObjectCommand({Bucket:o,Key:e,ResponseContentDisposition:`attachment${a?`; filename="${a}"`:""}`}),{expiresIn:s});return await t.hooks?.download?.onSuccess?.({request:r,key:e,bucket:o,fileName:a||void 0,url:m,expiresIn:s}),Response.json({bucket:o,key:e,url:m,expiresIn:s})})}function w(t){return u(async r=>{let{searchParams:n}=new URL(r.url),e=n.get("key")?.trim();if(!e)return Response.json({message:"key query parameter is required"},{status:400});let o=n.get("bucket")?.trim()||t.defaultBucket,s=await p(t.hooks?.delete?.guard,{request:r,key:e,bucket:o});if(s)return s;try{await t.s3.send(new HeadObjectCommand({Bucket:o,Key:e}));}catch{return Response.json({message:`Object "${e}" not found`},{status:404})}return await t.s3.send(new DeleteObjectCommand({Bucket:o,Key:e})),await t.hooks?.delete?.onSuccess?.({request:r,key:e,bucket:o}),Response.json({success:!0,bucket:o,key:e})})}function S(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=n.bucket?.trim()||t.defaultBucket,s=await p(t.hooks?.multipart?.guard,{request:r,key:e,bucket:o});if(s)return s;let{UploadId:a}=await t.s3.send(new CreateMultipartUploadCommand({Bucket:o,Key:e,ContentType:n.contentType,Metadata:n.metadata}));return await t.hooks?.multipart?.onInit?.({request:r,key:e,bucket:o,uploadId:a,contentType:n.contentType,metadata:n.metadata}),Response.json({bucket:o,key:e,uploadId:a},{status:201})})}function H(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=l(n.uploadId,"uploadId");if(o instanceof Response)return o;let s=Number(n.partNumber);if(!Number.isInteger(s)||s<=0)return Response.json({message:"partNumber must be a positive integer"},{status:400});let a=n.bucket?.trim()||t.defaultBucket,i=c(n.expiresIn),m=await p(t.hooks?.multipart?.guard,{request:r,key:e,bucket:a});if(m)return m;let y=await getSignedUrl(t.s3,new UploadPartCommand({Bucket:a,Key:e,UploadId:o,PartNumber:s}),{expiresIn:i});return Response.json({presignedUrl:y,partNumber:s,uploadId:o,bucket:a,expiresIn:i})})}function x(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=l(n.uploadId,"uploadId");if(o instanceof Response)return o;let s=(Array.isArray(n.parts)?n.parts:[]).map(({partNumber:g,eTag:f})=>({PartNumber:Number(g),ETag:String(f)})).filter(g=>Number.isInteger(g.PartNumber)&&g.ETag).sort((g,f)=>g.PartNumber-f.PartNumber);if(!s.length)return Response.json({message:"At least one valid part is required"},{status:400});let a=n.bucket?.trim()||t.defaultBucket,i=await p(t.hooks?.multipart?.guard,{request:r,key:e,bucket:a});if(i)return i;let{Location:m,ETag:y}=await t.s3.send(new CompleteMultipartUploadCommand({Bucket:a,Key:e,UploadId:o,MultipartUpload:{Parts:s}}));return await t.hooks?.multipart?.onComplete?.({request:r,key:e,bucket:a,uploadId:o}),Response.json({bucket:a,key:e,uploadId:o,location:m,eTag:y})})}function P(t){return u(async r=>{let n=await d(r);if(!n)return Response.json({message:"Invalid JSON payload"},{status:400});let e=l(n.key,"key");if(e instanceof Response)return e;let o=l(n.uploadId,"uploadId");if(o instanceof Response)return o;let s=n.bucket?.trim()||t.defaultBucket,a=await p(t.hooks?.multipart?.guard,{request:r,key:e,bucket:s});return a||(await t.s3.send(new AbortMultipartUploadCommand({Bucket:s,Key:e,UploadId:o})),await t.hooks?.multipart?.onAbort?.({request:r,key:e,bucket:s,uploadId:o}),Response.json({bucket:s,key:e,uploadId:o,aborted:!0}))})}function C(t){return {presign:{upload:k(t),confirm:b(t),download:R(t)},multipart:{init:S(t),part:H(t),complete:x(t),abort:P(t)},delete:w(t)}}function F(t){let r=C(t),n=t.basePath.replace(/\/$/,"");return async e=>{let o=await p(t.hooks?.guard,{request:e});if(o)return o;let a=new URL(e.url).pathname.slice(n.length).replace(/^\//,""),i=e.method;return i==="POST"&&a==="presign/upload"?r.presign.upload(e):i==="POST"&&a==="presign/upload/confirm"?r.presign.confirm(e):i==="GET"&&a==="presign/download"?r.presign.download(e):i==="DELETE"&&a==="delete"?r.delete(e):i==="POST"&&a==="presign/multipart/init"?r.multipart.init(e):i==="POST"&&a==="presign/multipart/part"?r.multipart.part(e):i==="POST"&&a==="presign/multipart/complete"?r.multipart.complete(e):i==="POST"&&a==="presign/multipart/abort"?r.multipart.abort(e):Response.json({message:"Not Found"},{status:404})}}function h(t="/api/s3"){let r=t.replace(/\/$/,""),n=async(o,s)=>{let a=await fetch(o,s);if(!a.ok){let i=await a.json().catch(()=>({}));throw new Error(i.message??a.statusText)}return a.json()},e=(o,s)=>n(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});return {upload(o){return e(`${r}/presign/upload`,o)},confirm(o){return e(`${r}/presign/upload/confirm`,o)},download(o,s){let a=new URLSearchParams({key:o});if(s?.fileName){let i=s.fileName.replace(/["\\\r\n]/g,"_");a.set("fileName",i);}return s?.bucket&&a.set("bucket",s.bucket),n(`${r}/presign/download?${a}`)},delete(o,s){let a=new URLSearchParams({key:o});return s?.bucket&&a.set("bucket",s.bucket),n(`${r}/delete?${a}`,{method:"DELETE"})},multipart:{init(o){return e(`${r}/presign/multipart/init`,o)},signPart(o){return e(`${r}/presign/multipart/part`,o)},complete(o){return e(`${r}/presign/multipart/complete`,o)},abort(o){return e(`${r}/presign/multipart/abort`,o)}}}}function v(t,r){if(r.accept?.length&&!r.accept.some(e=>e.startsWith(".")?t.name.toLowerCase().endsWith(e.toLowerCase()):e.endsWith("/*")?t.type.startsWith(e.replace("/*","/")):t.type===e)){let e=t.name.includes(".")?t.name.split(".").pop():null;return `File type "${e?`.${e}`:t.type||"unknown"}" is not allowed`}return t.size===0?"File is empty":r.maxFileSize&&t.size>r.maxFileSize?`File size exceeds ${(r.maxFileSize/1048576).toFixed(1)} MB limit`:null}
2
+ export{b as createConfirmHandler,w as createDeleteHandler,R as createDownloadHandler,C as createHandlers,P as createMultipartAbortHandler,x as createMultipartCompleteHandler,S as createMultipartInitHandler,H as createMultipartPartHandler,h as createPresignApi,F as createRouter,h as createS3Api,k as createUploadHandler,c as normalizeExpiresIn,d as parseBody,l as requireString,p as runHook,v as validateFile,u as withS3ErrorHandler};//# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/helpers.ts","../src/handlers/presign/upload.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/presign-api.ts","../src/validate.ts"],"names":["parseBody","request","body","requireString","value","name","trimmed","normalizeExpiresIn","n","withS3ErrorHandler","handler","err","message","runHook","hook","context","status","createUploadHandler","config","key","bucket","expiresIn","guardResult","url","getSignedUrl","PutObjectCommand","createConfirmHandler","head","HeadObjectCommand","createDownloadHandler","searchParams","fileName","GetObjectCommand","createDeleteHandler","DeleteObjectCommand","createMultipartInitHandler","UploadId","CreateMultipartUploadCommand","createMultipartPartHandler","uploadId","partNumber","presignedUrl","UploadPartCommand","createMultipartCompleteHandler","parts","eTag","p","a","b","Location","ETag","CompleteMultipartUploadCommand","createMultipartAbortHandler","AbortMultipartUploadCommand","createHandlers","createRouter","handlers","base","subpath","method","createPresignApi","basePath","json","init","res","post","payload","options","params","safe","validateFile","file","type","ext"],"mappings":"4QAAA,eAAsBA,CAAAA,CACpBC,CAAAA,CACmB,CACnB,GAAI,CACF,IAAMC,CAAAA,CAAO,MAAMD,CAAAA,CAAQ,IAAA,EAAK,CAChC,OAAOC,CAAAA,EAAQ,OAAOA,CAAAA,EAAS,QAAA,CAAYA,CAAAA,CAAa,IAC1D,CAAA,KAAQ,CACN,OAAO,IACT,CACF,CAEO,SAASC,CAAAA,CAAcC,CAAAA,CAAgBC,CAAAA,CAAiC,CAC7E,IAAMC,CAAAA,CAAU,OAAOF,CAAAA,EAAU,QAAA,CAAWA,CAAAA,CAAM,MAAK,CAAI,EAAA,CAC3D,OAAKE,CAAAA,EACI,QAAA,CAAS,IAAA,CAAK,CAAE,OAAA,CAAS,CAAA,EAAGD,CAAI,CAAA,YAAA,CAAe,CAAA,CAAG,CAAE,MAAA,CAAQ,GAAI,CAAC,CAG5E,CAEO,SAASE,CAAAA,CAAmBH,CAAAA,CAAwB,CACzD,IAAMI,CAAAA,CAAI,MAAA,CAAOJ,CAAK,CAAA,CACtB,OAAO,MAAA,CAAO,SAASI,CAAC,CAAA,EAAKA,CAAAA,CAAI,CAAA,CAAI,IAAA,CAAK,KAAA,CAAMA,CAAC,CAAA,CAAI,GACvD,CAEO,SAASC,CAAAA,CACdC,CAAAA,CACA,CACA,OAAO,MAAOT,CAAAA,EAAqB,CACjC,GAAI,CACF,OAAO,MAAMS,CAAAA,CAAQT,CAAO,CAC9B,CAAA,MAASU,CAAAA,CAAK,CACZ,IAAMC,CAAAA,CACJD,CAAAA,YAAe,KAAA,CAAQA,CAAAA,CAAI,OAAA,CAAU,uBAAA,CACvC,OAAA,OAAA,CAAQ,KAAA,CAAM,UAAA,CAAYC,CAAO,CAAA,CAC1B,QAAA,CAAS,IAAA,CAAK,CAAE,OAAA,CAAAA,CAAQ,EAAG,CAAE,MAAA,CAAQ,GAAI,CAAC,CACnD,CACF,CACF,CAMA,eAAsBC,CAAAA,CACpBC,CAAAA,CACAC,CAAAA,CAC0B,CAC1B,GAAI,CAACD,CAAAA,CAAM,OAAO,IAAA,CAClB,GAAI,CACF,OAAA,MAAMA,CAAAA,CAAKC,CAAO,CAAA,CACX,IACT,CAAA,MAASJ,CAAAA,CAAK,CACZ,IAAMC,EAAUD,CAAAA,YAAe,KAAA,CAAQA,CAAAA,CAAI,OAAA,CAAU,WAAA,CAC/CK,CAAAA,CACJ,OAAQL,CAAAA,EAAiC,MAAA,EAAW,QAAA,CAC9CA,CAAAA,CAAgC,MAAA,CAClC,GAAA,CACN,OAAO,SAAS,IAAA,CAAK,CAAE,OAAA,CAAAC,CAAQ,CAAA,CAAG,CAAE,MAAA,CAAAI,CAAO,CAAC,CAC9C,CACF,CCxCO,SAASC,CAAAA,CAAoBC,EAAyB,CAC3D,OAAOT,CAAAA,CAAmB,MAAOR,CAAAA,EAAqB,CACpD,IAAMC,CAAAA,CAAO,MAAMF,CAAAA,CAAmBC,CAAO,CAAA,CAC7C,GAAI,CAACC,EACH,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,sBAAuB,CAAA,CAClC,CAAE,MAAA,CAAQ,GAAI,CAChB,CAAA,CAGF,IAAMiB,CAAAA,CAAMhB,EAAcD,CAAAA,CAAK,GAAA,CAAK,KAAK,CAAA,CACzC,GAAIiB,CAAAA,YAAe,QAAA,CAAU,OAAOA,CAAAA,CAEpC,IAAMC,CAAAA,CAASlB,CAAAA,CAAK,MAAA,EAAQ,IAAA,EAAK,EAAKgB,CAAAA,CAAO,aAAA,CACvCG,CAAAA,CAAYd,CAAAA,CAAmBL,CAAAA,CAAK,SAAS,CAAA,CAE7CoB,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,MAAA,EAAQ,KAAA,CAAO,CAC7D,QAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CAAAA,CACA,WAAA,CAAalB,CAAAA,CAAK,WAAA,CAClB,QAAA,CAAUA,CAAAA,CAAK,QACjB,CAAC,CAAA,CACD,GAAIoB,EAAa,OAAOA,CAAAA,CAExB,IAAMC,CAAAA,CAAM,MAAMC,YAAAA,CAChBN,CAAAA,CAAO,EAAA,CACP,IAAIO,gBAAAA,CAAiB,CACnB,MAAA,CAAQL,CAAAA,CACR,GAAA,CAAKD,EACL,WAAA,CAAajB,CAAAA,CAAK,WAAA,CAClB,QAAA,CAAUA,CAAAA,CAAK,QACjB,CAAC,CAAA,CACD,CAAE,SAAA,CAAAmB,CAAU,CACd,CAAA,CAEA,OAAA,MAAMH,EAAO,KAAA,EAAO,MAAA,EAAQ,SAAA,GAAY,CACtC,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CAAAA,CACA,WAAA,CAAalB,CAAAA,CAAK,WAAA,CAClB,QAAA,CAAUA,EAAK,QAAA,CACf,GAAA,CAAAqB,CAAAA,CACA,SAAA,CAAAF,CACF,CAAC,CAAA,CAEM,QAAA,CAAS,IAAA,CAAK,CAAE,MAAA,CAAAD,CAAAA,CAAQ,GAAA,CAAAD,CAAAA,CAAK,IAAAI,CAAAA,CAAK,SAAA,CAAAF,CAAU,CAAC,CACtD,CAAC,CACH,CCrDO,SAASK,CAAAA,CAAqBR,EAAyB,CAC5D,OAAOT,CAAAA,CAAmB,MAAOR,CAAAA,EAAqB,CACpD,IAAMC,CAAAA,CAAO,MAAMF,CAAAA,CAAmBC,CAAO,CAAA,CAC7C,GAAI,CAACC,CAAAA,CACH,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,sBAAuB,CAAA,CAClC,CAAE,MAAA,CAAQ,GAAI,CAChB,CAAA,CAGF,IAAMiB,CAAAA,CAAMhB,CAAAA,CAAcD,EAAK,GAAA,CAAK,KAAK,CAAA,CACzC,GAAIiB,CAAAA,YAAe,QAAA,CAAU,OAAOA,CAAAA,CAEpC,IAAMC,CAAAA,CAASlB,CAAAA,CAAK,MAAA,EAAQ,IAAA,EAAK,EAAKgB,EAAO,aAAA,CAEvCI,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,MAAA,EAAQ,KAAA,CAAO,CAC7D,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CACF,CAAC,CAAA,CACD,GAAIE,CAAAA,CAAa,OAAOA,CAAAA,CAExB,IAAMK,CAAAA,CAAO,MAAMT,CAAAA,CAAO,EAAA,CAAG,IAAA,CAC3B,IAAIU,iBAAAA,CAAkB,CAAE,MAAA,CAAQR,CAAAA,CAAQ,GAAA,CAAKD,CAAI,CAAC,CACpD,CAAA,CAEMJ,CAAAA,CAAU,CACd,OAAA,CAAAd,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,EACA,WAAA,CAAaO,CAAAA,CAAK,WAAA,CAClB,aAAA,CAAeA,CAAAA,CAAK,aAAA,EAAiB,CAAA,CACrC,IAAA,CAAMA,CAAAA,CAAK,IAAA,EAAM,OAAA,CAAQ,IAAA,CAAM,EAAE,CAAA,CACjC,SAAUA,CAAAA,CAAK,QACjB,CAAA,CAEA,OAAA,MAAMT,CAAAA,CAAO,KAAA,EAAO,MAAA,EAAQ,UAAA,GAAaH,CAAO,CAAA,CAEzC,QAAA,CAAS,IAAA,CAAK,CACnB,GAAA,CAAAI,EACA,MAAA,CAAAC,CAAAA,CACA,WAAA,CAAaL,CAAAA,CAAQ,WAAA,CACrB,aAAA,CAAeA,CAAAA,CAAQ,aAAA,CACvB,IAAA,CAAMA,CAAAA,CAAQ,IAChB,CAAC,CACH,CAAC,CACH,CCvDO,SAASc,CAAAA,CAAsBX,CAAAA,CAAyB,CAC7D,OAAOT,CAAAA,CAAmB,MAAOR,CAAAA,EAAqB,CACpD,GAAM,CAAE,YAAA,CAAA6B,CAAa,CAAA,CAAI,IAAI,GAAA,CAAI7B,CAAAA,CAAQ,GAAG,CAAA,CACtCkB,CAAAA,CAAMW,CAAAA,CAAa,IAAI,KAAK,CAAA,EAAG,IAAA,EAAK,CAC1C,GAAI,CAACX,CAAAA,CACH,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,iCAAkC,CAAA,CAC7C,CAAE,MAAA,CAAQ,GAAI,CAChB,CAAA,CAGF,IAAMC,CAAAA,CAASU,CAAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,EAAG,IAAA,EAAK,EAAKZ,CAAAA,CAAO,aAAA,CACtDG,EAAYd,CAAAA,CAAmBuB,CAAAA,CAAa,GAAA,CAAI,WAAW,CAAC,CAAA,CAC5DC,CAAAA,CAAWD,CAAAA,CAAa,GAAA,CAAI,UAAU,CAAA,EAAG,IAAA,EAAK,CAE9CR,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,QAAA,EAAU,KAAA,CAAO,CAC/D,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CAAAA,CACA,QAAA,CAAUW,CAAAA,EAAY,MACxB,CAAC,CAAA,CACD,GAAIT,CAAAA,CAAa,OAAOA,CAAAA,CAExB,IAAMC,CAAAA,CAAM,MAAMC,YAAAA,CAChBN,CAAAA,CAAO,EAAA,CACP,IAAIc,gBAAAA,CAAiB,CACnB,MAAA,CAAQZ,CAAAA,CACR,GAAA,CAAKD,CAAAA,CACL,0BAAA,CAA4B,CAAA,UAAA,EAAaY,CAAAA,CAAW,CAAA,YAAA,EAAeA,CAAQ,CAAA,CAAA,CAAA,CAAM,EAAE,CAAA,CACrF,CAAC,CAAA,CACD,CAAE,SAAA,CAAAV,CAAU,CACd,CAAA,CAEA,OAAA,MAAMH,CAAAA,CAAO,KAAA,EAAO,QAAA,EAAU,SAAA,GAAY,CACxC,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,EACA,QAAA,CAAUW,CAAAA,EAAY,KAAA,CAAA,CACtB,GAAA,CAAAR,CAAAA,CACA,SAAA,CAAAF,CACF,CAAC,CAAA,CAEM,QAAA,CAAS,IAAA,CAAK,CAAE,MAAA,CAAAD,CAAAA,CAAQ,IAAAD,CAAAA,CAAK,GAAA,CAAAI,CAAAA,CAAK,SAAA,CAAAF,CAAU,CAAC,CACtD,CAAC,CACH,CC7CO,SAASY,CAAAA,CAAoBf,CAAAA,CAAyB,CAC3D,OAAOT,CAAAA,CAAmB,MAAOR,CAAAA,EAAqB,CACpD,GAAM,CAAE,YAAA,CAAA6B,CAAa,EAAI,IAAI,GAAA,CAAI7B,CAAAA,CAAQ,GAAG,CAAA,CACtCkB,CAAAA,CAAMW,CAAAA,CAAa,GAAA,CAAI,KAAK,CAAA,EAAG,IAAA,EAAK,CAC1C,GAAI,CAACX,EACH,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,iCAAkC,CAAA,CAC7C,CAAE,MAAA,CAAQ,GAAI,CAChB,CAAA,CAGF,IAAMC,CAAAA,CAASU,EAAa,GAAA,CAAI,QAAQ,CAAA,EAAG,IAAA,EAAK,EAAKZ,CAAAA,CAAO,aAAA,CAEtDI,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,MAAA,EAAQ,KAAA,CAAO,CAC7D,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CACF,CAAC,CAAA,CACD,GAAIE,CAAAA,CAAa,OAAOA,CAAAA,CAExB,GAAI,CACF,MAAMJ,CAAAA,CAAO,EAAA,CAAG,IAAA,CAAK,IAAIU,iBAAAA,CAAkB,CAAE,MAAA,CAAQR,CAAAA,CAAQ,GAAA,CAAKD,CAAI,CAAC,CAAC,EAC1E,CAAA,KAAQ,CACN,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,CAAA,QAAA,EAAWA,CAAG,CAAA,WAAA,CAAc,CAAA,CACvC,CAAE,MAAA,CAAQ,GAAI,CAChB,CACF,CAEA,aAAMD,CAAAA,CAAO,EAAA,CAAG,IAAA,CAAK,IAAIgB,mBAAAA,CAAoB,CAAE,MAAA,CAAQd,CAAAA,CAAQ,GAAA,CAAKD,CAAI,CAAC,CAAC,CAAA,CAE1E,MAAMD,EAAO,KAAA,EAAO,MAAA,EAAQ,SAAA,GAAY,CAAE,OAAA,CAAAjB,CAAAA,CAAS,GAAA,CAAAkB,CAAAA,CAAK,MAAA,CAAAC,CAAO,CAAC,CAAA,CAEzD,QAAA,CAAS,IAAA,CAAK,CAAE,OAAA,CAAS,CAAA,CAAA,CAAM,MAAA,CAAAA,CAAAA,CAAQ,GAAA,CAAAD,CAAI,CAAC,CACrD,CAAC,CACH,CCvBO,SAASgB,CAAAA,CAA2BjB,CAAAA,CAAyB,CAClE,OAAOT,CAAAA,CAAmB,MAAOR,CAAAA,EAAqB,CACpD,IAAMC,CAAAA,CAAO,MAAMF,CAAAA,CAAmBC,CAAO,EAC7C,GAAI,CAACC,CAAAA,CACH,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,sBAAuB,CAAA,CAClC,CAAE,MAAA,CAAQ,GAAI,CAChB,EAGF,IAAMiB,CAAAA,CAAMhB,CAAAA,CAAcD,CAAAA,CAAK,GAAA,CAAK,KAAK,CAAA,CACzC,GAAIiB,CAAAA,YAAe,QAAA,CAAU,OAAOA,CAAAA,CAEpC,IAAMC,CAAAA,CAASlB,CAAAA,CAAK,MAAA,EAAQ,IAAA,EAAK,EAAKgB,CAAAA,CAAO,aAAA,CAEvCI,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,SAAA,EAAW,KAAA,CAAO,CAChE,OAAA,CAAAjB,CAAAA,CACA,IAAAkB,CAAAA,CACA,MAAA,CAAAC,CACF,CAAC,CAAA,CACD,GAAIE,CAAAA,CAAa,OAAOA,CAAAA,CAExB,GAAM,CAAE,QAAA,CAAAc,CAAS,CAAA,CAAI,MAAMlB,CAAAA,CAAO,EAAA,CAAG,IAAA,CACnC,IAAImB,4BAAAA,CAA6B,CAC/B,MAAA,CAAQjB,CAAAA,CACR,GAAA,CAAKD,CAAAA,CACL,WAAA,CAAajB,CAAAA,CAAK,WAAA,CAClB,QAAA,CAAUA,EAAK,QACjB,CAAC,CACH,CAAA,CAEA,OAAA,MAAMgB,CAAAA,CAAO,KAAA,EAAO,SAAA,EAAW,MAAA,GAAS,CACtC,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CAAAA,CACA,QAAA,CAAUgB,CAAAA,CACV,WAAA,CAAalC,CAAAA,CAAK,WAAA,CAClB,QAAA,CAAUA,CAAAA,CAAK,QACjB,CAAC,CAAA,CAEM,QAAA,CAAS,IAAA,CAAK,CAAE,MAAA,CAAAkB,EAAQ,GAAA,CAAAD,CAAAA,CAAK,QAAA,CAAUiB,CAAS,CAAA,CAAG,CAAE,MAAA,CAAQ,GAAI,CAAC,CAC3E,CAAC,CACH,CCvCO,SAASE,CAAAA,CAA2BpB,CAAAA,CAAyB,CAClE,OAAOT,CAAAA,CAAmB,MAAOR,CAAAA,EAAqB,CACpD,IAAMC,CAAAA,CAAO,MAAMF,CAAAA,CAAmBC,CAAO,CAAA,CAC7C,GAAI,CAACC,CAAAA,CACH,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,sBAAuB,CAAA,CAClC,CAAE,MAAA,CAAQ,GAAI,CAChB,CAAA,CAGF,IAAMiB,CAAAA,CAAMhB,CAAAA,CAAcD,CAAAA,CAAK,GAAA,CAAK,KAAK,CAAA,CACzC,GAAIiB,CAAAA,YAAe,SAAU,OAAOA,CAAAA,CAEpC,IAAMoB,CAAAA,CAAWpC,CAAAA,CAAcD,CAAAA,CAAK,QAAA,CAAU,UAAU,CAAA,CACxD,GAAIqC,CAAAA,YAAoB,QAAA,CAAU,OAAOA,CAAAA,CAEzC,IAAMC,CAAAA,CAAa,MAAA,CAAOtC,CAAAA,CAAK,UAAU,CAAA,CACzC,GAAI,CAAC,MAAA,CAAO,SAAA,CAAUsC,CAAU,CAAA,EAAKA,CAAAA,EAAc,CAAA,CACjD,OAAO,SAAS,IAAA,CACd,CAAE,OAAA,CAAS,uCAAwC,CAAA,CACnD,CAAE,MAAA,CAAQ,GAAI,CAChB,CAAA,CAGF,IAAMpB,CAAAA,CAASlB,CAAAA,CAAK,MAAA,EAAQ,MAAK,EAAKgB,CAAAA,CAAO,aAAA,CACvCG,CAAAA,CAAYd,CAAAA,CAAmBL,CAAAA,CAAK,SAAS,CAAA,CAE7CoB,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,SAAA,EAAW,MAAO,CAChE,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CACF,CAAC,CAAA,CACD,GAAIE,CAAAA,CAAa,OAAOA,CAAAA,CAExB,IAAMmB,EAAe,MAAMjB,YAAAA,CACzBN,CAAAA,CAAO,EAAA,CACP,IAAIwB,iBAAAA,CAAkB,CACpB,MAAA,CAAQtB,CAAAA,CACR,GAAA,CAAKD,CAAAA,CACL,QAAA,CAAUoB,CAAAA,CACV,UAAA,CAAYC,CACd,CAAC,CAAA,CACD,CAAE,SAAA,CAAAnB,CAAU,CACd,CAAA,CAEA,OAAO,QAAA,CAAS,IAAA,CAAK,CACnB,YAAA,CAAAoB,CAAAA,CACA,UAAA,CAAAD,CAAAA,CACA,QAAA,CAAAD,CAAAA,CACA,MAAA,CAAAnB,CAAAA,CACA,SAAA,CAAAC,CACF,CAAC,CACH,CAAC,CACH,CCnDO,SAASsB,CAAAA,CAA+BzB,CAAAA,CAAyB,CACtE,OAAOT,CAAAA,CAAmB,MAAOR,CAAAA,EAAqB,CACpD,IAAMC,CAAAA,CAAO,MAAMF,CAAAA,CAAmBC,CAAO,CAAA,CAC7C,GAAI,CAACC,CAAAA,CACH,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,sBAAuB,CAAA,CAClC,CAAE,MAAA,CAAQ,GAAI,CAChB,CAAA,CAGF,IAAMiB,CAAAA,CAAMhB,CAAAA,CAAcD,CAAAA,CAAK,GAAA,CAAK,KAAK,CAAA,CACzC,GAAIiB,CAAAA,YAAe,QAAA,CAAU,OAAOA,CAAAA,CAEpC,IAAMoB,CAAAA,CAAWpC,CAAAA,CAAcD,EAAK,QAAA,CAAU,UAAU,CAAA,CACxD,GAAIqC,CAAAA,YAAoB,QAAA,CAAU,OAAOA,CAAAA,CAEzC,IAAMK,CAAAA,CAAAA,CAAS,KAAA,CAAM,OAAA,CAAQ1C,CAAAA,CAAK,KAAK,EAAIA,CAAAA,CAAK,KAAA,CAAQ,EAAC,EACtD,GAAA,CAAI,CAAC,CAAE,UAAA,CAAAsC,CAAAA,CAAY,IAAA,CAAAK,CAAK,CAAA,IAAO,CAC9B,UAAA,CAAY,OAAOL,CAAU,CAAA,CAC7B,IAAA,CAAM,MAAA,CAAOK,CAAI,CACnB,CAAA,CAAE,CAAA,CACD,MAAA,CAAQC,CAAAA,EAAM,MAAA,CAAO,SAAA,CAAUA,CAAAA,CAAE,UAAU,GAAKA,CAAAA,CAAE,IAAI,CAAA,CACtD,IAAA,CAAK,CAACC,CAAAA,CAAGC,CAAAA,GAAMD,CAAAA,CAAE,UAAA,CAAaC,CAAAA,CAAE,UAAU,CAAA,CAE7C,GAAI,CAACJ,CAAAA,CAAM,MAAA,CACT,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,qCAAsC,CAAA,CACjD,CAAE,MAAA,CAAQ,GAAI,CAChB,CAAA,CAGF,IAAMxB,CAAAA,CAASlB,EAAK,MAAA,EAAQ,IAAA,EAAK,EAAKgB,CAAAA,CAAO,aAAA,CAEvCI,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,SAAA,EAAW,KAAA,CAAO,CAChE,OAAA,CAAAjB,EACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CACF,CAAC,CAAA,CACD,GAAIE,CAAAA,CAAa,OAAOA,CAAAA,CAExB,GAAM,CAAE,QAAA,CAAA2B,CAAAA,CAAU,KAAAC,CAAK,CAAA,CAAI,MAAMhC,CAAAA,CAAO,EAAA,CAAG,IAAA,CACzC,IAAIiC,8BAAAA,CAA+B,CACjC,MAAA,CAAQ/B,CAAAA,CACR,GAAA,CAAKD,CAAAA,CACL,QAAA,CAAUoB,EACV,eAAA,CAAiB,CAAE,KAAA,CAAOK,CAAM,CAClC,CAAC,CACH,CAAA,CAEA,OAAA,MAAM1B,CAAAA,CAAO,KAAA,EAAO,SAAA,EAAW,UAAA,GAAa,CAC1C,QAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CAAAA,CACA,QAAA,CAAAmB,CACF,CAAC,CAAA,CAEM,QAAA,CAAS,IAAA,CAAK,CACnB,MAAA,CAAAnB,CAAAA,CACA,IAAAD,CAAAA,CACA,QAAA,CAAAoB,CAAAA,CACA,QAAA,CAAUU,CAAAA,CACV,IAAA,CAAMC,CACR,CAAC,CACH,CAAC,CACH,CCtEO,SAASE,CAAAA,CAA4BlC,CAAAA,CAAyB,CACnE,OAAOT,CAAAA,CAAmB,MAAOR,CAAAA,EAAqB,CACpD,IAAMC,CAAAA,CAAO,MAAMF,CAAAA,CAAmBC,CAAO,CAAA,CAC7C,GAAI,CAACC,CAAAA,CACH,OAAO,QAAA,CAAS,IAAA,CACd,CAAE,OAAA,CAAS,sBAAuB,CAAA,CAClC,CAAE,MAAA,CAAQ,GAAI,CAChB,EAGF,IAAMiB,CAAAA,CAAMhB,CAAAA,CAAcD,CAAAA,CAAK,GAAA,CAAK,KAAK,CAAA,CACzC,GAAIiB,CAAAA,YAAe,QAAA,CAAU,OAAOA,CAAAA,CAEpC,IAAMoB,CAAAA,CAAWpC,EAAcD,CAAAA,CAAK,QAAA,CAAU,UAAU,CAAA,CACxD,GAAIqC,CAAAA,YAAoB,QAAA,CAAU,OAAOA,CAAAA,CAEzC,IAAMnB,CAAAA,CAASlB,CAAAA,CAAK,MAAA,EAAQ,IAAA,IAAUgB,CAAAA,CAAO,aAAA,CAEvCI,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,SAAA,EAAW,KAAA,CAAO,CAChE,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,CAAAA,CACA,MAAA,CAAAC,CACF,CAAC,CAAA,CACD,OAAIE,CAAAA,GAEJ,MAAMJ,CAAAA,CAAO,EAAA,CAAG,IAAA,CACd,IAAImC,2BAAAA,CAA4B,CAC9B,MAAA,CAAQjC,CAAAA,CACR,GAAA,CAAKD,EACL,QAAA,CAAUoB,CACZ,CAAC,CACH,CAAA,CAEA,MAAMrB,CAAAA,CAAO,KAAA,EAAO,SAAA,EAAW,OAAA,GAAU,CACvC,OAAA,CAAAjB,CAAAA,CACA,GAAA,CAAAkB,EACA,MAAA,CAAAC,CAAAA,CACA,QAAA,CAAAmB,CACF,CAAC,CAAA,CAEM,QAAA,CAAS,IAAA,CAAK,CAAE,MAAA,CAAAnB,CAAAA,CAAQ,GAAA,CAAAD,CAAAA,CAAK,QAAA,CAAAoB,EAAU,OAAA,CAAS,CAAA,CAAK,CAAC,CAAA,CAC/D,CAAC,CACH,CC/CO,SAASe,CAAAA,CAAepC,CAAAA,CAAqC,CAClE,OAAO,CACL,OAAA,CAAS,CACP,MAAA,CAAQD,CAAAA,CAAoBC,CAAM,CAAA,CAClC,OAAA,CAASQ,CAAAA,CAAqBR,CAAM,CAAA,CACpC,QAAA,CAAUW,CAAAA,CAAsBX,CAAM,CACxC,CAAA,CACA,SAAA,CAAW,CACT,KAAMiB,CAAAA,CAA2BjB,CAAM,CAAA,CACvC,IAAA,CAAMoB,CAAAA,CAA2BpB,CAAM,CAAA,CACvC,QAAA,CAAUyB,CAAAA,CAA+BzB,CAAM,CAAA,CAC/C,KAAA,CAAOkC,CAAAA,CAA4BlC,CAAM,CAC3C,CAAA,CACA,MAAA,CAAQe,CAAAA,CAAoBf,CAAM,CACpC,CACF,CCrBO,SAASqC,CAAAA,CAAarC,CAAAA,CAA8B,CACzD,IAAMsC,CAAAA,CAAWF,CAAAA,CAAepC,CAAM,CAAA,CAChCuC,CAAAA,CAAOvC,CAAAA,CAAO,QAAA,CAAS,OAAA,CAAQ,KAAA,CAAO,EAAE,CAAA,CAE9C,OAAO,MAAOjB,CAAAA,EAAwC,CAEpD,IAAMqB,CAAAA,CAAc,MAAMT,CAAAA,CAAQK,CAAAA,CAAO,KAAA,EAAO,KAAA,CAAO,CAAE,OAAA,CAAAjB,CAAQ,CAAC,CAAA,CAClE,GAAIqB,CAAAA,CAAa,OAAOA,CAAAA,CAGxB,IAAMoC,EADM,IAAI,GAAA,CAAIzD,CAAAA,CAAQ,GAAG,CAAA,CACX,QAAA,CAAS,KAAA,CAAMwD,CAAAA,CAAK,MAAM,CAAA,CAAE,OAAA,CAAQ,KAAA,CAAO,EAAE,CAAA,CAC3DE,EAAS1D,CAAAA,CAAQ,MAAA,CAEvB,OAAI0D,CAAAA,GAAW,MAAA,EAAUD,CAAAA,GAAY,gBAAA,CAC5BF,CAAAA,CAAS,OAAA,CAAQ,MAAA,CAAOvD,CAAO,CAAA,CACpC0D,CAAAA,GAAW,MAAA,EAAUD,IAAY,wBAAA,CAC5BF,CAAAA,CAAS,OAAA,CAAQ,OAAA,CAAQvD,CAAO,CAAA,CACrC0D,CAAAA,GAAW,KAAA,EAASD,CAAAA,GAAY,kBAAA,CAC3BF,CAAAA,CAAS,OAAA,CAAQ,QAAA,CAASvD,CAAO,CAAA,CACtC0D,CAAAA,GAAW,QAAA,EAAYD,CAAAA,GAAY,QAAA,CAC9BF,CAAAA,CAAS,MAAA,CAAOvD,CAAO,CAAA,CAC5B0D,CAAAA,GAAW,MAAA,EAAUD,CAAAA,GAAY,wBAAA,CAC5BF,CAAAA,CAAS,SAAA,CAAU,IAAA,CAAKvD,CAAO,CAAA,CACpC0D,CAAAA,GAAW,MAAA,EAAUD,CAAAA,GAAY,wBAAA,CAC5BF,CAAAA,CAAS,SAAA,CAAU,IAAA,CAAKvD,CAAO,CAAA,CACpC0D,CAAAA,GAAW,MAAA,EAAUD,CAAAA,GAAY,4BAAA,CAC5BF,EAAS,SAAA,CAAU,QAAA,CAASvD,CAAO,CAAA,CACxC0D,CAAAA,GAAW,MAAA,EAAUD,CAAAA,GAAY,yBAAA,CAC5BF,CAAAA,CAAS,SAAA,CAAU,KAAA,CAAMvD,CAAO,CAAA,CAElC,QAAA,CAAS,KAAK,CAAE,OAAA,CAAS,WAAY,CAAA,CAAG,CAAE,MAAA,CAAQ,GAAI,CAAC,CAChE,CACF,CCuCO,SAAS2D,CAAAA,CAAiBC,CAAAA,CAAW,UAAuB,CACjE,IAAMJ,CAAAA,CAAOI,CAAAA,CAAS,OAAA,CAAQ,KAAA,CAAO,EAAE,CAAA,CAEjCC,CAAAA,CAAO,MAAUvC,CAAAA,CAAawC,CAAAA,GAAmC,CACrE,IAAMC,EAAM,MAAM,KAAA,CAAMzC,CAAAA,CAAKwC,CAAI,CAAA,CACjC,GAAI,CAACC,CAAAA,CAAI,EAAA,CAAI,CACX,IAAM9D,CAAAA,CAAO,MAAM8D,CAAAA,CAAI,MAAK,CAAE,KAAA,CAAM,KAAO,EAAC,CAAE,CAAA,CAC9C,MAAM,IAAI,KAAA,CAAO9D,CAAAA,CAA8B,OAAA,EAAW8D,CAAAA,CAAI,UAAU,CAC1E,CACA,OAAOA,CAAAA,CAAI,IAAA,EACb,CAAA,CAEMC,CAAAA,CAAO,CAAI1C,CAAAA,CAAarB,CAAAA,GAC5B4D,CAAAA,CAAQvC,CAAAA,CAAK,CACX,MAAA,CAAQ,MAAA,CACR,OAAA,CAAS,CAAE,cAAA,CAAgB,kBAAmB,CAAA,CAC9C,IAAA,CAAM,IAAA,CAAK,SAAA,CAAUrB,CAAI,CAC3B,CAAC,CAAA,CAEH,OAAO,CACL,MAAA,CAAOgE,CAAAA,CAAS,CACd,OAAOD,CAAAA,CAAsB,CAAA,EAAGR,CAAI,CAAA,eAAA,CAAA,CAAmBS,CAAO,CAChE,CAAA,CAEA,OAAA,CAAQA,CAAAA,CAAS,CACf,OAAOD,CAAAA,CACL,CAAA,EAAGR,CAAI,CAAA,uBAAA,CAAA,CACPS,CACF,CACF,CAAA,CAEA,QAAA,CAAS/C,CAAAA,CAAKgD,CAAAA,CAAU,CACtB,IAAMC,CAAAA,CAAS,IAAI,eAAA,CAAgB,CAAE,GAAA,CAAAjD,CAAI,CAAC,CAAA,CAC1C,GAAIgD,CAAAA,EAAS,QAAA,CAAU,CACrB,IAAME,CAAAA,CAAOF,CAAAA,CAAQ,QAAA,CAAS,OAAA,CAAQ,YAAA,CAAc,GAAG,CAAA,CACvDC,EAAO,GAAA,CAAI,UAAA,CAAYC,CAAI,EAC7B,CACA,OAAIF,CAAAA,EAAS,MAAA,EAAQC,CAAAA,CAAO,GAAA,CAAI,QAAA,CAAUD,CAAAA,CAAQ,MAAM,CAAA,CACjDL,EAAsB,CAAA,EAAGL,CAAI,CAAA,kBAAA,EAAqBW,CAAM,CAAA,CAAE,CACnE,CAAA,CAEA,MAAA,CAAOjD,CAAAA,CAAKgD,CAAAA,CAAU,CACpB,IAAMC,CAAAA,CAAS,IAAI,gBAAgB,CAAE,GAAA,CAAAjD,CAAI,CAAC,CAAA,CAC1C,OAAIgD,CAAAA,EAAS,MAAA,EAAQC,CAAAA,CAAO,GAAA,CAAI,QAAA,CAAUD,CAAAA,CAAQ,MAAM,CAAA,CACjDL,EACL,CAAA,EAAGL,CAAI,CAAA,QAAA,EAAWW,CAAM,CAAA,CAAA,CACxB,CAAE,MAAA,CAAQ,QAAS,CACrB,CACF,CAAA,CAEA,SAAA,CAAW,CACT,IAAA,CAAKF,CAAAA,CAAS,CACZ,OAAOD,CAAAA,CACL,CAAA,EAAGR,CAAI,CAAA,uBAAA,CAAA,CACPS,CACF,CACF,CAAA,CAEA,QAAA,CAASA,CAAAA,CAAS,CAChB,OAAOD,CAAAA,CACL,CAAA,EAAGR,CAAI,CAAA,uBAAA,CAAA,CACPS,CACF,CACF,CAAA,CAEA,QAAA,CAASA,CAAAA,CAAS,CAChB,OAAOD,CAAAA,CACL,CAAA,EAAGR,CAAI,CAAA,2BAAA,CAAA,CACPS,CACF,CACF,EAEA,KAAA,CAAMA,CAAAA,CAAS,CACb,OAAOD,CAAAA,CACL,CAAA,EAAGR,CAAI,CAAA,wBAAA,CAAA,CACPS,CACF,CACF,CACF,CACF,CACF,CC3JO,SAASI,CAAAA,CACdC,CAAAA,CACAJ,CAAAA,CACe,CACf,GAAIA,CAAAA,CAAQ,MAAA,EAAQ,MAAA,EAad,CAZYA,CAAAA,CAAQ,MAAA,CAAO,IAAA,CAAMK,CAAAA,EAE/BA,CAAAA,CAAK,UAAA,CAAW,GAAG,CAAA,CACdD,CAAAA,CAAK,IAAA,CAAK,WAAA,EAAY,CAAE,QAAA,CAASC,CAAAA,CAAK,WAAA,EAAa,CAAA,CAGxDA,CAAAA,CAAK,QAAA,CAAS,IAAI,CAAA,CACbD,EAAK,IAAA,CAAK,UAAA,CAAWC,CAAAA,CAAK,OAAA,CAAQ,IAAA,CAAM,GAAG,CAAC,CAAA,CAG9CD,CAAAA,CAAK,IAAA,GAASC,CACtB,CAAA,CACa,CACZ,IAAMC,EAAMF,CAAAA,CAAK,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA,CAAIA,CAAAA,CAAK,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,EAAI,CAAI,IAAA,CACnE,OAAO,cAAcE,CAAAA,CAAM,CAAA,CAAA,EAAIA,CAAG,CAAA,CAAA,CAAKF,CAAAA,CAAK,IAAA,EAAQ,SAAS,CAAA,gBAAA,CAC/D,CAGF,OAAIA,CAAAA,CAAK,IAAA,GAAS,CAAA,CACT,eAAA,CAGLJ,CAAAA,CAAQ,WAAA,EAAeI,CAAAA,CAAK,IAAA,CAAOJ,CAAAA,CAAQ,WAAA,CAEtC,CAAA,kBAAA,EAAA,CADQA,CAAAA,CAAQ,WAAA,CAAe,OAAA,EAAc,OAAA,CAAQ,CAAC,CAC5B,CAAA,SAAA,CAAA,CAG5B,IACT","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","import { PutObjectCommand } 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 \"../../helpers\";\n\ntype Payload = {\n key: string;\n contentType?: string;\n metadata?: Record<string, string>;\n bucket?: string;\n expiresIn?: number;\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\n const guardResult = await runHook(config.hooks?.upload?.guard, {\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n });\n if (guardResult) return guardResult;\n\n const url = await getSignedUrl(\n config.s3,\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: body.contentType,\n Metadata: body.metadata,\n }),\n { expiresIn },\n );\n\n await config.hooks?.upload?.onSuccess?.({\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n url,\n expiresIn,\n });\n\n return Response.json({ bucket, key, url, expiresIn });\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 \"../../helpers\";\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.hooks?.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 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 };\n\n await config.hooks?.upload?.onComplete?.(context);\n\n return Response.json({\n key,\n bucket,\n contentType: context.contentType,\n contentLength: context.contentLength,\n eTag: context.eTag,\n });\n });\n}\n","import { GetObjectCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport { normalizeExpiresIn, runHook, withS3ErrorHandler } 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.hooks?.download?.guard, {\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n });\n if (guardResult) return guardResult;\n\n const url = await getSignedUrl(\n config.s3,\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n ResponseContentDisposition: `attachment${fileName ? `; filename=\"${fileName}\"` : \"\"}`,\n }),\n { expiresIn },\n );\n\n await config.hooks?.download?.onSuccess?.({\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 \"../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.hooks?.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(\n { message: `Object \"${key}\" not found` },\n { status: 404 },\n );\n }\n\n await config.s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));\n\n await config.hooks?.delete?.onSuccess?.({ 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 \"../../helpers\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n contentType?: string;\n metadata?: Record<string, 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\n const guardResult = await runHook(config.hooks?.multipart?.guard, {\n request,\n key,\n bucket,\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 Metadata: body.metadata,\n }),\n );\n\n await config.hooks?.multipart?.onInit?.({\n request,\n key,\n bucket,\n uploadId: UploadId!,\n contentType: body.contentType,\n metadata: body.metadata,\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 \"../../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.hooks?.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 { CompleteMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../helpers\";\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.hooks?.multipart?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const { Location, ETag } = await config.s3.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: { Parts: parts },\n }),\n );\n\n await config.hooks?.multipart?.onComplete?.({\n request,\n key,\n bucket,\n uploadId,\n });\n\n return Response.json({\n bucket,\n key,\n uploadId,\n location: Location,\n eTag: ETag,\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 \"../../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.hooks?.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.hooks?.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 \"./helpers\";\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.hooks?.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 handlers.presign.upload(request);\n if (method === \"POST\" && subpath === \"presign/upload/confirm\")\n return handlers.presign.confirm(request);\n if (method === \"GET\" && subpath === \"presign/download\")\n return handlers.presign.download(request);\n if (method === \"DELETE\" && subpath === \"delete\")\n return handlers.delete(request);\n if (method === \"POST\" && subpath === \"presign/multipart/init\")\n return handlers.multipart.init(request);\n if (method === \"POST\" && subpath === \"presign/multipart/part\")\n return handlers.multipart.part(request);\n if (method === \"POST\" && subpath === \"presign/multipart/complete\")\n return handlers.multipart.complete(request);\n if (method === \"POST\" && subpath === \"presign/multipart/abort\")\n return handlers.multipart.abort(request);\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 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};\n\nexport type PresignApi = {\n upload: (payload: {\n key: string;\n contentType?: string;\n metadata?: Record<string, string>;\n bucket?: string;\n }) => Promise<PresignResponse>;\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 metadata?: Record<string, string>;\n bucket?: 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<{ key: string; bucket: string; uploadId: string }>;\n abort: (payload: {\n key: string;\n uploadId: string;\n bucket?: string;\n }) => Promise<{ aborted: boolean }>;\n };\n};\n\nexport function createPresignApi(basePath = \"/api/s3\"): PresignApi {\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<PresignResponse>(`${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<{ key: string; bucket: string; uploadId: string }>(\n `${base}/presign/multipart/complete`,\n payload,\n );\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/helpers.ts","../src/handlers/presign/upload.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/presign-api.ts","../src/validate.ts"],"names":["parseBody","request","body","requireString","value","name","trimmed","normalizeExpiresIn","n","withS3ErrorHandler","handler","err","message","runHook","hook","context","status","createUploadHandler","config","key","bucket","expiresIn","guardResult","url","getSignedUrl","PutObjectCommand","createConfirmHandler","head","HeadObjectCommand","createDownloadHandler","searchParams","fileName","GetObjectCommand","createDeleteHandler","DeleteObjectCommand","createMultipartInitHandler","UploadId","CreateMultipartUploadCommand","createMultipartPartHandler","uploadId","partNumber","presignedUrl","UploadPartCommand","createMultipartCompleteHandler","parts","eTag","p","a","b","Location","ETag","CompleteMultipartUploadCommand","createMultipartAbortHandler","AbortMultipartUploadCommand","createHandlers","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","import { PutObjectCommand } 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 \"../../helpers\";\n\ntype Payload = {\n key: string;\n contentType?: string;\n metadata?: Record<string, string>;\n bucket?: string;\n expiresIn?: number;\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\n const guardResult = await runHook(config.hooks?.upload?.guard, {\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n });\n if (guardResult) return guardResult;\n\n const url = await getSignedUrl(\n config.s3,\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: body.contentType,\n Metadata: body.metadata,\n }),\n { expiresIn },\n );\n\n await config.hooks?.upload?.onSuccess?.({\n request,\n key,\n bucket,\n contentType: body.contentType,\n metadata: body.metadata,\n url,\n expiresIn,\n });\n\n return Response.json({ bucket, key, url, expiresIn });\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 \"../../helpers\";\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.hooks?.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 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 };\n\n await config.hooks?.upload?.onComplete?.(context);\n\n return Response.json({\n key,\n bucket,\n contentType: context.contentType,\n contentLength: context.contentLength,\n eTag: context.eTag,\n });\n });\n}\n","import { GetObjectCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport { normalizeExpiresIn, runHook, withS3ErrorHandler } 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.hooks?.download?.guard, {\n request,\n key,\n bucket,\n fileName: fileName || undefined,\n });\n if (guardResult) return guardResult;\n\n const url = await getSignedUrl(\n config.s3,\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n ResponseContentDisposition: `attachment${fileName ? `; filename=\"${fileName}\"` : \"\"}`,\n }),\n { expiresIn },\n );\n\n await config.hooks?.download?.onSuccess?.({\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 \"../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.hooks?.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(\n { message: `Object \"${key}\" not found` },\n { status: 404 },\n );\n }\n\n await config.s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));\n\n await config.hooks?.delete?.onSuccess?.({ 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 \"../../helpers\";\n\ntype Payload = {\n key: string;\n bucket?: string;\n contentType?: string;\n metadata?: Record<string, 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\n const guardResult = await runHook(config.hooks?.multipart?.guard, {\n request,\n key,\n bucket,\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 Metadata: body.metadata,\n }),\n );\n\n await config.hooks?.multipart?.onInit?.({\n request,\n key,\n bucket,\n uploadId: UploadId!,\n contentType: body.contentType,\n metadata: body.metadata,\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 \"../../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.hooks?.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 { CompleteMultipartUploadCommand } from \"@aws-sdk/client-s3\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n runHook,\n withS3ErrorHandler,\n} from \"../../helpers\";\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.hooks?.multipart?.guard, {\n request,\n key,\n bucket,\n });\n if (guardResult) return guardResult;\n\n const { Location, ETag } = await config.s3.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: { Parts: parts },\n }),\n );\n\n await config.hooks?.multipart?.onComplete?.({\n request,\n key,\n bucket,\n uploadId,\n });\n\n return Response.json({\n bucket,\n key,\n uploadId,\n location: Location,\n eTag: ETag,\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 \"../../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.hooks?.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.hooks?.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 \"./helpers\";\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.hooks?.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 handlers.presign.upload(request);\n if (method === \"POST\" && subpath === \"presign/upload/confirm\")\n return handlers.presign.confirm(request);\n if (method === \"GET\" && subpath === \"presign/download\")\n return handlers.presign.download(request);\n if (method === \"DELETE\" && subpath === \"delete\")\n return handlers.delete(request);\n if (method === \"POST\" && subpath === \"presign/multipart/init\")\n return handlers.multipart.init(request);\n if (method === \"POST\" && subpath === \"presign/multipart/part\")\n return handlers.multipart.part(request);\n if (method === \"POST\" && subpath === \"presign/multipart/complete\")\n return handlers.multipart.complete(request);\n if (method === \"POST\" && subpath === \"presign/multipart/abort\")\n return handlers.multipart.abort(request);\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 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};\n\nexport type S3Api = {\n upload: (payload: {\n key: string;\n contentType?: string;\n metadata?: Record<string, string>;\n bucket?: string;\n }) => Promise<PresignResponse>;\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 metadata?: Record<string, string>;\n bucket?: 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<{ key: string; bucket: string; uploadId: string }>;\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<PresignResponse>(`${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<{ key: string; bucket: string; uploadId: string }>(\n `${base}/presign/multipart/complete`,\n payload,\n );\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"]}
@@ -23,7 +23,7 @@ export type UploadConfirmResponse = {
23
23
  contentLength: number;
24
24
  eTag?: string;
25
25
  };
26
- export type PresignApi = {
26
+ export type S3Api = {
27
27
  upload: (payload: {
28
28
  key: string;
29
29
  contentType?: string;
@@ -80,4 +80,4 @@ export type PresignApi = {
80
80
  }>;
81
81
  };
82
82
  };
83
- export declare function createPresignApi(basePath?: string): PresignApi;
83
+ export declare function createS3Api(basePath?: string): S3Api;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-s3/server",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Framework-agnostic S3 server handlers — presigned uploads, downloads, deletes, and multipart operations",
5
5
  "keywords": [
6
6
  "s3",