@quatrain/storage-s3 1.2.5 → 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
  }
@@ -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.5",
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.6",
36
+ "@quatrain/storage": "^1.2.7",
37
37
  "fs-extra": "^11.2.0"
38
38
  },
39
39
  "scripts": {
@@ -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
  /**
@@ -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
  }