@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/README.md +19 -5
- package/dist/index.d.mts +107 -2
- package/dist/index.d.ts +107 -2
- package/dist/index.js +381 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +381 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3';
|
|
1
|
+
import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, HeadObjectCommand, ListObjectsV2Command, DeleteObjectsCommand, CopyObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, PutBucketPolicyCommand, PutBucketCorsCommand, PutBucketLifecycleConfigurationCommand, PutPublicAccessBlockCommand } from '@aws-sdk/client-s3';
|
|
2
2
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
3
3
|
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
|
|
4
|
+
import { Buffer } from 'buffer';
|
|
4
5
|
import { Readable } from 'stream';
|
|
5
6
|
|
|
6
|
-
// src/
|
|
7
|
+
// src/interfaces/s3.interface.ts
|
|
8
|
+
var EnumS3Accessibility = /* @__PURE__ */ ((EnumS3Accessibility2) => {
|
|
9
|
+
EnumS3Accessibility2["public"] = "public";
|
|
10
|
+
EnumS3Accessibility2["private"] = "private";
|
|
11
|
+
return EnumS3Accessibility2;
|
|
12
|
+
})(EnumS3Accessibility || {});
|
|
7
13
|
var DEFAULT_EXPIRES_IN = 3600;
|
|
8
14
|
var DEFAULT_MAX_SIZE_BYTES = 100 * 1024 * 1024;
|
|
9
15
|
var DEFAULT_MIN_SIZE_BYTES = 1;
|
|
10
16
|
var S3StorageAdapter = class {
|
|
11
17
|
client;
|
|
12
18
|
bucketResolver;
|
|
19
|
+
region;
|
|
20
|
+
endpoint;
|
|
21
|
+
forcePathStyle;
|
|
13
22
|
constructor(config) {
|
|
23
|
+
this.region = config.region;
|
|
24
|
+
this.endpoint = config.endpoint;
|
|
25
|
+
this.forcePathStyle = config.forcePathStyle ?? (config.endpoint ? true : false);
|
|
14
26
|
this.bucketResolver = typeof config.bucket === "function" ? config.bucket : () => config.bucket;
|
|
15
27
|
this.client = new S3Client({
|
|
16
28
|
region: config.region,
|
|
@@ -20,7 +32,7 @@ var S3StorageAdapter = class {
|
|
|
20
32
|
},
|
|
21
33
|
...config.endpoint && {
|
|
22
34
|
endpoint: config.endpoint,
|
|
23
|
-
forcePathStyle:
|
|
35
|
+
forcePathStyle: this.forcePathStyle
|
|
24
36
|
}
|
|
25
37
|
});
|
|
26
38
|
}
|
|
@@ -32,6 +44,21 @@ var S3StorageAdapter = class {
|
|
|
32
44
|
getBucket(key) {
|
|
33
45
|
return this.bucketResolver(key);
|
|
34
46
|
}
|
|
47
|
+
getPublicUrl(bucket, key) {
|
|
48
|
+
if (this.endpoint) {
|
|
49
|
+
if (this.forcePathStyle) {
|
|
50
|
+
const baseUrl = this.endpoint.endsWith("/") ? this.endpoint.slice(0, -1) : this.endpoint;
|
|
51
|
+
return `${baseUrl}/${bucket}/${key}`;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const url = new URL(this.endpoint);
|
|
55
|
+
return `${url.protocol}//${bucket}.${url.host}${url.pathname === "/" ? "" : url.pathname}/${key}`;
|
|
56
|
+
} catch {
|
|
57
|
+
return `${this.endpoint}/${bucket}/${key}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return `https://${bucket}.s3.${this.region}.amazonaws.com/${key}`;
|
|
61
|
+
}
|
|
35
62
|
async get(key) {
|
|
36
63
|
try {
|
|
37
64
|
const response = await this.client.send(
|
|
@@ -75,6 +102,69 @@ var S3StorageAdapter = class {
|
|
|
75
102
|
throw err;
|
|
76
103
|
}
|
|
77
104
|
}
|
|
105
|
+
async checkConnection() {
|
|
106
|
+
try {
|
|
107
|
+
const bucket = this.getBucket("");
|
|
108
|
+
await this.client.send(
|
|
109
|
+
new HeadObjectCommand({ Bucket: bucket, Key: "connection-check-non-existent" })
|
|
110
|
+
);
|
|
111
|
+
return true;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (this.isNotFoundError(err)) return true;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async checkBucket(_options) {
|
|
118
|
+
try {
|
|
119
|
+
const bucket = this.getBucket("");
|
|
120
|
+
await this.client.send(new HeadObjectCommand({ Bucket: bucket, Key: "bucket-check" }));
|
|
121
|
+
return true;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (this.isNotFoundError(err)) return true;
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async checkItem(key, _options) {
|
|
128
|
+
const bucket = this.getBucket(key);
|
|
129
|
+
const response = await this.client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
|
|
130
|
+
const extension = key.split(".").pop() || "";
|
|
131
|
+
return {
|
|
132
|
+
bucket,
|
|
133
|
+
key,
|
|
134
|
+
mime: response.ContentType || "application/octet-stream",
|
|
135
|
+
extension,
|
|
136
|
+
size: response.ContentLength || 0,
|
|
137
|
+
completedUrl: this.getPublicUrl(bucket, key)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async getItem(key, _options) {
|
|
141
|
+
const bucket = this.getBucket(key);
|
|
142
|
+
const response = await this.client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
143
|
+
const extension = key.split(".").pop() || "";
|
|
144
|
+
return {
|
|
145
|
+
bucket,
|
|
146
|
+
key,
|
|
147
|
+
mime: response.ContentType || "application/octet-stream",
|
|
148
|
+
extension,
|
|
149
|
+
size: response.ContentLength || 0,
|
|
150
|
+
data: response.Body,
|
|
151
|
+
completedUrl: this.getPublicUrl(bucket, key)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async putItem(file, _options) {
|
|
155
|
+
const bucket = this.getBucket(file.key);
|
|
156
|
+
await this.put(file.key, file.file);
|
|
157
|
+
const extension = file.key.split(".").pop() || "";
|
|
158
|
+
return {
|
|
159
|
+
bucket,
|
|
160
|
+
key: file.key,
|
|
161
|
+
mime: "application/octet-stream",
|
|
162
|
+
// Should ideally be determined from extension
|
|
163
|
+
extension,
|
|
164
|
+
size: file.file.length,
|
|
165
|
+
completedUrl: this.getPublicUrl(bucket, file.key)
|
|
166
|
+
};
|
|
167
|
+
}
|
|
78
168
|
async getSize(key) {
|
|
79
169
|
try {
|
|
80
170
|
const response = await this.client.send(
|
|
@@ -109,6 +199,163 @@ var S3StorageAdapter = class {
|
|
|
109
199
|
throw err;
|
|
110
200
|
}
|
|
111
201
|
}
|
|
202
|
+
async list(prefix, options) {
|
|
203
|
+
const bucket = this.getBucket(prefix || "");
|
|
204
|
+
const response = await this.client.send(
|
|
205
|
+
new ListObjectsV2Command({
|
|
206
|
+
Bucket: bucket,
|
|
207
|
+
Prefix: prefix || void 0,
|
|
208
|
+
MaxKeys: options?.limit,
|
|
209
|
+
ContinuationToken: options?.continuationToken
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
const items = (response.Contents || []).map((obj) => ({
|
|
213
|
+
key: obj.Key || "",
|
|
214
|
+
size: obj.Size || 0,
|
|
215
|
+
lastModified: obj.LastModified,
|
|
216
|
+
etag: obj.ETag
|
|
217
|
+
}));
|
|
218
|
+
return {
|
|
219
|
+
items,
|
|
220
|
+
nextToken: response.NextContinuationToken
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async getItems(path, options) {
|
|
224
|
+
const { items } = await this.list(path, { continuationToken: options?.continuationToken });
|
|
225
|
+
const bucket = this.getBucket(path);
|
|
226
|
+
return items.map((item) => ({
|
|
227
|
+
bucket,
|
|
228
|
+
key: item.key,
|
|
229
|
+
mime: "application/octet-stream",
|
|
230
|
+
// Fallback as HeadObject for each item is expensive
|
|
231
|
+
extension: item.key.split(".").pop() || "",
|
|
232
|
+
size: item.size,
|
|
233
|
+
completedUrl: this.getPublicUrl(bucket, item.key)
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
async deleteMany(keys) {
|
|
237
|
+
if (keys.length === 0) return;
|
|
238
|
+
const bucket = this.getBucket(keys[0]);
|
|
239
|
+
await this.client.send(
|
|
240
|
+
new DeleteObjectsCommand({
|
|
241
|
+
Bucket: bucket,
|
|
242
|
+
Delete: {
|
|
243
|
+
Objects: keys.map((key) => ({ Key: key }))
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
async deleteItems(keys, _options) {
|
|
249
|
+
return this.deleteMany(keys);
|
|
250
|
+
}
|
|
251
|
+
async deleteDir(path, options) {
|
|
252
|
+
let continuationToken = options?.continuationToken;
|
|
253
|
+
do {
|
|
254
|
+
const result = await this.list(path, { continuationToken });
|
|
255
|
+
const keys = result.items.map((i) => i.key);
|
|
256
|
+
if (keys.length > 0) {
|
|
257
|
+
await this.deleteMany(keys);
|
|
258
|
+
}
|
|
259
|
+
continuationToken = result.nextToken;
|
|
260
|
+
} while (continuationToken);
|
|
261
|
+
}
|
|
262
|
+
async copy(source, destination) {
|
|
263
|
+
const sourceBucket = this.getBucket(source);
|
|
264
|
+
const destBucket = this.getBucket(destination);
|
|
265
|
+
await this.client.send(
|
|
266
|
+
new CopyObjectCommand({
|
|
267
|
+
Bucket: destBucket,
|
|
268
|
+
Key: destination,
|
|
269
|
+
CopySource: `${sourceBucket}/${source}`
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
async move(source, destination) {
|
|
274
|
+
await this.copy(source, destination);
|
|
275
|
+
await this.delete(source);
|
|
276
|
+
}
|
|
277
|
+
async moveItem(source, destinationKey, _options) {
|
|
278
|
+
await this.move(source.key, destinationKey);
|
|
279
|
+
const destBucket = this.getBucket(destinationKey);
|
|
280
|
+
return {
|
|
281
|
+
...source,
|
|
282
|
+
bucket: destBucket,
|
|
283
|
+
key: destinationKey,
|
|
284
|
+
completedUrl: this.getPublicUrl(destBucket, destinationKey)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async moveItems(sources, destinationPrefix, _options) {
|
|
288
|
+
return Promise.all(
|
|
289
|
+
sources.map((source) => {
|
|
290
|
+
const destKey = `${destinationPrefix}/${source.key.split("/").pop()}`;
|
|
291
|
+
return this.moveItem(source, destKey);
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
async createMultiPart(file, maxPartNumber, _options) {
|
|
296
|
+
const bucket = this.getBucket(file.key);
|
|
297
|
+
const response = await this.client.send(
|
|
298
|
+
new CreateMultipartUploadCommand({
|
|
299
|
+
Bucket: bucket,
|
|
300
|
+
Key: file.key
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
return {
|
|
304
|
+
bucket,
|
|
305
|
+
key: file.key,
|
|
306
|
+
uploadId: response.UploadId || "",
|
|
307
|
+
lastPartNumber: 0,
|
|
308
|
+
maxPartNumber,
|
|
309
|
+
parts: []
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
async putItemMultiPart(multipart, partNumber, file, _options) {
|
|
313
|
+
const response = await this.client.send(
|
|
314
|
+
new UploadPartCommand({
|
|
315
|
+
Bucket: multipart.bucket,
|
|
316
|
+
Key: multipart.key,
|
|
317
|
+
UploadId: multipart.uploadId,
|
|
318
|
+
PartNumber: partNumber,
|
|
319
|
+
Body: file
|
|
320
|
+
})
|
|
321
|
+
);
|
|
322
|
+
const newPart = {
|
|
323
|
+
size: file.length,
|
|
324
|
+
eTag: response.ETag || "",
|
|
325
|
+
partNumber
|
|
326
|
+
};
|
|
327
|
+
return {
|
|
328
|
+
...multipart,
|
|
329
|
+
lastPartNumber: partNumber,
|
|
330
|
+
parts: [...multipart.parts, newPart]
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
async completeMultipart(key, uploadId, parts, _options) {
|
|
334
|
+
const bucket = this.getBucket(key);
|
|
335
|
+
await this.client.send(
|
|
336
|
+
new CompleteMultipartUploadCommand({
|
|
337
|
+
Bucket: bucket,
|
|
338
|
+
Key: key,
|
|
339
|
+
UploadId: uploadId,
|
|
340
|
+
MultipartUpload: {
|
|
341
|
+
Parts: parts.map((p) => ({
|
|
342
|
+
ETag: p.eTag,
|
|
343
|
+
PartNumber: p.partNumber
|
|
344
|
+
}))
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
async abortMultipart(key, uploadId, _options) {
|
|
350
|
+
const bucket = this.getBucket(key);
|
|
351
|
+
await this.client.send(
|
|
352
|
+
new AbortMultipartUploadCommand({
|
|
353
|
+
Bucket: bucket,
|
|
354
|
+
Key: key,
|
|
355
|
+
UploadId: uploadId
|
|
356
|
+
})
|
|
357
|
+
);
|
|
358
|
+
}
|
|
112
359
|
async getUrl(key, options) {
|
|
113
360
|
const expiresIn = options?.expiresIn ?? DEFAULT_EXPIRES_IN;
|
|
114
361
|
const command = new GetObjectCommand({
|
|
@@ -117,6 +364,136 @@ var S3StorageAdapter = class {
|
|
|
117
364
|
});
|
|
118
365
|
return getSignedUrl(this.client, command, { expiresIn });
|
|
119
366
|
}
|
|
367
|
+
async presignGetItem(key, options) {
|
|
368
|
+
const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;
|
|
369
|
+
const presignUrl = await this.getUrl(key, { expiresIn: expiredIn });
|
|
370
|
+
const extension = key.split(".").pop() || "";
|
|
371
|
+
return {
|
|
372
|
+
key,
|
|
373
|
+
mime: "application/octet-stream",
|
|
374
|
+
extension,
|
|
375
|
+
presignUrl,
|
|
376
|
+
expiredIn
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
async presignPutItem(file, options) {
|
|
380
|
+
const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;
|
|
381
|
+
const bucket = this.getBucket(file.key);
|
|
382
|
+
const command = new PutObjectCommand({
|
|
383
|
+
Bucket: bucket,
|
|
384
|
+
Key: file.key
|
|
385
|
+
});
|
|
386
|
+
const presignUrl = await getSignedUrl(this.client, command, { expiresIn: expiredIn });
|
|
387
|
+
const extension = file.key.split(".").pop() || "";
|
|
388
|
+
return {
|
|
389
|
+
key: file.key,
|
|
390
|
+
mime: "application/octet-stream",
|
|
391
|
+
extension,
|
|
392
|
+
presignUrl,
|
|
393
|
+
expiredIn
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
async presignPutItemPart(file, options) {
|
|
397
|
+
const expiredIn = options?.expired ?? DEFAULT_EXPIRES_IN;
|
|
398
|
+
const bucket = this.getBucket(file.key);
|
|
399
|
+
const command = new UploadPartCommand({
|
|
400
|
+
Bucket: bucket,
|
|
401
|
+
Key: file.key,
|
|
402
|
+
UploadId: file.uploadId,
|
|
403
|
+
PartNumber: file.partNumber
|
|
404
|
+
});
|
|
405
|
+
const presignUrl = await getSignedUrl(this.client, command, { expiresIn: expiredIn });
|
|
406
|
+
const extension = file.key.split(".").pop() || "";
|
|
407
|
+
return {
|
|
408
|
+
key: file.key,
|
|
409
|
+
mime: "application/octet-stream",
|
|
410
|
+
extension,
|
|
411
|
+
presignUrl,
|
|
412
|
+
expiredIn,
|
|
413
|
+
size: file.size || 0,
|
|
414
|
+
partNumber: file.partNumber
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
async settingBucketPolicy(_options) {
|
|
418
|
+
const bucket = this.getBucket("");
|
|
419
|
+
const policy = JSON.stringify({
|
|
420
|
+
Version: "2012-10-17",
|
|
421
|
+
Statement: [
|
|
422
|
+
{
|
|
423
|
+
Sid: "PublicRead",
|
|
424
|
+
Effect: "Allow",
|
|
425
|
+
Principal: "*",
|
|
426
|
+
Action: ["s3:GetObject"],
|
|
427
|
+
Resource: [`arn:aws:s3:::${bucket}/*`]
|
|
428
|
+
}
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
await this.client.send(new PutBucketPolicyCommand({ Bucket: bucket, Policy: policy }));
|
|
432
|
+
}
|
|
433
|
+
async settingCorsConfiguration(_options) {
|
|
434
|
+
const bucket = this.getBucket("");
|
|
435
|
+
await this.client.send(
|
|
436
|
+
new PutBucketCorsCommand({
|
|
437
|
+
Bucket: bucket,
|
|
438
|
+
CORSConfiguration: {
|
|
439
|
+
CORSRules: [
|
|
440
|
+
{
|
|
441
|
+
AllowedOrigins: ["*"],
|
|
442
|
+
AllowedMethods: ["GET", "PUT", "POST", "DELETE", "HEAD"],
|
|
443
|
+
AllowedHeaders: ["*"],
|
|
444
|
+
MaxAgeSeconds: 3e3
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
async settingBucketExpiredObjectLifecycle(_options) {
|
|
452
|
+
const bucket = this.getBucket("");
|
|
453
|
+
await this.client.send(
|
|
454
|
+
new PutBucketLifecycleConfigurationCommand({
|
|
455
|
+
Bucket: bucket,
|
|
456
|
+
LifecycleConfiguration: {
|
|
457
|
+
Rules: [
|
|
458
|
+
{
|
|
459
|
+
ID: "ExpireOldObjects",
|
|
460
|
+
Status: "Enabled",
|
|
461
|
+
Prefix: "",
|
|
462
|
+
Expiration: { Days: 30 }
|
|
463
|
+
}
|
|
464
|
+
]
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
async settingDisableAclConfiguration(_options) {
|
|
470
|
+
}
|
|
471
|
+
async settingBlockPublicAccessConfiguration(_options) {
|
|
472
|
+
const bucket = this.getBucket("");
|
|
473
|
+
await this.client.send(
|
|
474
|
+
new PutPublicAccessBlockCommand({
|
|
475
|
+
Bucket: bucket,
|
|
476
|
+
PublicAccessBlockConfiguration: {
|
|
477
|
+
BlockPublicAcls: true,
|
|
478
|
+
IgnorePublicAcls: true,
|
|
479
|
+
BlockPublicPolicy: true,
|
|
480
|
+
RestrictPublicBuckets: true
|
|
481
|
+
}
|
|
482
|
+
})
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
mapPresign(file, _options) {
|
|
486
|
+
const bucket = this.getBucket(file.key);
|
|
487
|
+
const extension = file.key.split(".").pop() || "";
|
|
488
|
+
return {
|
|
489
|
+
bucket,
|
|
490
|
+
key: file.key,
|
|
491
|
+
mime: "application/octet-stream",
|
|
492
|
+
extension,
|
|
493
|
+
size: file.size || 0,
|
|
494
|
+
completedUrl: this.getPublicUrl(bucket, file.key)
|
|
495
|
+
};
|
|
496
|
+
}
|
|
120
497
|
/**
|
|
121
498
|
* Create a presigned upload for direct-to-storage upload.
|
|
122
499
|
*
|
|
@@ -218,6 +595,6 @@ var S3StorageAdapter = class {
|
|
|
218
595
|
}
|
|
219
596
|
};
|
|
220
597
|
|
|
221
|
-
export { S3StorageAdapter };
|
|
598
|
+
export { EnumS3Accessibility, S3StorageAdapter };
|
|
222
599
|
//# sourceMappingURL=index.mjs.map
|
|
223
600
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/s3-storage.adapter.ts"],"names":[],"mappings":"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","file":"index.mjs","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"],"mappings":"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","file":"index.mjs","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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-media/adapter-storage-s3",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "S3 storage adapter for Better Media",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@aws-sdk/client-s3": "^3.700.0",
|
|
21
21
|
"@aws-sdk/s3-presigned-post": "^3.700.0",
|
|
22
22
|
"@aws-sdk/s3-request-presigner": "^3.700.0",
|
|
23
|
-
"@better-media/core": "0.
|
|
23
|
+
"@better-media/core": "0.2.0"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"better-media",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"type": "git",
|
|
37
37
|
"url": "git+https://github.com/AbenezerAtnafu/better-media.git"
|
|
38
38
|
},
|
|
39
|
-
"homepage": "https://better-media.
|
|
39
|
+
"homepage": "https://better-media-platform.vercel.app/",
|
|
40
40
|
"bugs": {
|
|
41
41
|
"url": "https://github.com/AbenezerAtnafu/better-media/issues"
|
|
42
42
|
},
|