@better-s3/server 3.1.1 → 3.2.1

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
@@ -12,119 +12,66 @@ pnpm add @better-s3/server @aws-sdk/client-s3
12
12
 
13
13
  ```ts
14
14
  // app/api/s3/[...s3]/route.ts (Next.js App Router)
15
- import { S3Client } from "@aws-sdk/client-s3";
16
15
  import { createRouteHandler } from "@better-s3/server/next";
16
+ import { s3Config } from "@/lib/better-s3";
17
17
 
18
- const handler = createRouteHandler({
19
- s3: new S3Client({ region: "us-east-1" }),
20
- defaultBucket: "my-bucket",
21
- basePath: "/api/s3",
22
-
23
- // Enable only the features your app needs. All are disabled by default.
24
- features: {
25
- upload: true,
26
- download: true,
27
- delete: true,
28
- // multipart: true, // enable only if you explicitly need multipart support
29
- },
30
- });
31
-
18
+ const handler = createRouteHandler(s3Config);
32
19
  export { handler as GET, handler as POST, handler as DELETE };
33
20
  ```
34
21
 
35
- Other frameworks via `createRouter` from `@better-s3/server`:
36
-
37
22
  ```ts
38
- const router = createRouter({
39
- s3,
40
- defaultBucket: "my-bucket",
23
+ // lib/better-s3.ts
24
+ import type { S3RouteHandlerConfig } from "@better-s3/server";
25
+ import { s3, defaultBucket, resolvePublicUrl } from "@/lib/s3";
26
+
27
+ export const s3Config = {
28
+ s3: s3,
29
+ defaultBucket: defaultBucket,
30
+ resolvePublicUrl: resolvePublicUrl,
41
31
  basePath: "/api/s3",
42
- features: { upload: true, download: true, delete: true, multipart: true },
43
- hooks: {
44
- guard: async ({ request }) => {
45
- console.log("[guard]", request.method, request.url);
46
- },
47
32
 
48
- upload: {
49
- guard: async ({ key, bucket, contentType, fileSize }) => {
50
- console.log("[upload.guard]", { key, bucket, contentType, fileSize });
51
- },
52
- onPresigned: async ({ key, url, expiresIn }) => {
53
- console.log("[upload.onPresigned]", { key, url, expiresIn });
54
- },
55
- onUploaded: async ({ key, contentType, contentLength, eTag }) => {
56
- console.log("[upload.onUploaded]", {
57
- key,
58
- contentType,
59
- contentLength,
60
- eTag,
61
- });
62
- },
63
- },
33
+ // guard: async ({ request }) => { ... },
64
34
 
65
- download: {
66
- guard: async ({ key, bucket, fileName }) => {
67
- console.log("[download.guard]", { key, bucket, fileName });
68
- },
69
- onPresigned: async ({ key, url, expiresIn }) => {
70
- console.log("[download.onPresigned]", { key, url, expiresIn });
71
- },
35
+ upload: {
36
+ enabled: true,
37
+ onUploaded: async ({ key, contentLength }) => {
38
+ // await db.file.create({ data: { key, contentLength } });
72
39
  },
40
+ },
73
41
 
74
- delete: {
75
- guard: async ({ key, bucket }) => {
76
- console.log("[delete.guard]", { key, bucket });
77
- },
78
- onDeleted: async ({ key, bucket }) => {
79
- console.log("[delete.onDeleted]", { key, bucket });
80
- },
81
- },
42
+ download: { enabled: true },
82
43
 
83
- multipart: {
84
- guard: async ({ key, bucket, fileSize }) => {
85
- console.log("[multipart.guard]", { key, bucket, fileSize });
86
- },
87
- onInit: async ({ key, uploadId, contentType, fileSize }) => {
88
- console.log("[multipart.onInit]", {
89
- key,
90
- uploadId,
91
- contentType,
92
- fileSize,
93
- });
94
- },
95
- onComplete: async ({ key, uploadId, contentLength, eTag }) => {
96
- console.log("[multipart.onComplete]", {
97
- key,
98
- uploadId,
99
- contentLength,
100
- eTag,
101
- });
102
- },
103
- onAbort: async ({ key, uploadId }) => {
104
- console.log("[multipart.onAbort]", { key, uploadId });
105
- },
44
+ delete: {
45
+ enabled: true,
46
+ onDeleted: async ({ key }) => {
47
+ // await db.file.delete({ where: { key } });
106
48
  },
107
49
  },
108
- });
109
50
 
51
+ // multipart: { enabled: true },
52
+ } satisfies S3RouteHandlerConfig;
53
+ ```
54
+
55
+ Other frameworks via `createRouter`:
56
+
57
+ ```ts
58
+ import { createRouter } from "@better-s3/server";
59
+
60
+ const router = createRouter(s3Config);
110
61
  app.all("/api/s3/*", (c) => router(c.req.raw)); // Hono example
111
62
  ```
112
63
 
113
- ## Features
64
+ ## Enabling endpoints
114
65
 
115
- All endpoints are **disabled by default**. You must explicitly opt in via the `features` config this prevents unintended exposure of expensive or sensitive operations (especially multipart, which is vulnerable to [cost attacks](#multipart-cost-attacks)).
66
+ All endpoints are **disabled by default** set `enabled: true` inside each section to opt in. Disabled endpoints respond with `404`.
116
67
 
117
68
  ```ts
118
- features: {
119
- upload: true, // POST /presign/upload + POST /presign/upload/confirm
120
- download: true, // GET /presign/download
121
- delete: true, // DELETE /delete
122
- multipart: true, // POST /presign/multipart/{init,part,complete,abort}
123
- }
69
+ upload: { enabled: true } // POST /presign/upload + POST /presign/upload/confirm
70
+ download: { enabled: true } // GET /presign/download
71
+ delete: { enabled: true } // DELETE /delete
72
+ multipart: { enabled: true } // POST /presign/multipart/{init,part,complete,abort}
124
73
  ```
125
74
 
126
- > Disabled endpoints respond with `404 Not Found`.
127
-
128
75
  | Upload mode | Enforcement |
129
76
  | ----------- | --------------------------------------------------------------------------------------------------------------- |
130
77
  | Simple | S3 enforces exact size via `content-length-range` in the signed POST policy — tamperproof |
@@ -132,7 +79,7 @@ features: {
132
79
 
133
80
  > Simple upload is inherently more secure — enforcement happens at the S3 storage layer. Multipart presigned `UploadPart` URLs cannot enforce per-part size; see [Multipart cost attacks](#multipart-cost-attacks).
134
81
 
135
- ## Server Hooks
82
+ ## Hooks
136
83
 
137
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.
138
85
 
@@ -147,12 +94,10 @@ Multipart: multipart.guard(init) → multipart.onInit
147
94
  ### Authentication
148
95
 
149
96
  ```ts
150
- hooks: {
151
- guard: async ({ request }) => {
152
- const session = await getSession(request);
153
- if (!session) throw Object.assign(new Error("Unauthorized"), { status: 401 });
154
- },
155
- }
97
+ guard: async ({ request }) => {
98
+ const session = await getSession(request);
99
+ if (!session) throw Object.assign(new Error("Unauthorized"), { status: 401 });
100
+ },
156
101
  ```
157
102
 
158
103
  ### Quota check
@@ -160,18 +105,19 @@ hooks: {
160
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.
161
106
 
162
107
  ```ts
163
- hooks: {
164
- upload: {
165
- guard: async ({ request, fileSize }) => {
166
- const { userId } = await getSession(request);
167
- const used = await db.storage.getUsed(userId);
168
- if (fileSize && used + fileSize > QUOTA)
169
- throw Object.assign(new Error("Quota exceeded"), { status: 403 });
170
- },
108
+ upload: {
109
+ enabled: true,
110
+ guard: async ({ request, fileSize }) => {
111
+ const { userId } = await getSession(request);
112
+ const used = await db.storage.getUsed(userId);
113
+ if (fileSize && used + fileSize > QUOTA)
114
+ throw Object.assign(new Error("Quota exceeded"), { status: 403 });
171
115
  },
172
- }
116
+ },
173
117
  ```
174
118
 
119
+ ````
120
+
175
121
  ## Multipart cost attacks
176
122
 
177
123
  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).
@@ -179,24 +125,24 @@ S3 presigned `UploadPart` URLs cannot enforce per-part size. A malicious client
179
125
  **Mitigation: track uploads in a database + cron cleanup**
180
126
 
181
127
  ```ts
182
- hooks: {
183
- multipart: {
184
- // Guard runs on init, part, complete, and abort.
185
- // fileSize is only present during init — use it to limit open sessions.
186
- guard: async ({ request, fileSize }) => {
187
- if (!fileSize) return;
188
- const { userId } = await getSession(request);
189
- const pending = await db.upload.count({ where: { userId, status: "pending" } });
190
- if (pending >= 3)
191
- throw Object.assign(new Error("Too many pending uploads"), { status: 429 });
192
- },
128
+ multipart: {
129
+ enabled: true,
130
+ // Guard runs on init, part, complete, and abort.
131
+ // fileSize is only present during init — use it to limit open sessions.
132
+ guard: async ({ request, fileSize }) => {
133
+ if (!fileSize) return;
134
+ const { userId } = await getSession(request);
135
+ const pending = await db.upload.count({ where: { userId, status: "pending" } });
136
+ if (pending >= 3)
137
+ throw Object.assign(new Error("Too many pending uploads"), { status: 429 });
138
+ },
193
139
 
194
- onInit: async ({ request, key, uploadId, contentType, fileSize }) => {
195
- const { userId } = await getSession(request);
196
- await db.upload.create({
197
- data: { key, userId, uploadId, contentType, declaredSize: fileSize, status: "pending" },
198
- });
199
- },
140
+ onInit: async ({ request, key, uploadId, contentType, fileSize }) => {
141
+ const { userId } = await getSession(request);
142
+ await db.upload.create({
143
+ data: { key, userId, uploadId, contentType, declaredSize: fileSize, status: "pending" },
144
+ });
145
+ },
200
146
 
201
147
  onComplete: async ({ key, contentLength, contentType, eTag }) => {
202
148
  await db.upload.update({
@@ -210,7 +156,7 @@ hooks: {
210
156
  },
211
157
  },
212
158
  }
213
- ```
159
+ ````
214
160
 
215
161
  **Cron job** — abort stale uploads and release S3 part storage:
216
162
 
@@ -1,3 +1,3 @@
1
- import {createPresignedPost}from'@aws-sdk/s3-presigned-post';import {HeadObjectCommand,GetObjectCommand,DeleteObjectCommand,CreateMultipartUploadCommand,UploadPartCommand,CompleteMultipartUploadCommand,AbortMultipartUploadCommand}from'@aws-sdk/client-s3';import {getSignedUrl}from'@aws-sdk/s3-request-presigner';function S(e){let a=e.replace(/[^\x20-\x7E]/g,"_").replace(/["\\]/g,"_"),t=encodeURIComponent(e);return `attachment; filename="${a}"; filename*=UTF-8''${t}`}async function c(e){try{let a=await e.json();return a&&typeof a=="object"?a:null}catch{return null}}function l(e,a){let t=typeof e=="string"?e.trim():"";return t||Response.json({message:`${a} is required`},{status:400})}function b(e){let a=Number(e);return Number.isFinite(a)&&a>0?Math.floor(a):600}function d(e){return async a=>{try{return await e(a)}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 p(e,a){if(!e)return null;try{return await e(a),null}catch(t){let r=t instanceof Error?t.message:"Forbidden",n=typeof t?.status=="number"?t.status:403;return Response.json({message:r},{status:n})}}var F=5*1024*1024*1024;function w(e){return d(async a=>{let t=await c(a);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let n=t.bucket?.trim()||e.defaultBucket,s=b(t.expiresIn),o=t.acl==="public-read"?"public-read":"private",u=t.contentType?.trim()||"application/octet-stream",i=typeof t.fileSize=="number"&&t.fileSize>0?Math.floor(t.fileSize):null;if(i!==null&&e.maxFileSize&&i>e.maxFileSize)return Response.json({message:`File size (${i} bytes) exceeds the maximum allowed size of ${e.maxFileSize} bytes`},{status:413});let m=await p(e.hooks?.upload?.guard,{request:a,key:r,bucket:n,contentType:t.contentType,fileSize:i??void 0,metadata:t.metadata,acl:o});if(m)return m;let y={acl:o,"Content-Type":u};if(t.fileName&&(y["Content-Disposition"]=S(t.fileName)),t.metadata)for(let[E,B]of Object.entries(t.metadata))y[`x-amz-meta-${E}`]=B;let R=i??1,f=i??e.maxFileSize??F,{url:k,fields:z}=await createPresignedPost(e.s3,{Bucket:n,Key:r,Conditions:[["content-length-range",R,f]],Fields:y,Expires:s});return await e.hooks?.upload?.onPresigned?.({request:a,key:r,bucket:n,contentType:t.contentType,metadata:t.metadata,acl:o,url:k,expiresIn:s}),Response.json({bucket:n,key:r,url:k,fields:z,expiresIn:s})})}function H(e){return d(async a=>{let t=await c(a);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let n=t.bucket?.trim()||e.defaultBucket,s=await p(e.hooks?.upload?.guard,{request:a,key:r,bucket:n});if(s)return s;let o=await e.s3.send(new HeadObjectCommand({Bucket:n,Key:r})),u={request:a,key:r,bucket:n,contentType:o.ContentType,contentLength:o.ContentLength??0,eTag:o.ETag?.replace(/"/g,""),metadata:o.Metadata};return await e.hooks?.upload?.onUploaded?.(u),Response.json({key:r,bucket:n,contentType:u.contentType,contentLength:u.contentLength,eTag:u.eTag})})}function h(e){return d(async a=>{let{searchParams:t}=new URL(a.url),r=t.get("key")?.trim();if(!r)return Response.json({message:"key query parameter is required"},{status:400});let n=t.get("bucket")?.trim()||e.defaultBucket,s=b(t.get("expiresIn")),o=t.get("fileName")?.trim(),u=await p(e.hooks?.download?.guard,{request:a,key:r,bucket:n,fileName:o||void 0});if(u)return u;try{await e.s3.send(new HeadObjectCommand({Bucket:n,Key:r}));}catch(m){let y=m?.name;if(y==="NoSuchKey"||y==="NotFound")return Response.json({message:"Object not found"},{status:404});throw m}let i=await getSignedUrl(e.s3,new GetObjectCommand({Bucket:n,Key:r,ResponseContentDisposition:o?`attachment; filename="${o}"`:"attachment"}),{expiresIn:s});return await e.hooks?.download?.onPresigned?.({request:a,key:r,bucket:n,fileName:o||void 0,url:i,expiresIn:s}),Response.json({bucket:n,key:r,url:i,expiresIn:s})})}function C(e){return d(async a=>{let{searchParams:t}=new URL(a.url),r=t.get("key")?.trim();if(!r)return Response.json({message:"key query parameter is required"},{status:400});let n=t.get("bucket")?.trim()||e.defaultBucket,s=await p(e.hooks?.delete?.guard,{request:a,key:r,bucket:n});if(s)return s;try{await e.s3.send(new HeadObjectCommand({Bucket:n,Key:r}));}catch{return Response.json({message:"Object not found"},{status:404})}return await e.s3.send(new DeleteObjectCommand({Bucket:n,Key:r})),await e.hooks?.delete?.onDeleted?.({request:a,key:r,bucket:n}),Response.json({success:!0,bucket:n,key:r})})}function x(e){return d(async a=>{let t=await c(a);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let n=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 u=await p(e.hooks?.multipart?.guard,{request:a,key:r,bucket:n,fileSize:o});if(u)return u;let{UploadId:i}=await e.s3.send(new CreateMultipartUploadCommand({Bucket:n,Key:r,ContentType:t.contentType,ContentDisposition:t.fileName?S(t.fileName):void 0,Metadata:t.metadata,ACL:s}));return await e.hooks?.multipart?.onInit?.({request:a,key:r,bucket:n,uploadId:i,contentType:t.contentType,fileSize:o,metadata:t.metadata,acl:s}),Response.json({bucket:n,key:r,uploadId:i},{status:201})})}function I(e){return d(async a=>{let t=await c(a);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let n=l(t.uploadId,"uploadId");if(n instanceof Response)return n;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,u=b(t.expiresIn),i=await p(e.hooks?.multipart?.guard,{request:a,key:r,bucket:o});if(i)return i;let m=await getSignedUrl(e.s3,new UploadPartCommand({Bucket:o,Key:r,UploadId:n,PartNumber:s}),{expiresIn:u});return Response.json({presignedUrl:m,partNumber:s,uploadId:n,bucket:o,expiresIn:u})})}function P(e){return d(async a=>{let t=await c(a);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let n=l(t.uploadId,"uploadId");if(n instanceof Response)return n;let s=(Array.isArray(t.parts)?t.parts:[]).map(({partNumber:f,eTag:k})=>({PartNumber:Number(f),ETag:String(k)})).filter(f=>Number.isInteger(f.PartNumber)&&f.ETag).sort((f,k)=>f.PartNumber-k.PartNumber);if(!s.length)return Response.json({message:"At least one valid part is required"},{status:400});let o=t.bucket?.trim()||e.defaultBucket,u=await p(e.hooks?.multipart?.guard,{request:a,key:r,bucket:o});if(u)return u;await e.s3.send(new CompleteMultipartUploadCommand({Bucket:o,Key:r,UploadId:n,MultipartUpload:{Parts:s}}));let i=await e.s3.send(new HeadObjectCommand({Bucket:o,Key:r})),m=i.ContentLength??0,y=i.ContentType,R=i.ETag?.replace(/"/g,"");return e.maxFileSize&&m>e.maxFileSize?(await e.s3.send(new DeleteObjectCommand({Bucket:o,Key:r})).catch(()=>{}),Response.json({message:`File size (${m} bytes) exceeds the maximum allowed size of ${e.maxFileSize} bytes`},{status:422})):(await e.hooks?.multipart?.onComplete?.({request:a,key:r,bucket:o,uploadId:n,contentLength:m,contentType:y,eTag:R}),Response.json({bucket:o,key:r,uploadId:n,contentLength:m,contentType:y,eTag:R}))})}function T(e){return d(async a=>{let t=await c(a);if(!t)return Response.json({message:"Invalid JSON payload"},{status:400});let r=l(t.key,"key");if(r instanceof Response)return r;let n=l(t.uploadId,"uploadId");if(n instanceof Response)return n;let s=t.bucket?.trim()||e.defaultBucket,o=await p(e.hooks?.multipart?.guard,{request:a,key:r,bucket:s});return o||(await e.s3.send(new AbortMultipartUploadCommand({Bucket:s,Key:r,UploadId:n})),await e.hooks?.multipart?.onAbort?.({request:a,key:r,bucket:s,uploadId:n}),Response.json({bucket:s,key:r,uploadId:n,aborted:!0}))})}function j(e){return {presign:{upload:w(e),confirm:H(e),download:h(e)},multipart:{init:x(e),part:I(e),complete:P(e),abort:T(e)},delete:C(e)}}var g=()=>Response.json({message:"Not Found"},{status:404});function N(e){let a=j(e),t=e.basePath.replace(/\/$/,""),{features:r}=e;return async n=>{let s=await p(e.hooks?.guard,{request:n});if(s)return s;let u=new URL(n.url).pathname.slice(t.length).replace(/^\//,""),i=n.method;return i==="POST"&&u==="presign/upload"?r.upload?a.presign.upload(n):g():i==="POST"&&u==="presign/upload/confirm"?r.upload?a.presign.confirm(n):g():i==="GET"&&u==="presign/download"?r.download?a.presign.download(n):g():i==="DELETE"&&u==="delete"?r.delete?a.delete(n):g():i==="POST"&&u==="presign/multipart/init"?r.multipart?a.multipart.init(n):g():i==="POST"&&u==="presign/multipart/part"?r.multipart?a.multipart.part(n):g():i==="POST"&&u==="presign/multipart/complete"?r.multipart?a.multipart.complete(n):g():i==="POST"&&u==="presign/multipart/abort"?r.multipart?a.multipart.abort(n):g():Response.json({message:"Not Found"},{status:404})}}function Fe(e){return N(e)}
2
- export{Fe as createRouteHandler};//# sourceMappingURL=next.js.map
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
3
3
  //# sourceMappingURL=next.js.map
@@ -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/adapters/next.ts"],"names":["buildContentDisposition","fileName","ascii","encoded","parseBody","request","body","requireString","value","name","trimmed","normalizeExpiresIn","n","withS3ErrorHandler","handler","err","message","runHook","hook","context","status","MAX_UPLOAD_SIZE","createUploadHandler","config","key","bucket","expiresIn","acl","contentType","fileSize","guardResult","fields","k","v","rangeMin","rangeMax","url","signedFields","createPresignedPost","createConfirmHandler","head","HeadObjectCommand","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","features","subpath","method","createRouteHandler"],"mappings":"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","file":"next.js","sourcesContent":["/**\n * Builds a RFC 6266 `Content-Disposition` value for a file attachment.\n * Includes both the ASCII-safe `filename` fallback and the RFC 5987\n * `filename*` parameter for full Unicode support.\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\nexport 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 { createPresignedPost } from \"@aws-sdk/s3-presigned-post\";\nimport type { S3HandlerConfig } from \"../../types\";\nimport {\n parseBody,\n requireString,\n normalizeExpiresIn,\n buildContentDisposition,\n runHook,\n withS3ErrorHandler,\n} 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.hooks?.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 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.hooks?.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 { 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?.onUploaded?.(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, HeadObjectCommand } 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 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 ? `attachment; filename=\"${fileName}\"`\n : \"attachment\",\n }),\n { expiresIn },\n );\n\n await config.hooks?.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 \"../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({ message: \"Object not found\" }, { status: 404 });\n }\n\n await config.s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));\n\n await config.hooks?.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 buildContentDisposition,\n runHook,\n withS3ErrorHandler,\n} 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.hooks?.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.hooks?.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 \"../../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, HeadObjectCommand, DeleteObjectCommand } 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 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 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.hooks?.multipart?.onComplete?.({\n request,\n key,\n bucket,\n uploadId,\n contentLength,\n contentType,\n eTag,\n });\n\n return Response.json({ bucket, key, uploadId, contentLength, contentType, eTag });\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\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 const { features } = config;\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 features.upload ? handlers.presign.upload(request) : disabled();\n if (method === \"POST\" && subpath === \"presign/upload/confirm\")\n return features.upload ? handlers.presign.confirm(request) : disabled();\n if (method === \"GET\" && subpath === \"presign/download\")\n return features.download\n ? handlers.presign.download(request)\n : disabled();\n if (method === \"DELETE\" && subpath === \"delete\")\n return features.delete ? handlers.delete(request) : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/init\")\n return features.multipart ? handlers.multipart.init(request) : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/part\")\n return features.multipart ? handlers.multipart.part(request) : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/complete\")\n return features.multipart\n ? handlers.multipart.complete(request)\n : disabled();\n if (method === \"POST\" && subpath === \"presign/multipart/abort\")\n return features.multipart\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","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"]}
package/dist/api.d.ts CHANGED
@@ -29,6 +29,11 @@ export type UploadConfirmResponse = {
29
29
  contentType?: string;
30
30
  contentLength: number;
31
31
  eTag?: string;
32
+ acl: "private" | "public-read";
33
+ /** Display file name parsed from the object's Content-Disposition header. */
34
+ fileName?: string;
35
+ /** Public URL of the object. Only present when `acl` is `public-read` and `publicUrlBase` is configured on the server. */
36
+ publicUrl?: string;
32
37
  };
33
38
  export type S3Api = {
34
39
  upload: (payload: {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Builds a RFC 6266 `Content-Disposition` value for a file attachment.
3
+ *
4
+ * Includes both the ASCII-safe `filename` fallback and the RFC 5987
5
+ * `filename*` parameter for full Unicode support, so the original file
6
+ * name (including non-ASCII characters) is preserved across all browsers
7
+ * and HTTP clients.
8
+ *
9
+ * @example
10
+ * buildContentDisposition("résumé finale.pdf")
11
+ * // → 'attachment; filename="resume finale.pdf"; filename*=UTF-8\'\'r%C3%A9sum%C3%A9%20finale.pdf'
12
+ */
13
+ export declare function buildContentDisposition(fileName: string): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Builds the public URL for an S3 object.
3
+ *
4
+ * Returns `undefined` when:
5
+ * - the object is `"private"`, or
6
+ * - `publicUrlBase` is not provided.
7
+ *
8
+ * @param publicUrlBase - A function that returns the base URL for a given bucket.
9
+ * Matches the `publicUrlBase` field of `S3HandlerConfig`.
10
+ */
11
+ export declare function buildPublicUrl(publicUrlBase: ((bucket: string) => string) | undefined, bucket: string, key: string, acl: "public-read" | "private"): string | undefined;
@@ -0,0 +1,3 @@
1
+ export { buildContentDisposition } from "./build-content-disposition";
2
+ export { buildPublicUrl } from "./build-public-url";
3
+ export { parseFileName } from "./parse-file-name";
@@ -0,0 +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
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Parses the display file name from a `Content-Disposition` header value.
3
+ *
4
+ * Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over
5
+ * the plain ASCII `filename="…"` fallback.
6
+ *
7
+ * Returns `undefined` when no filename can be extracted.
8
+ */
9
+ export declare function parseFileName(contentDisposition: string | undefined): string | undefined;
@@ -0,0 +1 @@
1
+ export { resolveObjectAcl } from "./resolve-object-acl";