@dimah-s3/server 0.8.5 → 1.0.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.
Files changed (72) hide show
  1. package/dist/adapters/express.js +13 -1
  2. package/dist/adapters/express.js.map +1 -1
  3. package/dist/adapters/fastify.js +13 -1
  4. package/dist/adapters/fastify.js.map +1 -1
  5. package/dist/adapters/node.js +13 -1
  6. package/dist/adapters/node.js.map +1 -1
  7. package/dist/adapters/svelte-kit.d.ts +2 -0
  8. package/dist/adapters/svelte-kit.d.ts.map +1 -1
  9. package/dist/adapters/svelte-kit.js.map +1 -1
  10. package/dist/adapters/types.d.ts.map +1 -1
  11. package/dist/api/assert-feature-enabled.d.ts.map +1 -1
  12. package/dist/api/bind-endpoints.d.ts.map +1 -1
  13. package/dist/api/context.d.ts.map +1 -1
  14. package/dist/api/index.d.ts +2 -2
  15. package/dist/api/index.d.ts.map +1 -1
  16. package/dist/api/index.js +218 -147
  17. package/dist/api/index.js.map +1 -1
  18. package/dist/{router.d.ts → api/router.d.ts} +3 -2
  19. package/dist/api/router.d.ts.map +1 -0
  20. package/dist/api/routes/confirm.d.ts.map +1 -1
  21. package/dist/api/routes/delete.d.ts.map +1 -1
  22. package/dist/api/routes/download.d.ts.map +1 -1
  23. package/dist/api/routes/index.d.ts +1 -7
  24. package/dist/api/routes/index.d.ts.map +1 -1
  25. package/dist/api/routes/multipart/abort.d.ts +1 -5
  26. package/dist/api/routes/multipart/abort.d.ts.map +1 -1
  27. package/dist/api/routes/multipart/complete.d.ts.map +1 -1
  28. package/dist/api/routes/multipart/init.d.ts +0 -2
  29. package/dist/api/routes/multipart/init.d.ts.map +1 -1
  30. package/dist/api/routes/multipart/list-parts.d.ts.map +1 -1
  31. package/dist/api/routes/multipart/part.d.ts.map +1 -1
  32. package/dist/api/routes/upload.d.ts.map +1 -1
  33. package/dist/errors.d.ts +4 -2
  34. package/dist/errors.d.ts.map +1 -1
  35. package/dist/helpers/expires.d.ts +6 -0
  36. package/dist/helpers/expires.d.ts.map +1 -0
  37. package/dist/helpers/features.d.ts +34 -0
  38. package/dist/helpers/features.d.ts.map +1 -0
  39. package/dist/helpers/head-object.d.ts +11 -0
  40. package/dist/helpers/head-object.d.ts.map +1 -0
  41. package/dist/{internal-helpers.d.ts → helpers/hooks.d.ts} +1 -4
  42. package/dist/helpers/hooks.d.ts.map +1 -0
  43. package/dist/helpers/index.d.ts +12 -1
  44. package/dist/helpers/index.d.ts.map +1 -1
  45. package/dist/helpers/is-aws-not-found.d.ts +3 -1
  46. package/dist/helpers/is-aws-not-found.d.ts.map +1 -1
  47. package/dist/helpers/list-parts.d.ts +8 -0
  48. package/dist/helpers/list-parts.d.ts.map +1 -0
  49. package/dist/helpers/request.d.ts +3 -0
  50. package/dist/helpers/request.d.ts.map +1 -0
  51. package/dist/helpers/resolve-request-acl.d.ts +10 -0
  52. package/dist/helpers/resolve-request-acl.d.ts.map +1 -0
  53. package/dist/helpers/resolve-target.d.ts +2 -33
  54. package/dist/helpers/resolve-target.d.ts.map +1 -1
  55. package/dist/index.d.ts +1 -2
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +188 -111
  58. package/dist/index.js.map +1 -1
  59. package/dist/plugin/apply-plugins.d.ts.map +1 -1
  60. package/dist/plugin/types.d.ts +5 -5
  61. package/dist/plugin/types.d.ts.map +1 -1
  62. package/dist/plugins.js +184 -104
  63. package/dist/plugins.js.map +1 -1
  64. package/dist/types/config.d.ts +27 -9
  65. package/dist/types/config.d.ts.map +1 -1
  66. package/dist/types/hook-contexts.d.ts +8 -8
  67. package/dist/types/hook-contexts.d.ts.map +1 -1
  68. package/dist/types/index.d.ts +1 -1
  69. package/dist/types/index.d.ts.map +1 -1
  70. package/package.json +6 -6
  71. package/dist/internal-helpers.d.ts.map +0 -1
  72. package/dist/router.d.ts.map +0 -1
package/dist/api/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createMiddleware, createEndpoint } from 'better-call';
2
- import { S3_API_ROUTES, isAPIError, DimahS3Error, S3_ERROR_CODES, confirmBodySchema, deleteQuerySchema, downloadQuerySchema, multipartAbortBodySchema, multipartCompleteBodySchema, multipartInitBodySchema, multipartListPartsQuerySchema, multipartSignPartBodySchema, uploadBodySchema, parseFileName, buildContentDisposition } from '@dimah-s3/core';
3
- import { HeadObjectCommand, DeleteObjectCommand, GetObjectCommand, AbortMultipartUploadCommand, ListPartsCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, UploadPartCommand, PutObjectCommand, GetObjectAclCommand } from '@aws-sdk/client-s3';
2
+ import { S3_API_ROUTES, isAPIError, DimahS3Error, S3_ERROR_CODES, confirmBodySchema, deleteQuerySchema, downloadQuerySchema, multipartAbortBodySchema, multipartCompleteBodySchema, multipartInitBodySchema, multipartListPartsQuerySchema, multipartSignPartBodySchema, uploadBodySchema, parseFileName, S3_MAX_EXPIRES_IN, buildContentDisposition, S3_MAX_POST_OBJECT_BYTES, S3_DEFAULT_EXPIRES_IN } from '@dimah-s3/core';
3
+ import { DeleteObjectCommand, GetObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, PutObjectCommand, GetObjectAclCommand, ListPartsCommand, CompleteMultipartUploadCommand, HeadObjectCommand, AbortMultipartUploadCommand } from '@aws-sdk/client-s3';
4
4
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
5
5
  import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
6
6
 
@@ -39,11 +39,69 @@ var errors = {
39
39
  "BAD_REQUEST",
40
40
  S3_ERROR_CODES.FILE_SIZE_REQUIRED_MULTIPART
41
41
  ),
42
+ multipartPartMissing: (partNumber) => DimahS3Error.from("BAD_REQUEST", {
43
+ code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,
44
+ message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(
45
+ "{partNumber}",
46
+ String(partNumber)
47
+ ),
48
+ params: { partNumber }
49
+ }),
50
+ payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
51
+ ...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,
52
+ message
53
+ }),
42
54
  validationError: (message) => DimahS3Error.from("BAD_REQUEST", {
43
55
  code: S3_ERROR_CODES.VALIDATION_ERROR.code,
44
56
  message
45
57
  })
46
58
  };
59
+
60
+ // src/helpers/is-aws-not-found.ts
61
+ function isAwsNotFound(err) {
62
+ const e = err;
63
+ return e?.name === "NoSuchKey" || e?.name === "NotFound" || e?.name === "NoSuchUpload" || e?.Code === "NoSuchKey" || e?.Code === "NotFound" || e?.Code === "NoSuchUpload" || e?.$metadata?.httpStatusCode === 404;
64
+ }
65
+ async function sendOrObjectNotFound(send) {
66
+ try {
67
+ return await send();
68
+ } catch (err) {
69
+ if (isAwsNotFound(err)) throw errors.objectNotFound();
70
+ throw err;
71
+ }
72
+ }
73
+ var DEFAULT_ACL_LOOKUP_TIMEOUT_MS = 1500;
74
+ async function resolveObjectAcl(s3, bucket, key, timeoutMs = DEFAULT_ACL_LOOKUP_TIMEOUT_MS) {
75
+ const controller = new AbortController();
76
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
77
+ try {
78
+ const result = await s3.send(
79
+ new GetObjectAclCommand({ Bucket: bucket, Key: key }),
80
+ { abortSignal: controller.signal }
81
+ );
82
+ const isPublic = result.Grants?.some(
83
+ (g) => g.Grantee?.URI === "http://acs.amazonaws.com/groups/global/AllUsers" && (g.Permission === "READ" || g.Permission === "FULL_CONTROL")
84
+ );
85
+ return isPublic ? "public-read" : "private";
86
+ } catch {
87
+ return void 0;
88
+ } finally {
89
+ clearTimeout(timeout);
90
+ }
91
+ }
92
+
93
+ // src/helpers/resolve-request-acl.ts
94
+ function resolveRequestAcl(policy, requested) {
95
+ if (policy?.acl) return policy.acl;
96
+ if (policy?.allowClientAcl && requested) return requested;
97
+ return "private";
98
+ }
99
+ function normalizeExpiresIn(value, maxExpiresIn = S3_MAX_EXPIRES_IN) {
100
+ const n = Number(value);
101
+ const expires = Number.isFinite(n) && n > 0 ? Math.floor(n) : S3_DEFAULT_EXPIRES_IN;
102
+ const max = Number.isFinite(maxExpiresIn) && maxExpiresIn > 0 ? Math.floor(maxExpiresIn) : S3_MAX_EXPIRES_IN;
103
+ return Math.min(expires, max);
104
+ }
47
105
  async function runHook(hook, context) {
48
106
  if (!hook) return;
49
107
  try {
@@ -69,84 +127,12 @@ async function runLifecycleHook(hook, context) {
69
127
  throw errors.internalError();
70
128
  }
71
129
  }
72
- function normalizeExpiresIn(value) {
73
- const n = Number(value);
74
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : 600;
75
- }
130
+
131
+ // src/helpers/request.ts
76
132
  function requestFromHeaders(headers) {
77
133
  return new Request("http://dimah-s3.local", { headers });
78
134
  }
79
135
 
80
- // src/api/create-s3-middleware.ts
81
- var s3ContextMiddleware = createMiddleware(async (ctx) => {
82
- const injected = ctx.context;
83
- if (!injected?.config) {
84
- throw new Error(
85
- "createS3Endpoint requires dimahS3 router context. Call endpoints through dimahS3().api or the HTTP handler."
86
- );
87
- }
88
- const request = ctx.request ?? injected.request ?? requestFromHeaders(ctx.headers);
89
- await runHook(injected.config.guard, { request });
90
- return {
91
- config: injected.config,
92
- request
93
- };
94
- });
95
- var createS3Middleware = createMiddleware.create({
96
- use: [s3ContextMiddleware]
97
- });
98
-
99
- // src/api/create-s3-endpoint.ts
100
- var createEndpointWithContext = createEndpoint.create({
101
- use: [s3ContextMiddleware]
102
- });
103
- function withS3Validation(options) {
104
- return {
105
- ...options,
106
- onValidationError: options.onValidationError ?? (({ message }) => {
107
- throw errors.validationError(message);
108
- })
109
- };
110
- }
111
- var createS3Endpoint = ((pathOrOptions, optionsOrHandler, maybeHandler) => {
112
- if (typeof pathOrOptions === "string") {
113
- return createEndpointWithContext(
114
- pathOrOptions,
115
- withS3Validation(optionsOrHandler),
116
- maybeHandler
117
- );
118
- }
119
- return createEndpointWithContext(
120
- withS3Validation(pathOrOptions),
121
- optionsOrHandler
122
- );
123
- });
124
-
125
- // src/helpers/is-aws-not-found.ts
126
- function isAwsNotFound(err) {
127
- const e = err;
128
- return e?.name === "NoSuchKey" || e?.name === "NotFound" || e?.Code === "NoSuchKey" || e?.Code === "NotFound" || e?.$metadata?.httpStatusCode === 404;
129
- }
130
- var DEFAULT_ACL_LOOKUP_TIMEOUT_MS = 1500;
131
- async function resolveObjectAcl(s3, bucket, key, timeoutMs = DEFAULT_ACL_LOOKUP_TIMEOUT_MS) {
132
- const controller = new AbortController();
133
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
134
- try {
135
- const result = await s3.send(
136
- new GetObjectAclCommand({ Bucket: bucket, Key: key }),
137
- { abortSignal: controller.signal }
138
- );
139
- const isPublic = result.Grants?.some(
140
- (g) => g.Grantee?.URI === "http://acs.amazonaws.com/groups/global/AllUsers" && (g.Permission === "READ" || g.Permission === "FULL_CONTROL")
141
- );
142
- return isPublic ? "public-read" : "private";
143
- } catch {
144
- return void 0;
145
- } finally {
146
- clearTimeout(timeout);
147
- }
148
- }
149
-
150
136
  // src/helpers/resolve-target.ts
151
137
  function assertSafeObjectKey(key) {
152
138
  const trimmed = key.replace(/^\/+/u, "").replace(/\/+$/u, "");
@@ -198,10 +184,100 @@ async function resolveRequestTarget(config, policy, input) {
198
184
  });
199
185
  return { key, bucket };
200
186
  }
187
+ async function headObjectOrNotFound(client, bucket, key) {
188
+ return sendOrObjectNotFound(
189
+ () => client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }))
190
+ );
191
+ }
192
+ async function headObjectAfterMultipartComplete(client, bucket, key) {
193
+ const send = () => sendOrObjectNotFound(
194
+ () => client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }))
195
+ );
196
+ let head = await send();
197
+ for (let attempt = 0; attempt < 4 && !head.ContentLength; attempt++) {
198
+ await new Promise((r) => setTimeout(r, 250 * 2 ** attempt));
199
+ head = await send();
200
+ }
201
+ return head;
202
+ }
203
+ function requireContentLength(head) {
204
+ if (typeof head.ContentLength !== "number") {
205
+ throw errors.internalError();
206
+ }
207
+ return head.ContentLength;
208
+ }
209
+ async function listAllParts(client, input) {
210
+ const parts = [];
211
+ let partNumberMarker;
212
+ let isTruncated = true;
213
+ while (isTruncated) {
214
+ const response = await sendOrObjectNotFound(
215
+ () => client.send(
216
+ new ListPartsCommand({
217
+ Bucket: input.bucket,
218
+ Key: input.key,
219
+ UploadId: input.uploadId,
220
+ ...partNumberMarker ? { PartNumberMarker: partNumberMarker } : {}
221
+ })
222
+ )
223
+ );
224
+ parts.push(...response.Parts ?? []);
225
+ isTruncated = response.IsTruncated === true;
226
+ partNumberMarker = isTruncated ? response.NextPartNumberMarker : void 0;
227
+ }
228
+ return parts;
229
+ }
230
+
231
+ // src/helpers/features.ts
201
232
  function isFeatureEnabled(config, feature) {
202
233
  return config[feature]?.enabled === true;
203
234
  }
204
235
 
236
+ // src/api/create-s3-middleware.ts
237
+ var s3ContextMiddleware = createMiddleware(async (ctx) => {
238
+ const injected = ctx.context;
239
+ if (!injected?.config) {
240
+ throw new Error(
241
+ "createS3Endpoint requires dimahS3 router context. Call endpoints through dimahS3().api or the HTTP handler."
242
+ );
243
+ }
244
+ const request = ctx.request ?? injected.request ?? requestFromHeaders(ctx.headers);
245
+ await runHook(injected.config.guard, { request });
246
+ return {
247
+ config: injected.config,
248
+ request
249
+ };
250
+ });
251
+ var createS3Middleware = createMiddleware.create({
252
+ use: [s3ContextMiddleware]
253
+ });
254
+
255
+ // src/api/create-s3-endpoint.ts
256
+ var createEndpointWithContext = createEndpoint.create({
257
+ use: [s3ContextMiddleware]
258
+ });
259
+ function withS3Validation(options) {
260
+ return {
261
+ ...options,
262
+ onValidationError: options.onValidationError ?? (({ message }) => {
263
+ throw errors.validationError(message);
264
+ })
265
+ };
266
+ }
267
+ var createS3Endpoint = ((pathOrOptions, optionsOrHandler, maybeHandler) => {
268
+ if (typeof pathOrOptions === "string") {
269
+ return createEndpointWithContext(
270
+ pathOrOptions,
271
+ withS3Validation(optionsOrHandler),
272
+ maybeHandler
273
+ );
274
+ }
275
+ return createEndpointWithContext(
276
+ withS3Validation(pathOrOptions),
277
+ optionsOrHandler
278
+ );
279
+ });
280
+
205
281
  // src/api/assert-feature-enabled.ts
206
282
  function assertFeatureEnabled(config, feature) {
207
283
  if (!isFeatureEnabled(config, feature)) {
@@ -221,9 +297,7 @@ async function handleConfirm(config, input, request) {
221
297
  key,
222
298
  bucket
223
299
  });
224
- const head = await config.client.send(
225
- new HeadObjectCommand({ Bucket: bucket, Key: key })
226
- );
300
+ const head = await headObjectOrNotFound(config.client, bucket, key);
227
301
  const acl = config.resolveObjectAcl ? await resolveObjectAcl(config.client, bucket, key) : void 0;
228
302
  const fileName = parseFileName(head.ContentDisposition);
229
303
  const context = {
@@ -231,7 +305,7 @@ async function handleConfirm(config, input, request) {
231
305
  key,
232
306
  bucket,
233
307
  contentType: head.ContentType,
234
- contentLength: head.ContentLength ?? 0,
308
+ contentLength: requireContentLength(head),
235
309
  eTag: head.ETag?.replace(/"/g, ""),
236
310
  metadata: head.Metadata,
237
311
  acl,
@@ -272,16 +346,7 @@ async function handleDelete(config, input, request) {
272
346
  key,
273
347
  bucket
274
348
  });
275
- try {
276
- await config.client.send(
277
- new HeadObjectCommand({ Bucket: bucket, Key: key })
278
- );
279
- } catch (err) {
280
- if (isAwsNotFound(err)) {
281
- throw errors.objectNotFound();
282
- }
283
- throw err;
284
- }
349
+ await headObjectOrNotFound(config.client, bucket, key);
285
350
  await config.client.send(
286
351
  new DeleteObjectCommand({ Bucket: bucket, Key: key })
287
352
  );
@@ -303,7 +368,7 @@ async function handleDownload(config, input, request) {
303
368
  bucket: input.bucket,
304
369
  fileName: input.fileName
305
370
  });
306
- const expiresIn = normalizeExpiresIn(input.expiresIn);
371
+ const expiresIn = normalizeExpiresIn(input.expiresIn, config.maxExpiresIn);
307
372
  const fileName = input.fileName;
308
373
  await runHook(config.download?.guard, {
309
374
  request,
@@ -311,16 +376,7 @@ async function handleDownload(config, input, request) {
311
376
  bucket,
312
377
  fileName
313
378
  });
314
- try {
315
- await config.client.send(
316
- new HeadObjectCommand({ Bucket: bucket, Key: key })
317
- );
318
- } catch (err) {
319
- if (isAwsNotFound(err)) {
320
- throw errors.objectNotFound();
321
- }
322
- throw err;
323
- }
379
+ await headObjectOrNotFound(config.client, bucket, key);
324
380
  const url = await getSignedUrl(
325
381
  config.client,
326
382
  new GetObjectCommand({
@@ -361,12 +417,14 @@ async function handleAbort(config, input, request) {
361
417
  bucket,
362
418
  uploadId
363
419
  });
364
- await config.client.send(
365
- new AbortMultipartUploadCommand({
366
- Bucket: bucket,
367
- Key: key,
368
- UploadId: uploadId
369
- })
420
+ await sendOrObjectNotFound(
421
+ () => config.client.send(
422
+ new AbortMultipartUploadCommand({
423
+ Bucket: bucket,
424
+ Key: key,
425
+ UploadId: uploadId
426
+ })
427
+ )
370
428
  );
371
429
  await runLifecycleHook(config.multipart?.onAbort, {
372
430
  request,
@@ -400,32 +458,34 @@ async function handleComplete(config, input, request) {
400
458
  uploadId,
401
459
  parts: partRefs
402
460
  });
403
- const listed = await config.client.send(
404
- new ListPartsCommand({ Bucket: bucket, Key: key, UploadId: uploadId })
405
- );
406
- const listedParts = listed.Parts ?? [];
461
+ const listedParts = await listAllParts(config.client, {
462
+ bucket,
463
+ key,
464
+ uploadId
465
+ });
407
466
  const completeParts = parts.map((partNumber) => {
408
467
  const found = listedParts.find((p) => p.PartNumber === partNumber);
409
- return { PartNumber: partNumber, ETag: found?.ETag ?? "" };
468
+ if (!found?.ETag) {
469
+ throw errors.multipartPartMissing(partNumber);
470
+ }
471
+ return { PartNumber: partNumber, ETag: found.ETag };
410
472
  });
411
- const completeResult = await config.client.send(
412
- new CompleteMultipartUploadCommand({
413
- Bucket: bucket,
414
- Key: key,
415
- UploadId: uploadId,
416
- MultipartUpload: { Parts: completeParts }
417
- })
473
+ const completeResult = await sendOrObjectNotFound(
474
+ () => config.client.send(
475
+ new CompleteMultipartUploadCommand({
476
+ Bucket: bucket,
477
+ Key: key,
478
+ UploadId: uploadId,
479
+ MultipartUpload: { Parts: completeParts }
480
+ })
481
+ )
418
482
  );
419
- let head = await config.client.send(
420
- new HeadObjectCommand({ Bucket: bucket, Key: key })
483
+ const head = await headObjectAfterMultipartComplete(
484
+ config.client,
485
+ bucket,
486
+ key
421
487
  );
422
- for (let attempt = 0; attempt < 4 && !head.ContentLength; attempt++) {
423
- await new Promise((r) => setTimeout(r, 250 * 2 ** attempt));
424
- head = await config.client.send(
425
- new HeadObjectCommand({ Bucket: bucket, Key: key })
426
- );
427
- }
428
- const contentLength = head.ContentLength ?? 0;
488
+ const contentLength = requireContentLength(head);
429
489
  const contentType = head.ContentType;
430
490
  const eTag = (head.ETag ?? completeResult.ETag ?? "").replace(/"/g, "");
431
491
  const metadata = head.Metadata ?? {};
@@ -477,7 +537,7 @@ async function handleMultipartInit(config, input, request) {
477
537
  fileName: input.fileName,
478
538
  contentType: input.contentType
479
539
  });
480
- const acl = input.acl === "public-read" ? "public-read" : "private";
540
+ const acl = resolveRequestAcl(config.multipart, input.acl);
481
541
  const fileSize = typeof input.fileSize === "number" && input.fileSize > 0 ? Math.floor(input.fileSize) : void 0;
482
542
  if (config.multipart?.requireFileSize && fileSize === void 0) {
483
543
  throw errors.fileSizeRequiredMultipart();
@@ -486,7 +546,11 @@ async function handleMultipartInit(config, input, request) {
486
546
  request,
487
547
  key,
488
548
  bucket,
489
- fileSize
549
+ fileSize,
550
+ contentType: input.contentType,
551
+ metadata: input.metadata,
552
+ acl,
553
+ fileName: input.fileName
490
554
  });
491
555
  const { UploadId } = await config.client.send(
492
556
  new CreateMultipartUploadCommand({
@@ -498,6 +562,9 @@ async function handleMultipartInit(config, input, request) {
498
562
  ACL: acl
499
563
  })
500
564
  );
565
+ if (!UploadId) {
566
+ throw errors.internalError();
567
+ }
501
568
  await runLifecycleHook(config.multipart?.onInit, {
502
569
  request,
503
570
  key,
@@ -537,14 +604,8 @@ async function handleListParts(config, input, request) {
537
604
  bucket,
538
605
  uploadId
539
606
  });
540
- const response = await config.client.send(
541
- new ListPartsCommand({
542
- Bucket: bucket,
543
- Key: key,
544
- UploadId: uploadId
545
- })
546
- );
547
- const parts = (response.Parts ?? []).map((p) => ({
607
+ const listed = await listAllParts(config.client, { bucket, key, uploadId });
608
+ const parts = listed.map((p) => ({
548
609
  partNumber: p.PartNumber ?? 0,
549
610
  size: p.Size ?? 0,
550
611
  eTag: (p.ETag ?? "").replace(/"/g, "")
@@ -574,7 +635,7 @@ async function handleSignPart(config, input, request) {
574
635
  });
575
636
  const uploadId = input.uploadId;
576
637
  const partNumber = input.partNumber;
577
- const expiresIn = normalizeExpiresIn(input.expiresIn);
638
+ const expiresIn = normalizeExpiresIn(input.expiresIn, config.maxExpiresIn);
578
639
  const partSize = typeof input.partSize === "number" && input.partSize > 0 ? Math.floor(input.partSize) : null;
579
640
  await runHook(config.multipart?.partGuard, {
580
641
  request,
@@ -615,6 +676,14 @@ var multipartPart = createS3Endpoint(
615
676
  return handleSignPart(ctx.context.config, ctx.body, ctx.context.request);
616
677
  }
617
678
  );
679
+ function putMetadataHeaders(metadata) {
680
+ if (!metadata) return {};
681
+ const headers = {};
682
+ for (const [k, v] of Object.entries(metadata)) {
683
+ headers[`x-amz-meta-${k}`] = v;
684
+ }
685
+ return headers;
686
+ }
618
687
  async function handleUpload(config, input, request) {
619
688
  const { key, bucket } = await resolveRequestTarget(config, config.upload, {
620
689
  request,
@@ -623,10 +692,13 @@ async function handleUpload(config, input, request) {
623
692
  fileName: input.fileName,
624
693
  contentType: input.contentType
625
694
  });
626
- const expiresIn = normalizeExpiresIn(input.expiresIn);
627
- const acl = input.acl === "public-read" ? "public-read" : "private";
695
+ const expiresIn = normalizeExpiresIn(input.expiresIn, config.maxExpiresIn);
696
+ const acl = resolveRequestAcl(config.upload, input.acl);
628
697
  const contentType = input.contentType ?? "application/octet-stream";
629
698
  const fileSize = typeof input.fileSize === "number" && input.fileSize > 0 ? Math.floor(input.fileSize) : null;
699
+ if (config.upload?.requireFileSize && fileSize === null) {
700
+ throw errors.fileSizeRequiredUpload();
701
+ }
630
702
  await runHook(config.upload?.guard, {
631
703
  request,
632
704
  key,
@@ -638,12 +710,10 @@ async function handleUpload(config, input, request) {
638
710
  fileName: input.fileName
639
711
  });
640
712
  const method = config.upload?.method ?? "POST";
641
- if (method === "PUT" && config.upload?.requireFileSize && fileSize === null) {
642
- throw errors.fileSizeRequiredUpload();
643
- }
644
713
  if (method === "PUT") {
645
714
  const putHeaders = {
646
- "Content-Type": contentType
715
+ "Content-Type": contentType,
716
+ ...putMetadataHeaders(input.metadata)
647
717
  };
648
718
  if (input.fileName) {
649
719
  putHeaders["Content-Disposition"] = buildContentDisposition(
@@ -657,6 +727,7 @@ async function handleUpload(config, input, request) {
657
727
  Key: key,
658
728
  ContentType: contentType,
659
729
  ACL: acl,
730
+ Metadata: input.metadata,
660
731
  ...input.fileName ? { ContentDisposition: buildContentDisposition(input.fileName) } : {},
661
732
  ...fileSize !== null ? { ContentLength: fileSize } : {}
662
733
  }),
@@ -702,7 +773,7 @@ async function handleUpload(config, input, request) {
702
773
  {
703
774
  Bucket: bucket,
704
775
  Key: key,
705
- Conditions: rangeMax !== void 0 ? [["content-length-range", rangeMin, rangeMax]] : [["content-length-range", rangeMin, Number.MAX_SAFE_INTEGER]],
776
+ Conditions: rangeMax !== void 0 ? [["content-length-range", rangeMin, rangeMax]] : [["content-length-range", rangeMin, S3_MAX_POST_OBJECT_BYTES]],
706
777
  Fields: fields,
707
778
  Expires: expiresIn
708
779
  }
@@ -753,6 +824,6 @@ var CORE_ENDPOINT_NAMES = Object.keys(
753
824
  coreEndpoints
754
825
  );
755
826
 
756
- export { CORE_ENDPOINT_NAMES, coreEndpoints, createS3Endpoint, createS3Middleware, s3ContextMiddleware };
827
+ export { CORE_ENDPOINT_NAMES, createS3Endpoint, createS3Middleware };
757
828
  //# sourceMappingURL=index.js.map
758
829
  //# sourceMappingURL=index.js.map