@quatrain/storage-s3 1.2.4 → 1.2.6
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AbstractStorageAdapter, FileType, FileResponseLinkType, StorageParameters, DownloadFileMetaType } from '@quatrain/storage';
|
|
1
|
+
import { AbstractStorageAdapter, FileType, FileResponseLinkType, StorageParameters, DownloadFileMetaType, BucketStatsType } from '@quatrain/storage';
|
|
2
2
|
import { Readable, Stream } from 'node:stream';
|
|
3
3
|
import { S3Client } from '@aws-sdk/client-s3';
|
|
4
4
|
/**
|
|
@@ -111,4 +111,12 @@ export declare class S3StorageAdapter extends AbstractStorageAdapter {
|
|
|
111
111
|
* @returns A promise resolving to the upload URL instructions.
|
|
112
112
|
*/
|
|
113
113
|
getUploadUrl(file: FileType, expiresIn?: number): Promise<FileResponseLinkType>;
|
|
114
|
+
/**
|
|
115
|
+
* Computes statistics for a given bucket in S3.
|
|
116
|
+
* Iterates over all objects in the bucket to compute total count and size.
|
|
117
|
+
*
|
|
118
|
+
* @param bucket - Target bucket name.
|
|
119
|
+
* @returns A promise resolving to bucket statistics.
|
|
120
|
+
*/
|
|
121
|
+
getBucketStats(bucket?: string): Promise<BucketStatsType>;
|
|
114
122
|
}
|
package/dist/S3StorageAdapter.js
CHANGED
|
@@ -209,7 +209,7 @@ class S3StorageAdapter extends storage_1.AbstractStorageAdapter {
|
|
|
209
209
|
});
|
|
210
210
|
const item = yield ((_a = this._client) === null || _a === void 0 ? void 0 : _a.send(command));
|
|
211
211
|
const ByteArray = yield ((_b = item === null || item === void 0 ? void 0 : item.Body) === null || _b === void 0 ? void 0 : _b.transformToByteArray());
|
|
212
|
-
const buffer = Buffer.from(ByteArray
|
|
212
|
+
const buffer = Buffer.from(ByteArray);
|
|
213
213
|
const readable = new node_stream_1.Readable();
|
|
214
214
|
readable.push(buffer);
|
|
215
215
|
readable.push(null);
|
|
@@ -250,7 +250,7 @@ class S3StorageAdapter extends storage_1.AbstractStorageAdapter {
|
|
|
250
250
|
});
|
|
251
251
|
const item = yield this._client.send(command);
|
|
252
252
|
const ByteArray = yield ((_a = item.Body) === null || _a === void 0 ? void 0 : _a.transformToByteArray());
|
|
253
|
-
const buffer = Buffer.from(ByteArray
|
|
253
|
+
const buffer = Buffer.from(ByteArray);
|
|
254
254
|
const readable = new node_stream_1.Readable();
|
|
255
255
|
readable.push(buffer);
|
|
256
256
|
readable.push(null);
|
|
@@ -294,5 +294,69 @@ class S3StorageAdapter extends storage_1.AbstractStorageAdapter {
|
|
|
294
294
|
};
|
|
295
295
|
});
|
|
296
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Computes statistics for a given bucket in S3.
|
|
299
|
+
* Iterates over all objects in the bucket to compute total count and size.
|
|
300
|
+
*
|
|
301
|
+
* @param bucket - Target bucket name.
|
|
302
|
+
* @returns A promise resolving to bucket statistics.
|
|
303
|
+
*/
|
|
304
|
+
getBucketStats(bucket) {
|
|
305
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
306
|
+
var _a;
|
|
307
|
+
const targetBucket = bucket || this._params.config.bucket;
|
|
308
|
+
let totalObjects = 0;
|
|
309
|
+
let totalSize = 0;
|
|
310
|
+
let isTruncated = true;
|
|
311
|
+
let continuationToken = undefined;
|
|
312
|
+
const folders = {};
|
|
313
|
+
storage_1.Storage.info(`Fetching bucket stats for ${targetBucket}`);
|
|
314
|
+
while (isTruncated) {
|
|
315
|
+
const input = {
|
|
316
|
+
Bucket: targetBucket,
|
|
317
|
+
ContinuationToken: continuationToken,
|
|
318
|
+
};
|
|
319
|
+
const command = new client_s3_1.ListObjectsV2Command(input);
|
|
320
|
+
const res = yield this._client.send(command);
|
|
321
|
+
if (res.Contents) {
|
|
322
|
+
for (const item of res.Contents) {
|
|
323
|
+
const size = item.Size || 0;
|
|
324
|
+
totalObjects++;
|
|
325
|
+
totalSize += size;
|
|
326
|
+
if (item.Key) {
|
|
327
|
+
const parts = item.Key.split('/');
|
|
328
|
+
parts.pop(); // remove file name to get exact folder
|
|
329
|
+
const folderPath = parts.length > 0 ? parts.join('/') : '/';
|
|
330
|
+
const folderName = parts.length > 0 ? parts[parts.length - 1] : '/';
|
|
331
|
+
if (!folders[folderPath]) {
|
|
332
|
+
folders[folderPath] = {
|
|
333
|
+
name: folderName,
|
|
334
|
+
path: folderPath,
|
|
335
|
+
totalObjects: 0,
|
|
336
|
+
totalSize: 0,
|
|
337
|
+
lastModified: item.LastModified
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
else if (item.LastModified) {
|
|
341
|
+
if (!folders[folderPath].lastModified || item.LastModified > folders[folderPath].lastModified) {
|
|
342
|
+
folders[folderPath].lastModified = item.LastModified;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
folders[folderPath].totalObjects++;
|
|
346
|
+
folders[folderPath].totalSize += size;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
isTruncated = (_a = res.IsTruncated) !== null && _a !== void 0 ? _a : false;
|
|
351
|
+
continuationToken = res.NextContinuationToken;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
bucket: targetBucket,
|
|
355
|
+
totalObjects,
|
|
356
|
+
totalSize,
|
|
357
|
+
folders,
|
|
358
|
+
};
|
|
359
|
+
});
|
|
360
|
+
}
|
|
297
361
|
}
|
|
298
362
|
exports.S3StorageAdapter = S3StorageAdapter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quatrain/storage-s3",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "Storage adapter for S3 compatible services",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@aws-sdk/client-s3": "^3.337.0",
|
|
35
35
|
"@aws-sdk/s3-request-presigner": "^3.342.0",
|
|
36
|
-
"@quatrain/storage": "^1.2.
|
|
36
|
+
"@quatrain/storage": "^1.2.7",
|
|
37
37
|
"fs-extra": "^11.2.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
package/src/S3StorageAdapter.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
FileResponseLinkType,
|
|
6
6
|
StorageParameters,
|
|
7
7
|
DownloadFileMetaType,
|
|
8
|
+
BucketStatsType,
|
|
8
9
|
} from '@quatrain/storage'
|
|
9
10
|
import { Readable, Stream } from 'node:stream'
|
|
10
11
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
|
@@ -17,6 +18,9 @@ import {
|
|
|
17
18
|
CopyObjectCommand,
|
|
18
19
|
HeadObjectCommand,
|
|
19
20
|
ListBucketsCommand,
|
|
21
|
+
ListObjectsV2Command,
|
|
22
|
+
ListObjectsV2CommandInput,
|
|
23
|
+
ListObjectsV2CommandOutput,
|
|
20
24
|
} from '@aws-sdk/client-s3'
|
|
21
25
|
|
|
22
26
|
/**
|
|
@@ -236,7 +240,7 @@ export class S3StorageAdapter extends AbstractStorageAdapter {
|
|
|
236
240
|
})
|
|
237
241
|
const item = await this._client?.send(command)
|
|
238
242
|
const ByteArray: any = await item?.Body?.transformToByteArray()
|
|
239
|
-
const buffer = Buffer.from(ByteArray
|
|
243
|
+
const buffer = Buffer.from(ByteArray)
|
|
240
244
|
const readable = new Readable()
|
|
241
245
|
readable.push(buffer)
|
|
242
246
|
readable.push(null)
|
|
@@ -281,7 +285,7 @@ export class S3StorageAdapter extends AbstractStorageAdapter {
|
|
|
281
285
|
})
|
|
282
286
|
const item = await this._client.send(command)
|
|
283
287
|
const ByteArray: any = await item.Body?.transformToByteArray()
|
|
284
|
-
const buffer = Buffer.from(ByteArray
|
|
288
|
+
const buffer = Buffer.from(ByteArray)
|
|
285
289
|
const readable = new Readable()
|
|
286
290
|
readable.push(buffer)
|
|
287
291
|
readable.push(null)
|
|
@@ -331,4 +335,70 @@ export class S3StorageAdapter extends AbstractStorageAdapter {
|
|
|
331
335
|
expiresIn,
|
|
332
336
|
}
|
|
333
337
|
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Computes statistics for a given bucket in S3.
|
|
341
|
+
* Iterates over all objects in the bucket to compute total count and size.
|
|
342
|
+
*
|
|
343
|
+
* @param bucket - Target bucket name.
|
|
344
|
+
* @returns A promise resolving to bucket statistics.
|
|
345
|
+
*/
|
|
346
|
+
async getBucketStats(bucket?: string): Promise<BucketStatsType> {
|
|
347
|
+
const targetBucket = bucket || this._params.config.bucket
|
|
348
|
+
let totalObjects = 0
|
|
349
|
+
let totalSize = 0
|
|
350
|
+
let isTruncated = true
|
|
351
|
+
let continuationToken: string | undefined = undefined
|
|
352
|
+
const folders: Record<string, any> = {}
|
|
353
|
+
|
|
354
|
+
Storage.info(`Fetching bucket stats for ${targetBucket}`)
|
|
355
|
+
|
|
356
|
+
while (isTruncated) {
|
|
357
|
+
const input: ListObjectsV2CommandInput = {
|
|
358
|
+
Bucket: targetBucket,
|
|
359
|
+
ContinuationToken: continuationToken,
|
|
360
|
+
}
|
|
361
|
+
const command = new ListObjectsV2Command(input)
|
|
362
|
+
const res: ListObjectsV2CommandOutput = await this._client.send(command)
|
|
363
|
+
if (res.Contents) {
|
|
364
|
+
for (const item of res.Contents) {
|
|
365
|
+
const size = item.Size || 0
|
|
366
|
+
totalObjects++
|
|
367
|
+
totalSize += size
|
|
368
|
+
|
|
369
|
+
if (item.Key) {
|
|
370
|
+
const parts = item.Key.split('/')
|
|
371
|
+
parts.pop() // remove file name to get exact folder
|
|
372
|
+
const folderPath = parts.length > 0 ? parts.join('/') : '/'
|
|
373
|
+
const folderName = parts.length > 0 ? parts[parts.length - 1] : '/'
|
|
374
|
+
|
|
375
|
+
if (!folders[folderPath]) {
|
|
376
|
+
folders[folderPath] = {
|
|
377
|
+
name: folderName,
|
|
378
|
+
path: folderPath,
|
|
379
|
+
totalObjects: 0,
|
|
380
|
+
totalSize: 0,
|
|
381
|
+
lastModified: item.LastModified
|
|
382
|
+
}
|
|
383
|
+
} else if (item.LastModified) {
|
|
384
|
+
if (!folders[folderPath].lastModified || item.LastModified > folders[folderPath].lastModified) {
|
|
385
|
+
folders[folderPath].lastModified = item.LastModified
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
folders[folderPath].totalObjects++
|
|
389
|
+
folders[folderPath].totalSize += size
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
isTruncated = res.IsTruncated ?? false
|
|
394
|
+
continuationToken = res.NextContinuationToken
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
bucket: targetBucket,
|
|
399
|
+
totalObjects,
|
|
400
|
+
totalSize,
|
|
401
|
+
folders,
|
|
402
|
+
}
|
|
403
|
+
}
|
|
334
404
|
}
|