@better-media/adapter-storage-s3 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,16 +3,28 @@
3
3
  var clientS3 = require('@aws-sdk/client-s3');
4
4
  var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
5
5
  var s3PresignedPost = require('@aws-sdk/s3-presigned-post');
6
+ var buffer = require('buffer');
6
7
  var stream = require('stream');
7
8
 
8
- // src/adapters/s3-storage.adapter.ts
9
+ // src/interfaces/s3.interface.ts
10
+ var EnumS3Accessibility = /* @__PURE__ */ ((EnumS3Accessibility2) => {
11
+ EnumS3Accessibility2["public"] = "public";
12
+ EnumS3Accessibility2["private"] = "private";
13
+ return EnumS3Accessibility2;
14
+ })(EnumS3Accessibility || {});
9
15
  var DEFAULT_EXPIRES_IN = 3600;
10
16
  var DEFAULT_MAX_SIZE_BYTES = 100 * 1024 * 1024;
11
17
  var DEFAULT_MIN_SIZE_BYTES = 1;
12
18
  var S3StorageAdapter = class {
13
19
  client;
14
20
  bucketResolver;
21
+ region;
22
+ endpoint;
23
+ forcePathStyle;
15
24
  constructor(config) {
25
+ this.region = config.region;
26
+ this.endpoint = config.endpoint;
27
+ this.forcePathStyle = config.forcePathStyle ?? (config.endpoint ? true : false);
16
28
  this.bucketResolver = typeof config.bucket === "function" ? config.bucket : () => config.bucket;
17
29
  this.client = new clientS3.S3Client({
18
30
  region: config.region,
@@ -22,7 +34,7 @@ var S3StorageAdapter = class {
22
34
  },
23
35
  ...config.endpoint && {
24
36
  endpoint: config.endpoint,
25
- forcePathStyle: config.forcePathStyle ?? true
37
+ forcePathStyle: this.forcePathStyle
26
38
  }
27
39
  });
28
40
  }
@@ -34,6 +46,21 @@ var S3StorageAdapter = class {
34
46
  getBucket(key) {
35
47
  return this.bucketResolver(key);
36
48
  }
49
+ getPublicUrl(bucket, key) {
50
+ if (this.endpoint) {
51
+ if (this.forcePathStyle) {
52
+ const baseUrl = this.endpoint.endsWith("/") ? this.endpoint.slice(0, -1) : this.endpoint;
53
+ return `${baseUrl}/${bucket}/${key}`;
54
+ }
55
+ try {
56
+ const url = new URL(this.endpoint);
57
+ return `${url.protocol}//${bucket}.${url.host}${url.pathname === "/" ? "" : url.pathname}/${key}`;
58
+ } catch {
59
+ return `${this.endpoint}/${bucket}/${key}`;
60
+ }
61
+ }
62
+ return `https://${bucket}.s3.${this.region}.amazonaws.com/${key}`;
63
+ }
37
64
  async get(key) {
38
65
  try {
39
66
  const response = await this.client.send(
@@ -45,7 +72,7 @@ var S3StorageAdapter = class {
45
72
  for await (const chunk of body) {
46
73
  chunks.push(chunk);
47
74
  }
48
- return Buffer.concat(chunks);
75
+ return buffer.Buffer.concat(chunks);
49
76
  } catch (err) {
50
77
  if (this.isNotFoundError(err)) return null;
51
78
  throw err;
@@ -77,6 +104,69 @@ var S3StorageAdapter = class {
77
104
  throw err;
78
105
  }
79
106
  }
107
+ async checkConnection() {
108
+ try {
109
+ const bucket = this.getBucket("");
110
+ await this.client.send(
111
+ new clientS3.HeadObjectCommand({ Bucket: bucket, Key: "connection-check-non-existent" })
112
+ );
113
+ return true;
114
+ } catch (err) {
115
+ if (this.isNotFoundError(err)) return true;
116
+ return false;
117
+ }
118
+ }
119
+ async checkBucket(_options) {
120
+ try {
121
+ const bucket = this.getBucket("");
122
+ await this.client.send(new clientS3.HeadObjectCommand({ Bucket: bucket, Key: "bucket-check" }));
123
+ return true;
124
+ } catch (err) {
125
+ if (this.isNotFoundError(err)) return true;
126
+ return false;
127
+ }
128
+ }
129
+ async checkItem(key, _options) {
130
+ const bucket = this.getBucket(key);
131
+ const response = await this.client.send(new clientS3.HeadObjectCommand({ Bucket: bucket, Key: key }));
132
+ const extension = key.split(".").pop() || "";
133
+ return {
134
+ bucket,
135
+ key,
136
+ mime: response.ContentType || "application/octet-stream",
137
+ extension,
138
+ size: response.ContentLength || 0,
139
+ completedUrl: this.getPublicUrl(bucket, key)
140
+ };
141
+ }
142
+ async getItem(key, _options) {
143
+ const bucket = this.getBucket(key);
144
+ const response = await this.client.send(new clientS3.GetObjectCommand({ Bucket: bucket, Key: key }));
145
+ const extension = key.split(".").pop() || "";
146
+ return {
147
+ bucket,
148
+ key,
149
+ mime: response.ContentType || "application/octet-stream",
150
+ extension,
151
+ size: response.ContentLength || 0,
152
+ data: response.Body,
153
+ completedUrl: this.getPublicUrl(bucket, key)
154
+ };
155
+ }
156
+ async putItem(file, _options) {
157
+ const bucket = this.getBucket(file.key);
158
+ await this.put(file.key, file.file);
159
+ const extension = file.key.split(".").pop() || "";
160
+ return {
161
+ bucket,
162
+ key: file.key,
163
+ mime: "application/octet-stream",
164
+ // Should ideally be determined from extension
165
+ extension,
166
+ size: file.file.length,
167
+ completedUrl: this.getPublicUrl(bucket, file.key)
168
+ };
169
+ }
80
170
  async getSize(key) {
81
171
  try {
82
172
  const response = await this.client.send(
@@ -111,6 +201,163 @@ var S3StorageAdapter = class {
111
201
  throw err;
112
202
  }
113
203
  }
204
+ async list(prefix, options) {
205
+ const bucket = this.getBucket(prefix || "");
206
+ const response = await this.client.send(
207
+ new clientS3.ListObjectsV2Command({
208
+ Bucket: bucket,
209
+ Prefix: prefix || void 0,
210
+ MaxKeys: options?.limit,
211
+ ContinuationToken: options?.continuationToken
212
+ })
213
+ );
214
+ const items = (response.Contents || []).map((obj) => ({
215
+ key: obj.Key || "",
216
+ size: obj.Size || 0,
217
+ lastModified: obj.LastModified,
218
+ etag: obj.ETag
219
+ }));
220
+ return {
221
+ items,
222
+ nextToken: response.NextContinuationToken
223
+ };
224
+ }
225
+ async getItems(path, options) {
226
+ const { items } = await this.list(path, { continuationToken: options?.continuationToken });
227
+ const bucket = this.getBucket(path);
228
+ return items.map((item) => ({
229
+ bucket,
230
+ key: item.key,
231
+ mime: "application/octet-stream",
232
+ // Fallback as HeadObject for each item is expensive
233
+ extension: item.key.split(".").pop() || "",
234
+ size: item.size,
235
+ completedUrl: this.getPublicUrl(bucket, item.key)
236
+ }));
237
+ }
238
+ async deleteMany(keys) {
239
+ if (keys.length === 0) return;
240
+ const bucket = this.getBucket(keys[0]);
241
+ await this.client.send(
242
+ new clientS3.DeleteObjectsCommand({
243
+ Bucket: bucket,
244
+ Delete: {
245
+ Objects: keys.map((key) => ({ Key: key }))
246
+ }
247
+ })
248
+ );
249
+ }
250
+ async deleteItems(keys, _options) {
251
+ return this.deleteMany(keys);
252
+ }
253
+ async deleteDir(path, options) {
254
+ let continuationToken = options?.continuationToken;
255
+ do {
256
+ const result = await this.list(path, { continuationToken });
257
+ const keys = result.items.map((i) => i.key);
258
+ if (keys.length > 0) {
259
+ await this.deleteMany(keys);
260
+ }
261
+ continuationToken = result.nextToken;
262
+ } while (continuationToken);
263
+ }
264
+ async copy(source, destination) {
265
+ const sourceBucket = this.getBucket(source);
266
+ const destBucket = this.getBucket(destination);
267
+ await this.client.send(
268
+ new clientS3.CopyObjectCommand({
269
+ Bucket: destBucket,
270
+ Key: destination,
271
+ CopySource: `${sourceBucket}/${source}`
272
+ })
273
+ );
274
+ }
275
+ async move(source, destination) {
276
+ await this.copy(source, destination);
277
+ await this.delete(source);
278
+ }
279
+ async moveItem(source, destinationKey, _options) {
280
+ await this.move(source.key, destinationKey);
281
+ const destBucket = this.getBucket(destinationKey);
282
+ return {
283
+ ...source,
284
+ bucket: destBucket,
285
+ key: destinationKey,
286
+ completedUrl: this.getPublicUrl(destBucket, destinationKey)
287
+ };
288
+ }
289
+ async moveItems(sources, destinationPrefix, _options) {
290
+ return Promise.all(
291
+ sources.map((source) => {
292
+ const destKey = `${destinationPrefix}/${source.key.split("/").pop()}`;
293
+ return this.moveItem(source, destKey);
294
+ })
295
+ );
296
+ }
297
+ async createMultiPart(file, maxPartNumber, _options) {
298
+ const bucket = this.getBucket(file.key);
299
+ const response = await this.client.send(
300
+ new clientS3.CreateMultipartUploadCommand({
301
+ Bucket: bucket,
302
+ Key: file.key
303
+ })
304
+ );
305
+ return {
306
+ bucket,
307
+ key: file.key,
308
+ uploadId: response.UploadId || "",
309
+ lastPartNumber: 0,
310
+ maxPartNumber,
311
+ parts: []
312
+ };
313
+ }
314
+ async putItemMultiPart(multipart, partNumber, file, _options) {
315
+ const response = await this.client.send(
316
+ new clientS3.UploadPartCommand({
317
+ Bucket: multipart.bucket,
318
+ Key: multipart.key,
319
+ UploadId: multipart.uploadId,
320
+ PartNumber: partNumber,
321
+ Body: file
322
+ })
323
+ );
324
+ const newPart = {
325
+ size: file.length,
326
+ eTag: response.ETag || "",
327
+ partNumber
328
+ };
329
+ return {
330
+ ...multipart,
331
+ lastPartNumber: partNumber,
332
+ parts: [...multipart.parts, newPart]
333
+ };
334
+ }
335
+ async completeMultipart(key, uploadId, parts, _options) {
336
+ const bucket = this.getBucket(key);
337
+ await this.client.send(
338
+ new clientS3.CompleteMultipartUploadCommand({
339
+ Bucket: bucket,
340
+ Key: key,
341
+ UploadId: uploadId,
342
+ MultipartUpload: {
343
+ Parts: parts.map((p) => ({
344
+ ETag: p.eTag,
345
+ PartNumber: p.partNumber
346
+ }))
347
+ }
348
+ })
349
+ );
350
+ }
351
+ async abortMultipart(key, uploadId, _options) {
352
+ const bucket = this.getBucket(key);
353
+ await this.client.send(
354
+ new clientS3.AbortMultipartUploadCommand({
355
+ Bucket: bucket,
356
+ Key: key,
357
+ UploadId: uploadId
358
+ })
359
+ );
360
+ }
114
361
  async getUrl(key, options) {
115
362
  const expiresIn = options?.expiresIn ?? DEFAULT_EXPIRES_IN;
116
363
  const command = new clientS3.GetObjectCommand({
@@ -119,6 +366,136 @@ var S3StorageAdapter = class {
119
366
  });
120
367
  return s3RequestPresigner.getSignedUrl(this.client, command, { expiresIn });
121
368
  }
369
+ async presignGetItem(key, options) {
370
+ const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;
371
+ const presignUrl = await this.getUrl(key, { expiresIn: expiredIn });
372
+ const extension = key.split(".").pop() || "";
373
+ return {
374
+ key,
375
+ mime: "application/octet-stream",
376
+ extension,
377
+ presignUrl,
378
+ expiredIn
379
+ };
380
+ }
381
+ async presignPutItem(file, options) {
382
+ const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;
383
+ const bucket = this.getBucket(file.key);
384
+ const command = new clientS3.PutObjectCommand({
385
+ Bucket: bucket,
386
+ Key: file.key
387
+ });
388
+ const presignUrl = await s3RequestPresigner.getSignedUrl(this.client, command, { expiresIn: expiredIn });
389
+ const extension = file.key.split(".").pop() || "";
390
+ return {
391
+ key: file.key,
392
+ mime: "application/octet-stream",
393
+ extension,
394
+ presignUrl,
395
+ expiredIn
396
+ };
397
+ }
398
+ async presignPutItemPart(file, options) {
399
+ const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;
400
+ const bucket = this.getBucket(file.key);
401
+ const command = new clientS3.UploadPartCommand({
402
+ Bucket: bucket,
403
+ Key: file.key,
404
+ UploadId: file.uploadId,
405
+ PartNumber: file.partNumber
406
+ });
407
+ const presignUrl = await s3RequestPresigner.getSignedUrl(this.client, command, { expiresIn: expiredIn });
408
+ const extension = file.key.split(".").pop() || "";
409
+ return {
410
+ key: file.key,
411
+ mime: "application/octet-stream",
412
+ extension,
413
+ presignUrl,
414
+ expiredIn,
415
+ size: file.size || 0,
416
+ partNumber: file.partNumber
417
+ };
418
+ }
419
+ async settingBucketPolicy(_options) {
420
+ const bucket = this.getBucket("");
421
+ const policy = JSON.stringify({
422
+ Version: "2012-10-17",
423
+ Statement: [
424
+ {
425
+ Sid: "PublicRead",
426
+ Effect: "Allow",
427
+ Principal: "*",
428
+ Action: ["s3:GetObject"],
429
+ Resource: [`arn:aws:s3:::${bucket}/*`]
430
+ }
431
+ ]
432
+ });
433
+ await this.client.send(new clientS3.PutBucketPolicyCommand({ Bucket: bucket, Policy: policy }));
434
+ }
435
+ async settingCorsConfiguration(_options) {
436
+ const bucket = this.getBucket("");
437
+ await this.client.send(
438
+ new clientS3.PutBucketCorsCommand({
439
+ Bucket: bucket,
440
+ CORSConfiguration: {
441
+ CORSRules: [
442
+ {
443
+ AllowedOrigins: ["*"],
444
+ AllowedMethods: ["GET", "PUT", "POST", "DELETE", "HEAD"],
445
+ AllowedHeaders: ["*"],
446
+ MaxAgeSeconds: 3e3
447
+ }
448
+ ]
449
+ }
450
+ })
451
+ );
452
+ }
453
+ async settingBucketExpiredObjectLifecycle(_options) {
454
+ const bucket = this.getBucket("");
455
+ await this.client.send(
456
+ new clientS3.PutBucketLifecycleConfigurationCommand({
457
+ Bucket: bucket,
458
+ LifecycleConfiguration: {
459
+ Rules: [
460
+ {
461
+ ID: "ExpireOldObjects",
462
+ Status: "Enabled",
463
+ Prefix: "",
464
+ Expiration: { Days: 30 }
465
+ }
466
+ ]
467
+ }
468
+ })
469
+ );
470
+ }
471
+ async settingDisableAclConfiguration(_options) {
472
+ }
473
+ async settingBlockPublicAccessConfiguration(_options) {
474
+ const bucket = this.getBucket("");
475
+ await this.client.send(
476
+ new clientS3.PutPublicAccessBlockCommand({
477
+ Bucket: bucket,
478
+ PublicAccessBlockConfiguration: {
479
+ BlockPublicAcls: true,
480
+ IgnorePublicAcls: true,
481
+ BlockPublicPolicy: true,
482
+ RestrictPublicBuckets: true
483
+ }
484
+ })
485
+ );
486
+ }
487
+ mapPresign(file, _options) {
488
+ const bucket = this.getBucket(file.key);
489
+ const extension = file.key.split(".").pop() || "";
490
+ return {
491
+ bucket,
492
+ key: file.key,
493
+ mime: "application/octet-stream",
494
+ extension,
495
+ size: file.size || 0,
496
+ completedUrl: this.getPublicUrl(bucket, file.key)
497
+ };
498
+ }
122
499
  /**
123
500
  * Create a presigned upload for direct-to-storage upload.
124
501
  *
@@ -220,6 +597,7 @@ var S3StorageAdapter = class {
220
597
  }
221
598
  };
222
599
 
600
+ exports.EnumS3Accessibility = EnumS3Accessibility;
223
601
  exports.S3StorageAdapter = S3StorageAdapter;
224
602
  //# sourceMappingURL=index.js.map
225
603
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/s3-storage.adapter.ts"],"names":["S3Client","GetObjectCommand","PutObjectCommand","DeleteObjectCommand","HeadObjectCommand","Readable","getSignedUrl","createPresignedPost"],"mappings":"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","file":"index.js","sourcesContent":["import {\n S3Client,\n GetObjectCommand,\n PutObjectCommand,\n DeleteObjectCommand,\n HeadObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { createPresignedPost } from \"@aws-sdk/s3-presigned-post\";\nimport type { PresignedPostOptions } from \"@aws-sdk/s3-presigned-post\";\nimport { Readable } from \"node:stream\";\nimport type {\n StorageAdapter,\n GetUrlOptions,\n PresignedUploadOptions,\n PresignedUploadResult,\n} from \"@better-media/core\";\nimport type { S3StorageConfig } from \"../interfaces/s3-storage-config.interface\";\n\nconst DEFAULT_EXPIRES_IN = 3600; // 1 hour\nconst DEFAULT_MAX_SIZE_BYTES = 100 * 1024 * 1024; // 100 MB\nconst DEFAULT_MIN_SIZE_BYTES = 1; // at least 1 byte\n\n/**\n * S3 storage adapter for production deployments.\n * Supports AWS S3 and S3-compatible object storage (MinIO, etc.).\n */\nexport class S3StorageAdapter implements StorageAdapter {\n private readonly client: S3Client;\n private readonly bucketResolver: (key: string) => string;\n\n constructor(config: S3StorageConfig) {\n this.bucketResolver =\n typeof config.bucket === \"function\" ? config.bucket : () => config.bucket as string;\n this.client = new S3Client({\n region: config.region,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n ...(config.endpoint && {\n endpoint: config.endpoint,\n forcePathStyle: config.forcePathStyle ?? true,\n }),\n });\n }\n\n private isNotFoundError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n const e = err as { name?: string; $metadata?: { httpStatusCode?: number } };\n return e.name === \"NoSuchKey\" || e.name === \"NotFound\" || e.$metadata?.httpStatusCode === 404;\n }\n\n private getBucket(key: string): string {\n return this.bucketResolver(key);\n }\n\n async get(key: string): Promise<Buffer | null> {\n try {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.getBucket(key), Key: key })\n );\n const body = response.Body;\n if (body == null) return null;\n const chunks: Uint8Array[] = [];\n for await (const chunk of body as AsyncIterable<Uint8Array>) {\n chunks.push(chunk);\n }\n return Buffer.concat(chunks);\n } catch (err) {\n if (this.isNotFoundError(err)) return null;\n throw err;\n }\n }\n\n async put(key: string, value: Buffer): Promise<void> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.getBucket(key),\n Key: key,\n Body: value,\n })\n );\n }\n\n async delete(key: string): Promise<void> {\n try {\n await this.client.send(new DeleteObjectCommand({ Bucket: this.getBucket(key), Key: key }));\n } catch (err) {\n if (this.isNotFoundError(err)) return;\n throw err;\n }\n }\n\n async exists(key: string): Promise<boolean> {\n try {\n await this.client.send(new HeadObjectCommand({ Bucket: this.getBucket(key), Key: key }));\n return true;\n } catch (err) {\n if (this.isNotFoundError(err)) return false;\n throw err;\n }\n }\n\n async getSize(key: string): Promise<number | null> {\n try {\n const response = await this.client.send(\n new HeadObjectCommand({ Bucket: this.getBucket(key), Key: key })\n );\n const size = response.ContentLength;\n return size != null ? size : null;\n } catch (err) {\n if (this.isNotFoundError(err)) return null;\n throw err;\n }\n }\n\n async getStream(key: string): Promise<ReadableStream<Uint8Array> | null> {\n try {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.getBucket(key), Key: key })\n );\n const body = response.Body;\n if (body == null) return null;\n if (body instanceof Readable) {\n return Readable.toWeb(body) as ReadableStream<Uint8Array>;\n }\n if (body instanceof ReadableStream) {\n return body as ReadableStream<Uint8Array>;\n }\n if (typeof (body as Blob).stream === \"function\") {\n return (body as Blob).stream() as ReadableStream<Uint8Array>;\n }\n return null;\n } catch (err) {\n if (this.isNotFoundError(err)) return null;\n throw err;\n }\n }\n\n async getUrl(key: string, options?: GetUrlOptions): Promise<string> {\n const expiresIn = options?.expiresIn ?? DEFAULT_EXPIRES_IN;\n const command = new GetObjectCommand({\n Bucket: this.getBucket(key),\n Key: key,\n });\n return getSignedUrl(this.client, command, { expiresIn });\n }\n\n /**\n * Create a presigned upload for direct-to-storage upload.\n *\n * - **POST**: Uses an S3 Policy to strictly enforce `Content-Type` and\n * `content-length-range` server-side. S3 rejects any upload violating these.\n * Best for web/browser clients using multipart forms.\n *\n * - **PUT**: Signs `ContentType`, `ContentLength`, and metadata headers into the URL.\n * S3 rejects any request that presents mismatched header values.\n * Best for mobile/API clients doing direct binary body uploads.\n */\n async createPresignedUpload(\n key: string,\n options: PresignedUploadOptions\n ): Promise<PresignedUploadResult> {\n const {\n method = \"PUT\",\n contentType,\n expiresIn = DEFAULT_EXPIRES_IN,\n maxSizeBytes = DEFAULT_MAX_SIZE_BYTES,\n minSizeBytes = DEFAULT_MIN_SIZE_BYTES,\n metadata = {},\n } = options;\n\n const bucket = this.getBucket(key);\n // Build x-amz-meta-* header map for user metadata\n const metaHeaders: Record<string, string> = Object.fromEntries(\n Object.entries(metadata).map(([k, v]): [string, string] => [`x-amz-meta-${k}`, String(v)])\n );\n\n if (method === \"POST\") {\n return this._createPresignedPost({\n key,\n bucket,\n contentType,\n expiresIn,\n maxSizeBytes,\n minSizeBytes,\n metadata,\n metaHeaders,\n });\n }\n\n return this._createPresignedPut({\n key,\n bucket,\n contentType,\n expiresIn,\n maxSizeBytes,\n metadata,\n metaHeaders,\n });\n }\n\n private async _createPresignedPost(params: {\n key: string;\n bucket: string;\n contentType: string;\n expiresIn: number;\n maxSizeBytes: number;\n minSizeBytes: number;\n metadata: Record<string, string>;\n metaHeaders: Record<string, string>;\n }): Promise<PresignedUploadResult> {\n const {\n key,\n bucket,\n contentType,\n expiresIn,\n maxSizeBytes,\n minSizeBytes,\n metadata,\n metaHeaders,\n } = params;\n\n // Build the conditions array using the SDK's element type\n const conditions: NonNullable<PresignedPostOptions[\"Conditions\"]> = [\n // Enforce exact Content-Type — S3 rejects mismatches\n { \"Content-Type\": contentType },\n // Enforce file size range — S3 rejects files outside [min, max]\n [\"content-length-range\", minSizeBytes, maxSizeBytes],\n // Enforce each metadata k/v — S3 rejects if any value differs\n ...Object.entries(metadata).map(([k, v]) => ({\n [`x-amz-meta-${k}`]: v,\n })),\n ];\n\n const { url, fields } = await createPresignedPost(this.client, {\n Bucket: bucket,\n Key: key,\n Expires: expiresIn,\n Conditions: conditions,\n Fields: {\n \"Content-Type\": contentType,\n ...metaHeaders,\n },\n });\n\n return { method: \"POST\", url, fields };\n }\n\n private async _createPresignedPut(params: {\n key: string;\n bucket: string;\n contentType: string;\n expiresIn: number;\n maxSizeBytes: number;\n metadata: Record<string, string>;\n metaHeaders: Record<string, string>;\n }): Promise<PresignedUploadResult> {\n const { key, bucket, contentType, expiresIn, maxSizeBytes, metadata, metaHeaders } = params;\n\n // All fields set on PutObjectCommand are hashed into the signature.\n // S3 will return 403 for any request that presents different header values.\n const command = new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: contentType,\n // Encoding ContentLength ties the signature to an exact body size.\n // The client MUST send a Content-Length header that matches this value.\n ContentLength: maxSizeBytes,\n // User metadata is signed in — mismatches cause 403.\n Metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n });\n\n const url = await getSignedUrl(this.client, command, { expiresIn });\n\n // Surface the required headers so callers know exactly what to send.\n const headers: Record<string, string> = {\n \"Content-Type\": contentType,\n \"Content-Length\": String(maxSizeBytes),\n ...metaHeaders,\n };\n\n return { method: \"PUT\", url, headers };\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/interfaces/s3.interface.ts","../src/adapters/s3-storage.adapter.ts"],"names":["EnumS3Accessibility","S3Client","GetObjectCommand","Buffer","PutObjectCommand","DeleteObjectCommand","HeadObjectCommand","Readable","ListObjectsV2Command","DeleteObjectsCommand","CopyObjectCommand","CreateMultipartUploadCommand","UploadPartCommand","CompleteMultipartUploadCommand","AbortMultipartUploadCommand","getSignedUrl","PutBucketPolicyCommand","PutBucketCorsCommand","PutBucketLifecycleConfigurationCommand","PutPublicAccessBlockCommand","createPresignedPost"],"mappings":"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","file":"index.js","sourcesContent":["import { Buffer } from \"node:buffer\";\n\nexport enum EnumS3Accessibility {\n public = \"public\",\n private = \"private\",\n}\n\nexport interface S3Object {\n bucket: string;\n key: string;\n cdnUrl?: string;\n completedUrl: string;\n mime: string;\n extension: string;\n access?: EnumS3Accessibility;\n size: number;\n data?: unknown; // Stream or Buffer-like object\n}\n\nexport interface S3MultipartPart {\n size: number;\n eTag: string;\n partNumber: number;\n}\n\nexport interface S3Multipart {\n bucket: string;\n key: string;\n uploadId: string;\n lastPartNumber: number;\n maxPartNumber: number;\n parts: S3MultipartPart[];\n}\n\nexport interface S3Presign {\n key: string;\n mime: string;\n extension: string;\n presignUrl: string;\n expiredIn: number;\n}\n\nexport interface S3PresignPart extends S3Presign {\n size: number;\n partNumber: number;\n}\n\nexport interface S3Options {\n access?: EnumS3Accessibility;\n}\n\nexport interface S3PutItemOptions extends S3Options {\n forceUpdate?: boolean;\n}\n\nexport interface S3GetItemsOptions extends S3Options {\n continuationToken?: string;\n}\n\nexport type S3DeleteDirOptions = S3GetItemsOptions;\n\nexport interface S3PresignGetItemOptions extends S3Options {\n expired?: number;\n}\n\nexport interface S3PresignPutItemOptions extends S3PutItemOptions {\n expired?: number;\n}\n\nexport interface S3PresignPutItemPartOptions extends S3Options {\n expired?: number;\n}\n\nexport interface S3MoveItemOptions {\n accessFrom?: EnumS3Accessibility;\n accessTo?: EnumS3Accessibility;\n}\n\nexport interface S3CreateMultiplePart {\n key: string;\n size?: number;\n}\n\nexport interface S3PutItem extends S3CreateMultiplePart {\n file: Buffer;\n}\n","import {\n S3Client,\n GetObjectCommand,\n PutObjectCommand,\n DeleteObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n DeleteObjectsCommand,\n CopyObjectCommand,\n CreateMultipartUploadCommand,\n UploadPartCommand,\n CompleteMultipartUploadCommand,\n AbortMultipartUploadCommand,\n PutBucketPolicyCommand,\n PutBucketCorsCommand,\n PutBucketLifecycleConfigurationCommand,\n PutPublicAccessBlockCommand,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { createPresignedPost } from \"@aws-sdk/s3-presigned-post\";\nimport type { PresignedPostOptions } from \"@aws-sdk/s3-presigned-post\";\nimport { Buffer } from \"node:buffer\";\nimport { Readable } from \"node:stream\";\nimport type {\n StorageAdapter,\n GetUrlOptions,\n PresignedUploadOptions,\n PresignedUploadResult,\n ListOptions,\n ListResult,\n StorageObject,\n} from \"@better-media/core\";\nimport type { S3StorageConfig } from \"../interfaces/s3-storage-config.interface\";\nimport {\n S3Object,\n S3Multipart,\n S3MultipartPart,\n S3Presign,\n S3PresignPart,\n S3CreateMultiplePart,\n S3PutItem,\n S3Options,\n S3PutItemOptions,\n S3GetItemsOptions,\n S3DeleteDirOptions,\n S3PresignGetItemOptions,\n S3PresignPutItemOptions,\n S3PresignPutItemPartOptions,\n S3MoveItemOptions,\n} from \"../interfaces/s3.interface\";\n\nconst DEFAULT_EXPIRES_IN = 3600; // 1 hour\nconst DEFAULT_MAX_SIZE_BYTES = 100 * 1024 * 1024; // 100 MB\nconst DEFAULT_MIN_SIZE_BYTES = 1; // at least 1 byte\n\n/**\n * S3 storage adapter for production deployments.\n * Supports AWS S3 and S3-compatible object storage (MinIO, etc.).\n */\nexport class S3StorageAdapter implements StorageAdapter {\n private readonly client: S3Client;\n private readonly bucketResolver: (key: string) => string;\n private readonly region: string;\n private readonly endpoint?: string;\n private readonly forcePathStyle: boolean;\n\n constructor(config: S3StorageConfig) {\n this.region = config.region;\n this.endpoint = config.endpoint;\n this.forcePathStyle = config.forcePathStyle ?? (config.endpoint ? true : false);\n this.bucketResolver =\n typeof config.bucket === \"function\" ? config.bucket : () => config.bucket as string;\n this.client = new S3Client({\n region: config.region,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n ...(config.endpoint && {\n endpoint: config.endpoint,\n forcePathStyle: this.forcePathStyle,\n }),\n });\n }\n\n private isNotFoundError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n const e = err as { name?: string; $metadata?: { httpStatusCode?: number } };\n return e.name === \"NoSuchKey\" || e.name === \"NotFound\" || e.$metadata?.httpStatusCode === 404;\n }\n\n private getBucket(key: string): string {\n return this.bucketResolver(key);\n }\n\n private getPublicUrl(bucket: string, key: string): string {\n if (this.endpoint) {\n if (this.forcePathStyle) {\n // Path style: endpoint/bucket/key\n const baseUrl = this.endpoint.endsWith(\"/\") ? this.endpoint.slice(0, -1) : this.endpoint;\n return `${baseUrl}/${bucket}/${key}`;\n }\n // Virtual host style: bucket.endpoint/key\n try {\n const url = new URL(this.endpoint);\n return `${url.protocol}//${bucket}.${url.host}${url.pathname === \"/\" ? \"\" : url.pathname}/${key}`;\n } catch {\n // Fallback if endpoint is not a valid URL\n return `${this.endpoint}/${bucket}/${key}`;\n }\n }\n // Standard AWS URL - using standard regional format for reliability\n // Fallback to the original simple format if that's preferred,\n // but the regional one is more robust.\n return `https://${bucket}.s3.${this.region}.amazonaws.com/${key}`;\n }\n\n async get(key: string): Promise<Buffer | null> {\n try {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.getBucket(key), Key: key })\n );\n const body = response.Body;\n if (body == null) return null;\n const chunks: Uint8Array[] = [];\n for await (const chunk of body as AsyncIterable<Uint8Array>) {\n chunks.push(chunk);\n }\n return Buffer.concat(chunks);\n } catch (err) {\n if (this.isNotFoundError(err)) return null;\n throw err;\n }\n }\n\n async put(key: string, value: Buffer): Promise<void> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.getBucket(key),\n Key: key,\n Body: value,\n })\n );\n }\n\n async delete(key: string): Promise<void> {\n try {\n await this.client.send(new DeleteObjectCommand({ Bucket: this.getBucket(key), Key: key }));\n } catch (err) {\n if (this.isNotFoundError(err)) return;\n throw err;\n }\n }\n\n async exists(key: string): Promise<boolean> {\n try {\n await this.client.send(new HeadObjectCommand({ Bucket: this.getBucket(key), Key: key }));\n return true;\n } catch (err) {\n if (this.isNotFoundError(err)) return false;\n throw err;\n }\n }\n\n async checkConnection(): Promise<boolean> {\n try {\n // Simplest way to check connection is to list buckets (requires permission)\n // or just check a bucket existence if we have one.\n const bucket = this.getBucket(\"\");\n await this.client.send(\n new HeadObjectCommand({ Bucket: bucket, Key: \"connection-check-non-existent\" })\n );\n return true;\n } catch (err) {\n // If we get a 404, the connection itself is fine (we reached S3).\n if (this.isNotFoundError(err)) return true;\n // If we get a 403, we are connected but unauthorized, which counts as \"connected\" often,\n // but let's be strict and return false if we can't even reach it.\n return false;\n }\n }\n\n async checkBucket(_options?: S3Options): Promise<boolean> {\n try {\n const bucket = this.getBucket(\"\");\n await this.client.send(new HeadObjectCommand({ Bucket: bucket, Key: \"bucket-check\" }));\n return true;\n } catch (err) {\n if (this.isNotFoundError(err)) return true;\n return false;\n }\n }\n\n async checkItem(key: string, _options?: S3Options): Promise<S3Object> {\n const bucket = this.getBucket(key);\n const response = await this.client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n\n const extension = key.split(\".\").pop() || \"\";\n return {\n bucket,\n key,\n mime: response.ContentType || \"application/octet-stream\",\n extension,\n size: response.ContentLength || 0,\n completedUrl: this.getPublicUrl(bucket, key),\n };\n }\n\n async getItem(key: string, _options?: S3Options): Promise<S3Object> {\n const bucket = this.getBucket(key);\n const response = await this.client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));\n\n const extension = key.split(\".\").pop() || \"\";\n return {\n bucket,\n key,\n mime: response.ContentType || \"application/octet-stream\",\n extension,\n size: response.ContentLength || 0,\n data: response.Body,\n completedUrl: this.getPublicUrl(bucket, key),\n };\n }\n\n async putItem(file: S3PutItem, _options?: S3PutItemOptions): Promise<S3Object> {\n const bucket = this.getBucket(file.key);\n await this.put(file.key, file.file);\n\n const extension = file.key.split(\".\").pop() || \"\";\n return {\n bucket,\n key: file.key,\n mime: \"application/octet-stream\", // Should ideally be determined from extension\n extension,\n size: file.file.length,\n completedUrl: this.getPublicUrl(bucket, file.key),\n };\n }\n\n async getSize(key: string): Promise<number | null> {\n try {\n const response = await this.client.send(\n new HeadObjectCommand({ Bucket: this.getBucket(key), Key: key })\n );\n const size = response.ContentLength;\n return size != null ? size : null;\n } catch (err) {\n if (this.isNotFoundError(err)) return null;\n throw err;\n }\n }\n\n async getStream(key: string): Promise<ReadableStream<Uint8Array> | null> {\n try {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.getBucket(key), Key: key })\n );\n const body = response.Body;\n if (body == null) return null;\n if (body instanceof Readable) {\n return Readable.toWeb(body) as ReadableStream<Uint8Array>;\n }\n if (body instanceof ReadableStream) {\n return body as ReadableStream<Uint8Array>;\n }\n if (typeof (body as Blob).stream === \"function\") {\n return (body as Blob).stream() as ReadableStream<Uint8Array>;\n }\n return null;\n } catch (err) {\n if (this.isNotFoundError(err)) return null;\n throw err;\n }\n }\n\n async list(prefix: string, options?: ListOptions): Promise<ListResult> {\n const bucket = this.getBucket(prefix || \"\");\n const response = await this.client.send(\n new ListObjectsV2Command({\n Bucket: bucket as string,\n Prefix: prefix || undefined,\n MaxKeys: options?.limit,\n ContinuationToken: options?.continuationToken,\n })\n );\n\n const items: StorageObject[] = (response.Contents || []).map((obj) => ({\n key: obj.Key || \"\",\n size: obj.Size || 0,\n lastModified: obj.LastModified,\n etag: obj.ETag,\n }));\n\n return {\n items,\n nextToken: response.NextContinuationToken,\n };\n }\n\n async getItems(path: string, options?: S3GetItemsOptions): Promise<S3Object[]> {\n const { items } = await this.list(path, { continuationToken: options?.continuationToken });\n const bucket = this.getBucket(path);\n return items.map((item: StorageObject) => ({\n bucket,\n key: item.key,\n mime: \"application/octet-stream\", // Fallback as HeadObject for each item is expensive\n extension: item.key.split(\".\").pop() || \"\",\n size: item.size,\n completedUrl: this.getPublicUrl(bucket, item.key),\n }));\n }\n\n async deleteMany(keys: string[]): Promise<void> {\n if (keys.length === 0) return;\n const bucket = this.getBucket(keys[0]!);\n await this.client.send(\n new DeleteObjectsCommand({\n Bucket: bucket,\n Delete: {\n Objects: keys.map((key) => ({ Key: key })),\n },\n })\n );\n }\n\n async deleteItems(keys: string[], _options?: S3Options): Promise<void> {\n return this.deleteMany(keys);\n }\n\n async deleteDir(path: string, options?: S3DeleteDirOptions): Promise<void> {\n let continuationToken: string | undefined = options?.continuationToken;\n do {\n const result: ListResult = await this.list(path, { continuationToken });\n const keys = result.items.map((i: StorageObject) => i.key);\n if (keys.length > 0) {\n await this.deleteMany(keys);\n }\n continuationToken = result.nextToken;\n } while (continuationToken);\n }\n\n async copy(source: string, destination: string): Promise<void> {\n const sourceBucket = this.getBucket(source);\n const destBucket = this.getBucket(destination);\n await this.client.send(\n new CopyObjectCommand({\n Bucket: destBucket,\n Key: destination,\n CopySource: `${sourceBucket}/${source}`,\n })\n );\n }\n\n async move(source: string, destination: string): Promise<void> {\n await this.copy(source, destination);\n await this.delete(source);\n }\n\n async moveItem(\n source: S3Object,\n destinationKey: string,\n _options?: S3MoveItemOptions\n ): Promise<S3Object> {\n await this.move(source.key, destinationKey);\n const destBucket = this.getBucket(destinationKey);\n return {\n ...source,\n bucket: destBucket,\n key: destinationKey,\n completedUrl: this.getPublicUrl(destBucket, destinationKey),\n };\n }\n\n async moveItems(\n sources: S3Object[],\n destinationPrefix: string,\n _options?: S3Options\n ): Promise<S3Object[]> {\n return Promise.all(\n sources.map((source) => {\n const destKey = `${destinationPrefix}/${source.key.split(\"/\").pop()}`;\n return this.moveItem(source, destKey);\n })\n );\n }\n\n async createMultiPart(\n file: S3CreateMultiplePart,\n maxPartNumber: number,\n _options?: S3Options\n ): Promise<S3Multipart> {\n const bucket = this.getBucket(file.key);\n const response = await this.client.send(\n new CreateMultipartUploadCommand({\n Bucket: bucket,\n Key: file.key,\n })\n );\n\n return {\n bucket,\n key: file.key,\n uploadId: response.UploadId || \"\",\n lastPartNumber: 0,\n maxPartNumber,\n parts: [],\n };\n }\n\n async putItemMultiPart(\n multipart: S3Multipart,\n partNumber: number,\n file: Buffer,\n _options?: S3Options\n ): Promise<S3Multipart> {\n const response = await this.client.send(\n new UploadPartCommand({\n Bucket: multipart.bucket,\n Key: multipart.key,\n UploadId: multipart.uploadId,\n PartNumber: partNumber,\n Body: file,\n })\n );\n\n const newPart: S3MultipartPart = {\n size: file.length,\n eTag: response.ETag || \"\",\n partNumber,\n };\n\n return {\n ...multipart,\n lastPartNumber: partNumber,\n parts: [...multipart.parts, newPart],\n };\n }\n\n async completeMultipart(\n key: string,\n uploadId: string,\n parts: S3MultipartPart[],\n _options?: S3Options\n ): Promise<void> {\n const bucket = this.getBucket(key);\n await this.client.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts.map((p) => ({\n ETag: p.eTag,\n PartNumber: p.partNumber,\n })),\n },\n })\n );\n }\n\n async abortMultipart(key: string, uploadId: string, _options?: S3Options): Promise<void> {\n const bucket = this.getBucket(key);\n await this.client.send(\n new AbortMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n })\n );\n }\n\n async getUrl(key: string, options?: GetUrlOptions): Promise<string> {\n const expiresIn = options?.expiresIn ?? DEFAULT_EXPIRES_IN;\n const command = new GetObjectCommand({\n Bucket: this.getBucket(key),\n Key: key,\n });\n return getSignedUrl(this.client, command, { expiresIn });\n }\n\n async presignGetItem(key: string, options?: S3PresignGetItemOptions): Promise<S3Presign> {\n const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;\n const presignUrl = await this.getUrl(key, { expiresIn: expiredIn });\n const extension = key.split(\".\").pop() || \"\";\n return {\n key,\n mime: \"application/octet-stream\",\n extension,\n presignUrl,\n expiredIn,\n };\n }\n\n async presignPutItem(\n file: S3CreateMultiplePart,\n options?: S3PresignPutItemOptions\n ): Promise<S3Presign> {\n const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;\n const bucket = this.getBucket(file.key);\n const command = new PutObjectCommand({\n Bucket: bucket,\n Key: file.key,\n });\n const presignUrl = await getSignedUrl(this.client, command, { expiresIn: expiredIn });\n const extension = file.key.split(\".\").pop() || \"\";\n\n return {\n key: file.key,\n mime: \"application/octet-stream\",\n extension,\n presignUrl,\n expiredIn,\n };\n }\n\n async presignPutItemPart(\n file: S3CreateMultiplePart & { uploadId: string; partNumber: number },\n options?: S3PresignPutItemPartOptions\n ): Promise<S3PresignPart> {\n const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;\n const bucket = this.getBucket(file.key);\n const command = new UploadPartCommand({\n Bucket: bucket,\n Key: file.key,\n UploadId: file.uploadId,\n PartNumber: file.partNumber,\n });\n const presignUrl = await getSignedUrl(this.client, command, { expiresIn: expiredIn });\n const extension = file.key.split(\".\").pop() || \"\";\n\n return {\n key: file.key,\n mime: \"application/octet-stream\",\n extension,\n presignUrl,\n expiredIn,\n size: file.size || 0,\n partNumber: file.partNumber,\n };\n }\n\n async settingBucketPolicy(_options?: S3Options): Promise<void> {\n const bucket = this.getBucket(\"\");\n // Example: allow public read (simplified)\n const policy = JSON.stringify({\n Version: \"2012-10-17\",\n Statement: [\n {\n Sid: \"PublicRead\",\n Effect: \"Allow\",\n Principal: \"*\",\n Action: [\"s3:GetObject\"],\n Resource: [`arn:aws:s3:::${bucket}/*`],\n },\n ],\n });\n await this.client.send(new PutBucketPolicyCommand({ Bucket: bucket, Policy: policy }));\n }\n\n async settingCorsConfiguration(_options?: S3Options): Promise<void> {\n const bucket = this.getBucket(\"\");\n await this.client.send(\n new PutBucketCorsCommand({\n Bucket: bucket,\n CORSConfiguration: {\n CORSRules: [\n {\n AllowedOrigins: [\"*\"],\n AllowedMethods: [\"GET\", \"PUT\", \"POST\", \"DELETE\", \"HEAD\"],\n AllowedHeaders: [\"*\"],\n MaxAgeSeconds: 3000,\n },\n ],\n },\n })\n );\n }\n\n async settingBucketExpiredObjectLifecycle(_options: S3Options): Promise<void> {\n const bucket = this.getBucket(\"\");\n await this.client.send(\n new PutBucketLifecycleConfigurationCommand({\n Bucket: bucket,\n LifecycleConfiguration: {\n Rules: [\n {\n ID: \"ExpireOldObjects\",\n Status: \"Enabled\",\n Prefix: \"\",\n Expiration: { Days: 30 },\n },\n ],\n },\n })\n );\n }\n\n async settingDisableAclConfiguration(_options?: S3Options): Promise<void> {\n // This is often part of Public Access Block or Bucket creation\n // S3 disables ACLs by default now with \"Bucket owner enforced\"\n }\n\n async settingBlockPublicAccessConfiguration(_options?: S3Options): Promise<void> {\n const bucket = this.getBucket(\"\");\n await this.client.send(\n new PutPublicAccessBlockCommand({\n Bucket: bucket,\n PublicAccessBlockConfiguration: {\n BlockPublicAcls: true,\n IgnorePublicAcls: true,\n BlockPublicPolicy: true,\n RestrictPublicBuckets: true,\n },\n })\n );\n }\n\n mapPresign(file: S3CreateMultiplePart, _options?: S3Options): S3Object {\n const bucket = this.getBucket(file.key);\n const extension = file.key.split(\".\").pop() || \"\";\n return {\n bucket,\n key: file.key,\n mime: \"application/octet-stream\",\n extension,\n size: file.size || 0,\n completedUrl: this.getPublicUrl(bucket, file.key),\n };\n }\n\n /**\n * Create a presigned upload for direct-to-storage upload.\n *\n * - **POST**: Uses an S3 Policy to strictly enforce `Content-Type` and\n * `content-length-range` server-side. S3 rejects any upload violating these.\n * Best for web/browser clients using multipart forms.\n *\n * - **PUT**: Signs `ContentType`, `ContentLength`, and metadata headers into the URL.\n * S3 rejects any request that presents mismatched header values.\n * Best for mobile/API clients doing direct binary body uploads.\n */\n async createPresignedUpload(\n key: string,\n options: PresignedUploadOptions\n ): Promise<PresignedUploadResult> {\n const {\n method = \"PUT\",\n contentType,\n expiresIn = DEFAULT_EXPIRES_IN,\n maxSizeBytes = DEFAULT_MAX_SIZE_BYTES,\n minSizeBytes = DEFAULT_MIN_SIZE_BYTES,\n metadata = {},\n } = options;\n\n const bucket = this.getBucket(key);\n // Build x-amz-meta-* header map for user metadata\n const metaHeaders: Record<string, string> = Object.fromEntries(\n Object.entries(metadata).map(([k, v]): [string, string] => [`x-amz-meta-${k}`, String(v)])\n );\n\n if (method === \"POST\") {\n return this._createPresignedPost({\n key,\n bucket,\n contentType,\n expiresIn,\n maxSizeBytes,\n minSizeBytes,\n metadata,\n metaHeaders,\n });\n }\n\n return this._createPresignedPut({\n key,\n bucket,\n contentType,\n expiresIn,\n maxSizeBytes,\n metadata,\n metaHeaders,\n });\n }\n\n private async _createPresignedPost(params: {\n key: string;\n bucket: string;\n contentType: string;\n expiresIn: number;\n maxSizeBytes: number;\n minSizeBytes: number;\n metadata: Record<string, string>;\n metaHeaders: Record<string, string>;\n }): Promise<PresignedUploadResult> {\n const {\n key,\n bucket,\n contentType,\n expiresIn,\n maxSizeBytes,\n minSizeBytes,\n metadata,\n metaHeaders,\n } = params;\n\n // Build the conditions array using the SDK's element type\n const conditions: NonNullable<PresignedPostOptions[\"Conditions\"]> = [\n // Enforce exact Content-Type — S3 rejects mismatches\n { \"Content-Type\": contentType },\n // Enforce file size range — S3 rejects files outside [min, max]\n [\"content-length-range\", minSizeBytes, maxSizeBytes],\n // Enforce each metadata k/v — S3 rejects if any value differs\n ...Object.entries(metadata).map(([k, v]) => ({\n [`x-amz-meta-${k}`]: v,\n })),\n ];\n\n const { url, fields } = await createPresignedPost(this.client, {\n Bucket: bucket,\n Key: key,\n Expires: expiresIn,\n Conditions: conditions,\n Fields: {\n \"Content-Type\": contentType,\n ...metaHeaders,\n },\n });\n\n return { method: \"POST\", url, fields };\n }\n\n private async _createPresignedPut(params: {\n key: string;\n bucket: string;\n contentType: string;\n expiresIn: number;\n maxSizeBytes: number;\n metadata: Record<string, string>;\n metaHeaders: Record<string, string>;\n }): Promise<PresignedUploadResult> {\n const { key, bucket, contentType, expiresIn, maxSizeBytes, metadata, metaHeaders } = params;\n\n // All fields set on PutObjectCommand are hashed into the signature.\n // S3 will return 403 for any request that presents different header values.\n const command = new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: contentType,\n // Encoding ContentLength ties the signature to an exact body size.\n // The client MUST send a Content-Length header that matches this value.\n ContentLength: maxSizeBytes,\n // User metadata is signed in — mismatches cause 403.\n Metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n });\n\n const url = await getSignedUrl(this.client, command, { expiresIn });\n\n // Surface the required headers so callers know exactly what to send.\n const headers: Record<string, string> = {\n \"Content-Type\": contentType,\n \"Content-Length\": String(maxSizeBytes),\n ...metaHeaders,\n };\n\n return { method: \"PUT\", url, headers };\n }\n}\n"]}