@hdriel/aws-utils 1.1.3 → 1.1.5

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 CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  A powerful, type-safe wrapper around AWS S3 SDK v3 that simplifies S3 operations with advanced features like streaming, file uploads, directory management, and LocalStack support.
4
4
 
5
+
5
6
  ## Features
6
7
 
7
8
  ✨ **Simplified API** - Clean, intuitive methods for common S3 operations
@@ -15,6 +16,11 @@ A powerful, type-safe wrapper around AWS S3 SDK v3 that simplifies S3 operations
15
16
  ⚡ **Connection Pooling** - Optimized HTTP/HTTPS agents for better performance
16
17
  📊 **Pagination** - Built-in pagination for large directory listings
17
18
 
19
+ # FULL DEMO PROJECT EXAMPLE:
20
+ please see this project code before using: [aws-utils-demo github link!](https://github.com/hdriel/aws-utils-demo)
21
+ ![Main Screen - Preview](readme-assets/demo-bucket-image-preview.webp)
22
+
23
+
18
24
  ## Installation
19
25
 
20
26
  ```bash
@@ -98,8 +104,9 @@ interface S3UtilProps {
98
104
  ```
99
105
  ----
100
106
 
101
- # FULL DEMO PROJECT EXAMPLE:
102
- please see this project code before using: [AWS-UTILS-DEMO github linke](https://github.com/hdriel/aws-utils-demo)
107
+ # FULL DEMO PROJECT EXAMPLE:
108
+ please see this project code before using: [aws-utils-demo github link!](https://github.com/hdriel/aws-utils-demo)
109
+ ![Main Screen - Preview](readme-assets/login-screen.webp)
103
110
 
104
111
 
105
112
  ---
@@ -107,27 +114,19 @@ please see this project code before using: [AWS-UTILS-DEMO github linke](https:/
107
114
 
108
115
  ## Core Features
109
116
 
110
- ### 🪣 Bucket Operations
117
+ ### C.R.U.D Bucket Operations
111
118
 
112
- #### Initialize Bucket
113
119
  ```typescript
114
- // Create private bucket
115
- await s3.initBucket('private');
116
-
117
- // Create public bucket
118
- await s3.initBucket('public-read');
119
-
120
- // With location constraint
121
- await s3.initBucket('private', {
122
- includeConstraintLocation: true
123
- });
124
- ```
120
+ // CREATE
121
+ await s3.initBucket('private'); // Create private bucket (if not exists)
122
+ await s3.initBucket('public-read'); // Create public bucket (if not exists)
123
+ // Could provided includeConstraintLocation option, like:
124
+ await s3.initBucket('private', { includeConstraintLocation: true} );
125
125
 
126
- #### Bucket Information
127
- ```typescript
126
+ // READ
127
+ const exists = await s3.isBucketExists(); // check for existance bucket
128
128
  const info = await s3.bucketInfo();
129
- console.log(info);
130
- // {
129
+ // info = {
131
130
  // name: 'my-bucket',
132
131
  // region: 'us-east-1',
133
132
  // exists: true,
@@ -137,44 +136,47 @@ console.log(info);
137
136
  // publicAccessBlock: { ... },
138
137
  // policy: { ... }
139
138
  // }
140
- ```
141
139
 
142
- #### Check Bucket Exists
143
- ```typescript
144
- const exists = await s3.isBucketExists();
145
- ```
140
+ const buckets = await s3.getBucketList(); // get all bucket list from aws s3 storage
141
+ // Could get bucket list with public access info like:
142
+ /*
143
+ bucket list option: {
144
+ Name?: string | undefined;
145
+ CreationDate?: Date | undefined;
146
+ BucketRegion?: string | undefined;
147
+ BucketArn?: string | undefined;
148
+ PublicAccessBlockConfiguration: {
149
+ BlockPublicAcls?: boolean | undefined;
150
+ IgnorePublicAcls?: boolean | undefined;
151
+ BlockPublicPolicy?: boolean | undefined;
152
+ RestrictPublicBuckets?: boolean | undefined;
153
+ }
154
+ }
155
+ */
156
+ const bucketsWithAccess = await s3.getBucketList({ includePublicAccess: true });
146
157
 
147
- #### Delete Bucket
148
- ```typescript
149
- // Delete bucket (must be empty)
150
- await s3.destroyBucket();
158
+ // UPDATE
159
+ s3.changeBucket('another-bucket'); // Switch to different bucket
151
160
 
152
- // Force delete with all contents
153
- await s3.destroyBucket(true);
161
+ // DELETE
162
+ await s3.destroyBucket(); // delete empty bucket
163
+ await s3.destroyBucket(true); // Force delete with all contents and bucket
154
164
  ```
155
165
 
156
- #### List All Buckets
157
- ```typescript
158
- const buckets = await s3.getBucketList();
159
-
160
- // Include public access configuration
161
- const bucketsWithAccess = await s3.getBucketList({
162
- includePublicAccess: true
163
- });
164
- ```
166
+ ### 📁 C.R.U.D Directory Operations
165
167
 
166
- ### 📁 Directory Operations
168
+ * auto decodeURIComponent for all directory input params
169
+ * handle directory issue (no matter if prefix/postfix slashes)
167
170
 
168
171
  #### Create Directory
169
172
  ```typescript
173
+ // CREATE
170
174
  await s3.createDirectory('/uploads/images');
171
- ```
172
175
 
173
- #### List Directory Contents
174
- ```typescript
176
+ // READ
177
+ const exists = await s3.directoryExists('/uploads/images'); // check for existance directory
175
178
  const { directories, files } = await s3.directoryList('/uploads');
176
-
177
- console.log('Subdirectories:', directories);
179
+ console.log('Subdirectories:', directories); // string[] directories like: ['images', 'test']
178
180
  console.log('Files:', files);
179
181
  // files: [
180
182
  // {
@@ -185,36 +187,24 @@ console.log('Files:', files);
185
187
  // Location: 'https://...'
186
188
  // }
187
189
  // ]
188
- ```
189
190
 
190
- #### Paginated Directory Listing
191
- ```typescript
192
191
  // Get second page with 50 items per page
193
- const result = await s3.directoryListPaginated('/uploads', {
194
- pageSize: 50,
195
- pageNumber: 1
192
+ const { directories, files, totalFetched } = await s3.directoryListPaginated('/uploads', {
193
+ pageSize: 50,
194
+ pageNumber: 1 // pageNumber is zero base (0-page one, 1- page two, ...)
196
195
  });
197
196
 
198
- console.log(result.directories); // Array of directory names
199
- console.log(result.files); // Array of file objects
200
- console.log(result.totalFetched); // Number of items returned
201
- ```
197
+ // DELETE
198
+ await s3.deleteDirectory('/uploads/temp'); // Delete directory and all contents
202
199
 
203
- #### Delete Directory
204
- ```typescript
205
- // Delete directory and all contents
206
- await s3.deleteDirectory('/uploads/temp');
207
200
  ```
208
201
 
209
- #### Check Directory Exists
210
- ```typescript
211
- const exists = await s3.directoryExists('/uploads/images');
212
- ```
213
202
 
214
- ### 📄 File Operations
203
+ ### 📄 C.R.U.D File Operations
215
204
 
216
- #### Upload File
217
205
  ```typescript
206
+ // CREATE
207
+ // > Upload File
218
208
  import { ACLs } from '@hdriel/aws-utils';
219
209
 
220
210
  // Upload buffer
@@ -225,79 +215,45 @@ await s3.uploadFile('/public/image.jpg', buffer, ACLs.public_read);
225
215
 
226
216
  // Upload with version tag
227
217
  await s3.uploadFile('/docs/v2.pdf', buffer, ACLs.private, '2.0.0');
228
- ```
229
-
230
- #### Check File Exists
231
- ```typescript
232
- const exists = await s3.fileExists('/documents/file.pdf');
233
- ```
234
-
235
- #### Get File Content
236
- ```typescript
237
- // As buffer
238
- const buffer = await s3.fileContent('/documents/file.pdf');
239
218
 
240
- // As base64 string
241
- const base64 = await s3.fileContent('/image.jpg', 'base64');
219
+ // > Generate Presigned URL
220
+ const url = await s3.fileUrl('/private/document.pdf'); // Expires in 15 minutes (default)
221
+ const url = await s3.fileUrl('/private/document.pdf', '1h'); // Custom expiration in string value
222
+ const url = await s3.fileUrl('/private/document.pdf', 3600); // Custom expiration in seconds value
242
223
 
243
- // As UTF-8 string
244
- const text = await s3.fileContent('/data.json', 'utf8');
245
- ```
246
-
247
- #### File Information
248
- ```typescript
224
+ // READ
225
+ const exists = await s3.fileExists('/documents/file.pdf'); // check for existance file
249
226
  const info = await s3.fileInfo('/documents/file.pdf');
250
- console.log(info.ContentLength);
251
- console.log(info.ContentType);
252
- console.log(info.LastModified);
253
- ```
227
+ const files = await s3.fileListInfo('/documents'); // List all files in directory
228
+ const pdfFiles = await s3.fileListInfo('/documents', 'report-'); // List files with prefix
229
+ // Paginated file listing - Recommanded way!
230
+ const { files, totalFetched } = await s3.fileListInfoPaginated('/documents', {
231
+ fileNamePrefix: 'invoice-',
232
+ pageSize: 100,
233
+ pageNumber: 0
234
+ });
235
+ const version = await s3.fileVersion('/documents/file.pdf'); // Get file version
254
236
 
255
- #### List Files
256
- ```typescript
257
- // List all files in directory
258
- const files = await s3.fileListInfo('/documents');
259
237
 
260
- // List files with prefix
261
- const pdfFiles = await s3.fileListInfo('/documents', 'report-');
238
+ // > Get File Content
239
+ const buffer = await s3.fileContent('/documents/file.pdf'); // As buffer
240
+ const base64 = await s3.fileContent('/image.jpg', 'base64'); // As base64 string
241
+ const text = await s3.fileContent('/data.json', 'utf8'); // As UTF-8 string
262
242
 
263
- // Paginated file listing
264
- const { files, totalFetched } = await s3.fileListInfoPaginated('/documents', {
265
- fileNamePrefix: 'invoice-',
266
- pageSize: 100,
267
- pageNumber: 0
268
- });
269
- ```
270
-
271
- #### File Size
272
- ```typescript
243
+ // > Get File Size
273
244
  const bytes = await s3.sizeOf('/large-file.zip');
274
245
  const kb = await s3.sizeOf('/large-file.zip', 'KB');
275
246
  const mb = await s3.sizeOf('/large-file.zip', 'MB');
276
247
  const gb = await s3.sizeOf('/large-file.zip', 'GB');
277
- ```
278
248
 
279
- #### Delete File
280
- ```typescript
281
- await s3.deleteFile('/documents/old-file.pdf');
282
- ```
249
+ // UPDATE
250
+ // > File Tagging
251
+ await s3.taggingFile('/documents/file.pdf', {Key: 'version', Value: '1.0.0'}); // Tag file with version
283
252
 
284
- #### Generate Presigned URL
285
- ```typescript
286
- // Expires in 15 minutes (default)
287
- const url = await s3.fileUrl('/private/document.pdf');
288
-
289
- // Custom expiration
290
- const url = await s3.fileUrl('/private/document.pdf', '1h');
291
- const url = await s3.fileUrl('/private/document.pdf', 3600); // seconds
292
- ```
293
253
 
294
- #### File Tagging
295
- ```typescript
296
- // Tag file with version
297
- await s3.taggingFile('/documents/file.pdf', '1.0.0');
254
+ // DELETE
255
+ await s3.deleteFile('/documents/old-file.pdf');
298
256
 
299
- // Get file version
300
- const version = await s3.fileVersion('/documents/file.pdf');
301
257
  ```
302
258
 
303
259
  ### 🎬 Streaming & Express.js Integration
@@ -487,11 +443,10 @@ import { S3LocalstackUtil } from '@hdriel/aws-utils';
487
443
 
488
444
  const s3 = new S3LocalstackUtil({
489
445
  bucket: 'test-bucket',
490
- endpoint: 'http://localhost:4566',
491
- region: 'us-east-1',
492
- accessKeyId: 'test',
493
- secretAccessKey: 'test',
494
- s3ForcePathStyle: true
446
+ // endpoint: 'http://localhost:4566', // get from .env file
447
+ // region: 'us-east-1', // get from .env file
448
+ // accessKeyId: 'test', // get from .env file
449
+ // secretAccessKey: 'test', // get from .env file
495
450
  });
496
451
 
497
452
  // Use same API as S3Util
@@ -540,19 +495,6 @@ services:
540
495
 
541
496
  ### Dynamic Bucket Switching
542
497
 
543
- ```typescript
544
- const s3 = new S3Util({
545
- bucket: 'default-bucket',
546
- // ... other config
547
- });
548
-
549
- // Switch to different bucket
550
- s3.changeBucket('another-bucket');
551
-
552
- // Operations now use 'another-bucket'
553
- await s3.fileExists('/file.txt');
554
- ```
555
-
556
498
  ### Custom Logger Integration
557
499
 
558
500
  ```typescript
@@ -561,9 +503,9 @@ import { Logger } from 'stack-trace-logger';
561
503
  const logger = new Logger('S3Service');
562
504
 
563
505
  const s3 = new S3Util({
564
- bucket: 'my-bucket',
565
- logger,
566
- reqId: 'request-123'
506
+ bucket: 'my-bucket',
507
+ reqId: 'request-123',
508
+ logger,
567
509
  });
568
510
 
569
511
  // All operations will log with your logger
@@ -582,152 +524,16 @@ The utility includes optimized HTTP/HTTPS agents:
582
524
  // - socketTimeout: 30000ms
583
525
  ```
584
526
 
585
- ### Batch Operations
586
-
587
- ```typescript
588
- // Upload multiple files in parallel
589
- const files = [
590
- { path: '/docs/file1.pdf', data: buffer1 },
591
- { path: '/docs/file2.pdf', data: buffer2 },
592
- { path: '/docs/file3.pdf', data: buffer3 }
593
- ];
594
-
595
- await Promise.all(
596
- files.map(file => s3.uploadFile(file.path, file.data))
597
- );
598
-
599
- // Delete multiple files
600
- const filesToDelete = ['/old/file1.txt', '/old/file2.txt'];
601
- await Promise.all(
602
- filesToDelete.map(path => s3.deleteFile(path))
603
- );
604
- ```
605
527
 
606
528
  ## 📋 Complete Express.js Example
529
+ # FULL DEMO PROJECT EXAMPLE:
530
+ please see this project code before using: [aws-utils-demo github link!](https://github.com/hdriel/aws-utils-demo)
531
+ ![Main Screen - Preview](readme-assets/demo-bucket-image-preview.webp)
607
532
 
608
- ```typescript
609
- import express from 'express';
610
- import { S3Util, ACLs } from '@hdriel/aws-utils';
611
-
612
- const app = express();
613
- const s3 = new S3Util({
614
- bucket: process.env.S3_BUCKET!,
615
- region: process.env.AWS_REGION,
616
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
617
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
618
- });
619
-
620
- // Initialize bucket on startup
621
- (async () => {
622
- await s3.initBucket();
623
- console.log('S3 bucket initialized');
624
- })();
625
-
626
- // Upload endpoint
627
- app.post('/api/upload',
628
- s3.uploadSingleFile('file', '/uploads', {
629
- maxFileSize: '10MB',
630
- fileType: ['image', 'application'],
631
- filename: async (req, file) => {
632
- const timestamp = Date.now();
633
- const sanitized = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
634
- return `${timestamp}-${sanitized}`;
635
- }
636
- }),
637
- async (req, res) => {
638
- const { key, location, size } = req.s3File!;
639
-
640
- // Generate temporary URL
641
- const url = await s3.fileUrl(key, '1h');
642
-
643
- res.json({ key, location, size, temporaryUrl: url });
644
- }
645
- );
646
-
647
- // Download endpoint
648
- app.get('/api/download/:key(*)',
649
- async (req, res, next) => {
650
- const key = decodeURIComponent(req.params.key);
651
- const ctrl = await s3.getStreamFileCtrl({
652
- filePath: key,
653
- forDownloading: true
654
- });
655
- ctrl(req, res, next);
656
- }
657
- );
658
-
659
- // List files endpoint
660
- app.get('/api/files', async (req, res) => {
661
- const { page = '0', size = '50' } = req.query;
662
-
663
- const result = await s3.directoryListPaginated('/uploads', {
664
- pageNumber: parseInt(page as string),
665
- pageSize: parseInt(size as string)
666
- });
667
-
668
- res.json(result);
669
- });
670
-
671
- // Delete file endpoint
672
- app.delete('/api/files/:key(*)', async (req, res) => {
673
- const key = decodeURIComponent(req.params.key);
674
- await s3.deleteFile(key);
675
- res.json({ success: true });
676
- });
677
-
678
- // Video streaming endpoint
679
- app.get('/api/video/:id',
680
- async (req, res, next) => {
681
- const videoPath = `/videos/${req.params.id}.mp4`;
682
- const ctrl = await s3.getStreamVideoFileCtrl({
683
- fileKey: videoPath,
684
- contentType: 'video/mp4',
685
- bufferMB: 5
686
- });
687
- ctrl(req, res, next);
688
- }
689
- );
690
-
691
- app.listen(3000, () => {
692
- console.log('Server running on port 3000');
693
- });
694
- ```
695
-
696
- ## 🚀 Performance Tips
697
-
698
- 1. **Use Pagination**: For large directories, always use paginated methods
699
- 2. **Stream Large Files**: Use streaming methods instead of loading entire files into memory
700
- 3. **Connection Pooling**: The built-in connection pooling is optimized for concurrent requests
701
- 4. **Batch Operations**: Use `Promise.all()` for parallel operations when possible
702
- 5. **Presigned URLs**: Generate presigned URLs for direct client uploads/downloads when appropriate
703
-
704
- ## 🛡️ Error Handling
705
-
706
- ```typescript
707
- try {
708
- await s3.uploadFile('/docs/file.pdf', buffer);
709
- } catch (error) {
710
- if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
711
- console.error('File not found');
712
- } else {
713
- console.error('Upload failed:', error);
714
- }
715
- }
716
- ```
717
533
 
718
534
  ## 📝 TypeScript Support
719
535
 
720
- This package is written in TypeScript and includes full type definitions:
721
-
722
- ```typescript
723
- import type {
724
- ContentFile,
725
- FileUploadResponse,
726
- TreeDirectoryItem,
727
- UploadedS3File,
728
- S3UploadOptions
729
- } from '@hdriel/aws-utils';
730
- ```
536
+ This package is written in TypeScript and includes full type definitions
731
537
 
732
538
  ## 👤 Author
733
539
 
package/dist/index.cjs CHANGED
@@ -271,7 +271,7 @@ var LambdaUtil = class {
271
271
  var import_client_s36 = require("@aws-sdk/client-s3");
272
272
 
273
273
  // src/aws/s3/s3-stream.ts
274
- var import_pathe = __toESM(require("pathe"), 1);
274
+ var import_pathe2 = __toESM(require("pathe"), 1);
275
275
  var import_stream = require("stream");
276
276
  var import_util = require("util");
277
277
  var import_buffer2 = require("buffer");
@@ -295,6 +295,7 @@ var s3Limiter = (0, import_p_limit.default)(4);
295
295
 
296
296
  // src/utils/helpers.ts
297
297
  var import_bytes = __toESM(require("bytes"), 1);
298
+ var import_ms = __toESM(require("ms"), 1);
298
299
  var parseRangeHeader = (range, contentLength, chunkSize) => {
299
300
  if (!range || !range.startsWith("bytes=")) return null;
300
301
  const rangeParts = range.replace("bytes=", "").split("-");
@@ -316,11 +317,16 @@ var getFileSize = (maxFileSize, defaultMaxFileSize) => {
316
317
  const fileSize = typeof fileSizeUnitValue === "number" ? fileSizeUnitValue : (0, import_bytes.default)(fileSizeUnitValue);
317
318
  return fileSize != null ? fileSize : void 0;
318
319
  };
320
+ var getTotalSeconds = (msValue) => {
321
+ const value = (0, import_ms.default)(msValue);
322
+ return value / 1e3;
323
+ };
319
324
 
320
325
  // src/aws/s3/s3-file.ts
321
326
  var import_buffer = require("buffer");
322
327
  var import_node_stream = require("stream");
323
- var import_ms = __toESM(require("ms"), 1);
328
+ var import_ms2 = __toESM(require("ms"), 1);
329
+ var import_pathe = require("pathe");
324
330
  var import_lib_storage = require("@aws-sdk/lib-storage");
325
331
  var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
326
332
  var import_client_s34 = require("@aws-sdk/client-s3");
@@ -954,7 +960,13 @@ var S3File = class extends S3Directory {
954
960
  const normalizedKey = getNormalizedPath(filePath);
955
961
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
956
962
  const command = new import_client_s34.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
957
- return yield this.execute(command);
963
+ const result = yield this.execute(command);
964
+ if (!result) return result;
965
+ return __spreadProps(__spreadValues({}, result), {
966
+ Name: (0, import_pathe.basename)(normalizedKey),
967
+ Key: normalizedKey,
968
+ Location: `${this.link}${normalizedKey == null ? void 0 : normalizedKey.replace(/^\//, "")}`
969
+ });
958
970
  });
959
971
  }
960
972
  fileListInfo(directoryPath, fileNamePrefix) {
@@ -1080,7 +1092,7 @@ var S3File = class extends S3Directory {
1080
1092
  var _a2;
1081
1093
  let normalizedKey = getNormalizedPath(filePath);
1082
1094
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1083
- const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : (0, import_ms.default)(expiresIn) / 1e3;
1095
+ const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : (0, import_ms2.default)(expiresIn) / 1e3;
1084
1096
  const command = new import_client_s34.GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1085
1097
  const url = yield (0, import_s3_request_presigner.getSignedUrl)(this.s3Client, command, {
1086
1098
  expiresIn: expiresInSeconds
@@ -1216,22 +1228,20 @@ var S3Stream = class _S3Stream extends S3File {
1216
1228
  __publicField(this, "getImageFileViewCtrl", ({
1217
1229
  fileKey: _fileKey,
1218
1230
  queryField = "file",
1219
- cachingAge = 31536e3
1231
+ paramField = "file",
1232
+ cachingAgeSeconds = "1y"
1220
1233
  } = {}) => {
1221
1234
  return (req, res, next) => __async(this, null, function* () {
1222
- var _a2, _b, _c, _d, _e;
1223
- let fileKey = _fileKey || (((_a2 = req.query) == null ? void 0 : _a2[queryField]) ? decodeURIComponent((_b = req.query) == null ? void 0 : _b[queryField]) : void 0);
1235
+ var _a2, _b, _c, _d, _e, _f;
1236
+ let fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramField]) ? decodeURIComponent((_b = req.params) == null ? void 0 : _b[paramField]) : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? decodeURIComponent((_d = req.query) == null ? void 0 : _d[queryField]) : void 0);
1224
1237
  if (!fileKey || fileKey === "/") {
1225
- (_d = this.logger) == null ? void 0 : _d.warn(req.id, "image file view required file query field", {
1226
- fileKey: (_c = req.query) == null ? void 0 : _c[queryField],
1227
- queryField
1228
- });
1229
- next("image file key is required");
1238
+ (_e = this.logger) == null ? void 0 : _e.warn(req.id, "image fileKey is required");
1239
+ next("image fileKey is required");
1230
1240
  return;
1231
1241
  }
1232
1242
  try {
1233
1243
  const imageBuffer = yield this.fileContent(fileKey, "buffer");
1234
- const ext = import_pathe.default.extname(fileKey).slice(1).toLowerCase();
1244
+ const ext = import_pathe2.default.extname(fileKey).slice(1).toLowerCase();
1235
1245
  const mimeTypeMap = {
1236
1246
  jpg: "image/jpeg",
1237
1247
  jpeg: "image/jpeg",
@@ -1243,14 +1253,14 @@ var S3Stream = class _S3Stream extends S3File {
1243
1253
  };
1244
1254
  const contentType = mimeTypeMap[ext] || "application/octet-stream";
1245
1255
  res.setHeader("Content-Type", contentType);
1246
- if (cachingAge) res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1247
1256
  res.setHeader("Content-Length", imageBuffer.length);
1257
+ const cachingAge = !cachingAgeSeconds || typeof cachingAgeSeconds === "number" ? cachingAgeSeconds : getTotalSeconds(cachingAgeSeconds);
1258
+ if (cachingAge) res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1248
1259
  res.status(200).send(imageBuffer);
1249
1260
  } catch (error) {
1250
- (_e = this.logger) == null ? void 0 : _e.warn(req.id, "image view fileKey not found", {
1251
- fileKey,
1252
- localstack: this.localstack
1253
- });
1261
+ (_f = this.logger) == null ? void 0 : _f.warn(req.id, "image fileKey not found", __spreadValues({
1262
+ fileKey
1263
+ }, this.localstack && { localstack: this.localstack }));
1254
1264
  next(`Failed to retrieve image file: ${error.message}`);
1255
1265
  }
1256
1266
  });
@@ -1259,18 +1269,20 @@ var S3Stream = class _S3Stream extends S3File {
1259
1269
  __publicField(this, "getPdfFileViewCtrl", ({
1260
1270
  fileKey: _fileKey,
1261
1271
  queryField = "file",
1262
- cachingAge = 31536e3
1272
+ paramField = "file",
1273
+ cachingAgeSeconds = "1y"
1263
1274
  } = {}) => {
1264
1275
  return (req, res, next) => __async(this, null, function* () {
1265
- var _a2, _b;
1266
- let fileKey = _fileKey || (((_a2 = req.query) == null ? void 0 : _a2[queryField]) ? decodeURIComponent((_b = req.query) == null ? void 0 : _b[queryField]) : void 0);
1276
+ var _a2, _b, _c, _d, _e, _f;
1277
+ let fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramField]) ? decodeURIComponent((_b = req.params) == null ? void 0 : _b[paramField]) : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? decodeURIComponent((_d = req.query) == null ? void 0 : _d[queryField]) : void 0);
1267
1278
  if (!fileKey) {
1268
- next("pdf file key is required");
1279
+ (_e = this.logger) == null ? void 0 : _e.warn(req.id, "pdf fileKey is required");
1280
+ next("pdf fileKey is required");
1269
1281
  return;
1270
1282
  }
1271
1283
  try {
1272
1284
  const fileBuffer = yield this.fileContent(fileKey, "buffer");
1273
- const ext = import_pathe.default.extname(fileKey).slice(1).toLowerCase();
1285
+ const ext = import_pathe2.default.extname(fileKey).slice(1).toLowerCase();
1274
1286
  const mimeTypeMap = {
1275
1287
  pdf: "application/pdf",
1276
1288
  txt: "text/plain",
@@ -1283,11 +1295,15 @@ var S3Stream = class _S3Stream extends S3File {
1283
1295
  };
1284
1296
  const contentType = mimeTypeMap[ext] || "application/octet-stream";
1285
1297
  res.setHeader("Content-Type", contentType);
1286
- res.setHeader("Content-Disposition", `inline; filename="${import_pathe.default.basename(fileKey)}"`);
1287
- res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1298
+ res.setHeader("Content-Disposition", `inline; filename="${import_pathe2.default.basename(fileKey)}"`);
1288
1299
  res.setHeader("Content-Length", fileBuffer.length);
1300
+ const cachingAge = !cachingAgeSeconds || typeof cachingAgeSeconds === "number" ? cachingAgeSeconds : getTotalSeconds(cachingAgeSeconds);
1301
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1289
1302
  res.status(200).send(fileBuffer);
1290
1303
  } catch (error) {
1304
+ (_f = this.logger) == null ? void 0 : _f.warn(req.id, "pdf fileKey not found", __spreadValues({
1305
+ fileKey
1306
+ }, this.localstack && { localstack: this.localstack }));
1291
1307
  next(`Failed to retrieve pdf file: ${error.message}`);
1292
1308
  }
1293
1309
  });
@@ -1295,12 +1311,12 @@ var S3Stream = class _S3Stream extends S3File {
1295
1311
  this.maxUploadFileSizeRestriction = maxUploadFileSizeRestriction;
1296
1312
  }
1297
1313
  streamObjectFile(_0) {
1298
- return __async(this, arguments, function* (filePath, {
1314
+ return __async(this, arguments, function* (fileKey, {
1299
1315
  Range,
1300
1316
  checkFileExists = true,
1301
1317
  abortSignal
1302
1318
  } = {}) {
1303
- let normalizedKey = getNormalizedPath(filePath);
1319
+ let normalizedKey = getNormalizedPath(fileKey);
1304
1320
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1305
1321
  if (checkFileExists) {
1306
1322
  const isExists = yield this.fileExists(normalizedKey);
@@ -1319,13 +1335,12 @@ var S3Stream = class _S3Stream extends S3File {
1319
1335
  }
1320
1336
  // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1321
1337
  streamVideoFile(_0) {
1322
- return __async(this, arguments, function* ({
1323
- filePath,
1338
+ return __async(this, arguments, function* (fileKey, {
1324
1339
  Range,
1325
1340
  abortSignal
1326
- }) {
1341
+ } = {}) {
1327
1342
  var _a2;
1328
- let normalizedKey = getNormalizedPath(filePath);
1343
+ let normalizedKey = getNormalizedPath(fileKey);
1329
1344
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1330
1345
  try {
1331
1346
  const cmd = new import_client_s35.GetObjectCommand(__spreadValues({
@@ -1349,7 +1364,7 @@ var S3Stream = class _S3Stream extends S3File {
1349
1364
  } catch (error) {
1350
1365
  (_a2 = this.logger) == null ? void 0 : _a2.warn(this.reqId, "getS3VideoStream error", {
1351
1366
  Bucket: this.bucket,
1352
- filePath: normalizedKey,
1367
+ fileKey: normalizedKey,
1353
1368
  Range,
1354
1369
  error
1355
1370
  });
@@ -1407,8 +1422,7 @@ var S3Stream = class _S3Stream extends S3File {
1407
1422
  const onClose = () => abort.abort();
1408
1423
  req.once("close", onClose);
1409
1424
  try {
1410
- const result = yield this.streamVideoFile({
1411
- filePath: normalizedKey,
1425
+ const result = yield this.streamVideoFile(normalizedKey, {
1412
1426
  Range,
1413
1427
  abortSignal: abort.signal
1414
1428
  });
@@ -1473,11 +1487,10 @@ var S3Stream = class _S3Stream extends S3File {
1473
1487
  }
1474
1488
  // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1475
1489
  getStreamFileCtrl(_0) {
1476
- return __async(this, arguments, function* ({
1477
- filePath,
1490
+ return __async(this, arguments, function* (fileKey, {
1478
1491
  filename,
1479
1492
  forDownloading = false
1480
- }) {
1493
+ } = {}) {
1481
1494
  return (req, res, next) => __async(this, null, function* () {
1482
1495
  var _a2, _b;
1483
1496
  const abort = new AbortController();
@@ -1488,7 +1501,7 @@ var S3Stream = class _S3Stream extends S3File {
1488
1501
  (_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
1489
1502
  };
1490
1503
  req.once("close", onClose);
1491
- let normalizedKey = getNormalizedPath(filePath);
1504
+ let normalizedKey = getNormalizedPath(fileKey);
1492
1505
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1493
1506
  try {
1494
1507
  const isExists = yield this.fileExists(normalizedKey);
@@ -1517,7 +1530,7 @@ var S3Stream = class _S3Stream extends S3File {
1517
1530
  }
1518
1531
  stream.on("error", (err) => {
1519
1532
  var _a3, _b2;
1520
- (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "Stream error", { filePath: normalizedKey, error: err });
1533
+ (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "Stream error", { fileKey: normalizedKey, error: err });
1521
1534
  abort.abort();
1522
1535
  (_b2 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _b2.call(stream);
1523
1536
  });
@@ -1537,7 +1550,7 @@ var S3Stream = class _S3Stream extends S3File {
1537
1550
  if (isBenignStreamError) {
1538
1551
  return;
1539
1552
  }
1540
- (_b = this.logger) == null ? void 0 : _b.error(this.reqId, "Failed to stream file", { filePath: normalizedKey, error });
1553
+ (_b = this.logger) == null ? void 0 : _b.error(this.reqId, "Failed to stream file", { fileKey: normalizedKey, error });
1541
1554
  if (!res.headersSent) {
1542
1555
  next(error);
1543
1556
  } else if (!res.writableEnded) {
@@ -1554,33 +1567,30 @@ var S3Stream = class _S3Stream extends S3File {
1554
1567
  }
1555
1568
  // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1556
1569
  getStreamZipFileCtr(_0) {
1557
- return __async(this, arguments, function* ({
1558
- filePath,
1570
+ return __async(this, arguments, function* (fileKey, {
1559
1571
  filename: _filename,
1560
1572
  compressionLevel = 5
1561
- }) {
1573
+ } = {}) {
1562
1574
  return (req, res, next) => __async(this, null, function* () {
1563
1575
  var _a2, _b, _c, _d, _e;
1564
- const filePaths = [].concat(filePath).map((filePath2) => getNormalizedPath(filePath2)).filter((v) => v && v !== "/");
1565
- if (!filePaths.length) {
1566
- throw new Error("No file keys provided");
1567
- }
1568
- let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1569
- filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1570
1576
  const abort = new AbortController();
1571
- const onClose = () => {
1572
- abort.abort();
1573
- };
1574
- req.once("close", onClose);
1577
+ const onClose = () => abort.abort();
1575
1578
  try {
1576
- (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: filePaths.length });
1577
- const downloadPromises = filePaths.map((filePath2) => __async(this, null, function* () {
1579
+ const fileKeys = [].concat(fileKey).map((fileKey2) => getNormalizedPath(fileKey2)).filter((v) => v && v !== "/");
1580
+ if (!fileKeys.length) {
1581
+ throw new Error("No file keys provided");
1582
+ }
1583
+ let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1584
+ filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1585
+ req.once("close", onClose);
1586
+ (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: fileKeys.length });
1587
+ const downloadPromises = fileKeys.map((fileKey2) => __async(this, null, function* () {
1578
1588
  var _a3, _b2, _c2;
1579
1589
  try {
1580
1590
  if (abort.signal.aborted) return null;
1581
- const stream = yield this.streamObjectFile(filePath2, { abortSignal: abort.signal });
1591
+ const stream = yield this.streamObjectFile(fileKey2, { abortSignal: abort.signal });
1582
1592
  if (!stream) {
1583
- (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { filePath: filePath2 });
1593
+ (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { fileKey: fileKey2 });
1584
1594
  return null;
1585
1595
  }
1586
1596
  const chunks = [];
@@ -1604,14 +1614,14 @@ var S3Stream = class _S3Stream extends S3File {
1604
1614
  }
1605
1615
  }
1606
1616
  const buffer = import_buffer2.Buffer.concat(chunks);
1607
- const fileName = filePath2.split("/").pop() || filePath2;
1617
+ const fileName = fileKey2.split("/").pop() || fileKey2;
1608
1618
  (_b2 = this.logger) == null ? void 0 : _b2.debug(this.reqId, "File downloaded", {
1609
- filePath: filePath2,
1619
+ fileKey: fileKey2,
1610
1620
  sizeMB: (buffer.length / (1024 * 1024)).toFixed(2)
1611
1621
  });
1612
- return { buffer, name: fileName, path: filePath2 };
1622
+ return { buffer, name: fileName, path: fileKey2 };
1613
1623
  } catch (error2) {
1614
- (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { filePath: filePath2, error: error2 });
1624
+ (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { fileKey: fileKey2, error: error2 });
1615
1625
  return null;
1616
1626
  }
1617
1627
  }));
@@ -1693,7 +1703,7 @@ var S3Stream = class _S3Stream extends S3File {
1693
1703
  static fileFilter(types, fileExt) {
1694
1704
  const fileTypesChecker = (fileExt == null ? void 0 : fileExt.length) ? new RegExp(`\\.(${fileExt.join("|")})$`, "i") : void 0;
1695
1705
  return function(_req, file, cb) {
1696
- const fileExtension = import_pathe.default.extname(file.originalname).substring(1);
1706
+ const fileExtension = import_pathe2.default.extname(file.originalname).substring(1);
1697
1707
  const extname = fileTypesChecker ? fileTypesChecker.test(`.${fileExtension}`) : true;
1698
1708
  const mimeType = (types == null ? void 0 : types.length) ? types.some((type) => file.mimetype.startsWith(`${type}/`)) : true;
1699
1709
  if (mimeType && extname) {
package/dist/index.d.cts CHANGED
@@ -242,7 +242,11 @@ declare class S3Directory extends S3Bucket {
242
242
  type S3FileProps = S3DirectoryProps;
243
243
  declare class S3File extends S3Directory {
244
244
  constructor(props: S3FileProps);
245
- fileInfo(filePath: string): Promise<HeadObjectCommandOutput>;
245
+ fileInfo(filePath: string): Promise<HeadObjectCommandOutput & {
246
+ Name: string;
247
+ Location: string;
248
+ Key: string;
249
+ }>;
246
250
  fileListInfo(directoryPath?: string, fileNamePrefix?: string): Promise<(ContentFile & {
247
251
  Location: string;
248
252
  })[]>;
@@ -273,13 +277,12 @@ type S3StreamProps = S3FileProps & {
273
277
  declare class S3Stream extends S3File {
274
278
  private readonly maxUploadFileSizeRestriction;
275
279
  constructor({ maxUploadFileSizeRestriction, ...props }: S3StreamProps);
276
- protected streamObjectFile(filePath: string, { Range, checkFileExists, abortSignal, }?: {
280
+ protected streamObjectFile(fileKey: string, { Range, checkFileExists, abortSignal, }?: {
277
281
  Range?: string;
278
282
  checkFileExists?: boolean;
279
283
  abortSignal?: AbortSignal;
280
284
  }): Promise<Readable | null>;
281
- protected streamVideoFile({ filePath, Range, abortSignal, }: {
282
- filePath: string;
285
+ protected streamVideoFile(fileKey: string, { Range, abortSignal, }?: {
283
286
  Range?: string;
284
287
  abortSignal?: AbortSignal;
285
288
  }): Promise<{
@@ -300,23 +303,23 @@ declare class S3Stream extends S3File {
300
303
  bufferMB?: number | undefined;
301
304
  streamTimeoutMS?: number | undefined;
302
305
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<any>>;
303
- getImageFileViewCtrl: ({ fileKey: _fileKey, queryField, cachingAge, }?: {
306
+ getImageFileViewCtrl: ({ fileKey: _fileKey, queryField, paramField, cachingAgeSeconds, }?: {
304
307
  fileKey?: string;
305
308
  queryField?: string;
306
- cachingAge?: number;
309
+ paramField?: string;
310
+ cachingAgeSeconds?: null | number | StringValue;
307
311
  }) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
308
- getPdfFileViewCtrl: ({ fileKey: _fileKey, queryField, cachingAge, }?: {
312
+ getPdfFileViewCtrl: ({ fileKey: _fileKey, queryField, paramField, cachingAgeSeconds, }?: {
309
313
  fileKey?: string;
310
314
  queryField?: string;
311
- cachingAge?: number;
315
+ paramField?: string;
316
+ cachingAgeSeconds?: null | number | StringValue;
312
317
  }) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
313
- getStreamFileCtrl({ filePath, filename, forDownloading, }: {
314
- filePath: string;
318
+ getStreamFileCtrl(fileKey: string, { filename, forDownloading, }?: {
315
319
  filename?: string;
316
320
  forDownloading?: boolean;
317
321
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>>;
318
- getStreamZipFileCtr({ filePath, filename: _filename, compressionLevel, }: {
319
- filePath: string | string[];
322
+ getStreamZipFileCtr(fileKey: string | string[], { filename: _filename, compressionLevel, }?: {
320
323
  filename?: string;
321
324
  compressionLevel?: number;
322
325
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>>;
package/dist/index.d.ts CHANGED
@@ -242,7 +242,11 @@ declare class S3Directory extends S3Bucket {
242
242
  type S3FileProps = S3DirectoryProps;
243
243
  declare class S3File extends S3Directory {
244
244
  constructor(props: S3FileProps);
245
- fileInfo(filePath: string): Promise<HeadObjectCommandOutput>;
245
+ fileInfo(filePath: string): Promise<HeadObjectCommandOutput & {
246
+ Name: string;
247
+ Location: string;
248
+ Key: string;
249
+ }>;
246
250
  fileListInfo(directoryPath?: string, fileNamePrefix?: string): Promise<(ContentFile & {
247
251
  Location: string;
248
252
  })[]>;
@@ -273,13 +277,12 @@ type S3StreamProps = S3FileProps & {
273
277
  declare class S3Stream extends S3File {
274
278
  private readonly maxUploadFileSizeRestriction;
275
279
  constructor({ maxUploadFileSizeRestriction, ...props }: S3StreamProps);
276
- protected streamObjectFile(filePath: string, { Range, checkFileExists, abortSignal, }?: {
280
+ protected streamObjectFile(fileKey: string, { Range, checkFileExists, abortSignal, }?: {
277
281
  Range?: string;
278
282
  checkFileExists?: boolean;
279
283
  abortSignal?: AbortSignal;
280
284
  }): Promise<Readable | null>;
281
- protected streamVideoFile({ filePath, Range, abortSignal, }: {
282
- filePath: string;
285
+ protected streamVideoFile(fileKey: string, { Range, abortSignal, }?: {
283
286
  Range?: string;
284
287
  abortSignal?: AbortSignal;
285
288
  }): Promise<{
@@ -300,23 +303,23 @@ declare class S3Stream extends S3File {
300
303
  bufferMB?: number | undefined;
301
304
  streamTimeoutMS?: number | undefined;
302
305
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<any>>;
303
- getImageFileViewCtrl: ({ fileKey: _fileKey, queryField, cachingAge, }?: {
306
+ getImageFileViewCtrl: ({ fileKey: _fileKey, queryField, paramField, cachingAgeSeconds, }?: {
304
307
  fileKey?: string;
305
308
  queryField?: string;
306
- cachingAge?: number;
309
+ paramField?: string;
310
+ cachingAgeSeconds?: null | number | StringValue;
307
311
  }) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
308
- getPdfFileViewCtrl: ({ fileKey: _fileKey, queryField, cachingAge, }?: {
312
+ getPdfFileViewCtrl: ({ fileKey: _fileKey, queryField, paramField, cachingAgeSeconds, }?: {
309
313
  fileKey?: string;
310
314
  queryField?: string;
311
- cachingAge?: number;
315
+ paramField?: string;
316
+ cachingAgeSeconds?: null | number | StringValue;
312
317
  }) => (req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>;
313
- getStreamFileCtrl({ filePath, filename, forDownloading, }: {
314
- filePath: string;
318
+ getStreamFileCtrl(fileKey: string, { filename, forDownloading, }?: {
315
319
  filename?: string;
316
320
  forDownloading?: boolean;
317
321
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>>;
318
- getStreamZipFileCtr({ filePath, filename: _filename, compressionLevel, }: {
319
- filePath: string | string[];
322
+ getStreamZipFileCtr(fileKey: string | string[], { filename: _filename, compressionLevel, }?: {
320
323
  filename?: string;
321
324
  compressionLevel?: number;
322
325
  }): Promise<(req: Request$1 & any, res: Response & any, next: NextFunction & any) => Promise<void>>;
package/dist/index.js CHANGED
@@ -256,6 +256,7 @@ var s3Limiter = pLimit(4);
256
256
 
257
257
  // src/utils/helpers.ts
258
258
  import bytes from "bytes";
259
+ import ms from "ms";
259
260
  var parseRangeHeader = (range, contentLength, chunkSize) => {
260
261
  if (!range || !range.startsWith("bytes=")) return null;
261
262
  const rangeParts = range.replace("bytes=", "").split("-");
@@ -277,11 +278,16 @@ var getFileSize = (maxFileSize, defaultMaxFileSize) => {
277
278
  const fileSize = typeof fileSizeUnitValue === "number" ? fileSizeUnitValue : bytes(fileSizeUnitValue);
278
279
  return fileSize != null ? fileSize : void 0;
279
280
  };
281
+ var getTotalSeconds = (msValue) => {
282
+ const value = ms(msValue);
283
+ return value / 1e3;
284
+ };
280
285
 
281
286
  // src/aws/s3/s3-file.ts
282
287
  import { Buffer as Buffer2 } from "buffer";
283
288
  import "stream";
284
- import ms from "ms";
289
+ import ms2 from "ms";
290
+ import { basename } from "pathe";
285
291
  import { Upload } from "@aws-sdk/lib-storage";
286
292
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
287
293
  import {
@@ -943,7 +949,13 @@ var S3File = class extends S3Directory {
943
949
  const normalizedKey = getNormalizedPath(filePath);
944
950
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
945
951
  const command = new HeadObjectCommand2({ Bucket: this.bucket, Key: normalizedKey });
946
- return yield this.execute(command);
952
+ const result = yield this.execute(command);
953
+ if (!result) return result;
954
+ return __spreadProps(__spreadValues({}, result), {
955
+ Name: basename(normalizedKey),
956
+ Key: normalizedKey,
957
+ Location: `${this.link}${normalizedKey == null ? void 0 : normalizedKey.replace(/^\//, "")}`
958
+ });
947
959
  });
948
960
  }
949
961
  fileListInfo(directoryPath, fileNamePrefix) {
@@ -1069,7 +1081,7 @@ var S3File = class extends S3Directory {
1069
1081
  var _a2;
1070
1082
  let normalizedKey = getNormalizedPath(filePath);
1071
1083
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1072
- const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : ms(expiresIn) / 1e3;
1084
+ const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : ms2(expiresIn) / 1e3;
1073
1085
  const command = new GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1074
1086
  const url = yield getSignedUrl(this.s3Client, command, {
1075
1087
  expiresIn: expiresInSeconds
@@ -1205,17 +1217,15 @@ var S3Stream = class _S3Stream extends S3File {
1205
1217
  __publicField(this, "getImageFileViewCtrl", ({
1206
1218
  fileKey: _fileKey,
1207
1219
  queryField = "file",
1208
- cachingAge = 31536e3
1220
+ paramField = "file",
1221
+ cachingAgeSeconds = "1y"
1209
1222
  } = {}) => {
1210
1223
  return (req, res, next) => __async(this, null, function* () {
1211
- var _a2, _b, _c, _d, _e;
1212
- let fileKey = _fileKey || (((_a2 = req.query) == null ? void 0 : _a2[queryField]) ? decodeURIComponent((_b = req.query) == null ? void 0 : _b[queryField]) : void 0);
1224
+ var _a2, _b, _c, _d, _e, _f;
1225
+ let fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramField]) ? decodeURIComponent((_b = req.params) == null ? void 0 : _b[paramField]) : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? decodeURIComponent((_d = req.query) == null ? void 0 : _d[queryField]) : void 0);
1213
1226
  if (!fileKey || fileKey === "/") {
1214
- (_d = this.logger) == null ? void 0 : _d.warn(req.id, "image file view required file query field", {
1215
- fileKey: (_c = req.query) == null ? void 0 : _c[queryField],
1216
- queryField
1217
- });
1218
- next("image file key is required");
1227
+ (_e = this.logger) == null ? void 0 : _e.warn(req.id, "image fileKey is required");
1228
+ next("image fileKey is required");
1219
1229
  return;
1220
1230
  }
1221
1231
  try {
@@ -1232,14 +1242,14 @@ var S3Stream = class _S3Stream extends S3File {
1232
1242
  };
1233
1243
  const contentType = mimeTypeMap[ext] || "application/octet-stream";
1234
1244
  res.setHeader("Content-Type", contentType);
1235
- if (cachingAge) res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1236
1245
  res.setHeader("Content-Length", imageBuffer.length);
1246
+ const cachingAge = !cachingAgeSeconds || typeof cachingAgeSeconds === "number" ? cachingAgeSeconds : getTotalSeconds(cachingAgeSeconds);
1247
+ if (cachingAge) res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1237
1248
  res.status(200).send(imageBuffer);
1238
1249
  } catch (error) {
1239
- (_e = this.logger) == null ? void 0 : _e.warn(req.id, "image view fileKey not found", {
1240
- fileKey,
1241
- localstack: this.localstack
1242
- });
1250
+ (_f = this.logger) == null ? void 0 : _f.warn(req.id, "image fileKey not found", __spreadValues({
1251
+ fileKey
1252
+ }, this.localstack && { localstack: this.localstack }));
1243
1253
  next(`Failed to retrieve image file: ${error.message}`);
1244
1254
  }
1245
1255
  });
@@ -1248,13 +1258,15 @@ var S3Stream = class _S3Stream extends S3File {
1248
1258
  __publicField(this, "getPdfFileViewCtrl", ({
1249
1259
  fileKey: _fileKey,
1250
1260
  queryField = "file",
1251
- cachingAge = 31536e3
1261
+ paramField = "file",
1262
+ cachingAgeSeconds = "1y"
1252
1263
  } = {}) => {
1253
1264
  return (req, res, next) => __async(this, null, function* () {
1254
- var _a2, _b;
1255
- let fileKey = _fileKey || (((_a2 = req.query) == null ? void 0 : _a2[queryField]) ? decodeURIComponent((_b = req.query) == null ? void 0 : _b[queryField]) : void 0);
1265
+ var _a2, _b, _c, _d, _e, _f;
1266
+ let fileKey = _fileKey || (((_a2 = req.params) == null ? void 0 : _a2[paramField]) ? decodeURIComponent((_b = req.params) == null ? void 0 : _b[paramField]) : void 0) || (((_c = req.query) == null ? void 0 : _c[queryField]) ? decodeURIComponent((_d = req.query) == null ? void 0 : _d[queryField]) : void 0);
1256
1267
  if (!fileKey) {
1257
- next("pdf file key is required");
1268
+ (_e = this.logger) == null ? void 0 : _e.warn(req.id, "pdf fileKey is required");
1269
+ next("pdf fileKey is required");
1258
1270
  return;
1259
1271
  }
1260
1272
  try {
@@ -1273,10 +1285,14 @@ var S3Stream = class _S3Stream extends S3File {
1273
1285
  const contentType = mimeTypeMap[ext] || "application/octet-stream";
1274
1286
  res.setHeader("Content-Type", contentType);
1275
1287
  res.setHeader("Content-Disposition", `inline; filename="${path.basename(fileKey)}"`);
1276
- res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1277
1288
  res.setHeader("Content-Length", fileBuffer.length);
1289
+ const cachingAge = !cachingAgeSeconds || typeof cachingAgeSeconds === "number" ? cachingAgeSeconds : getTotalSeconds(cachingAgeSeconds);
1290
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1278
1291
  res.status(200).send(fileBuffer);
1279
1292
  } catch (error) {
1293
+ (_f = this.logger) == null ? void 0 : _f.warn(req.id, "pdf fileKey not found", __spreadValues({
1294
+ fileKey
1295
+ }, this.localstack && { localstack: this.localstack }));
1280
1296
  next(`Failed to retrieve pdf file: ${error.message}`);
1281
1297
  }
1282
1298
  });
@@ -1284,12 +1300,12 @@ var S3Stream = class _S3Stream extends S3File {
1284
1300
  this.maxUploadFileSizeRestriction = maxUploadFileSizeRestriction;
1285
1301
  }
1286
1302
  streamObjectFile(_0) {
1287
- return __async(this, arguments, function* (filePath, {
1303
+ return __async(this, arguments, function* (fileKey, {
1288
1304
  Range,
1289
1305
  checkFileExists = true,
1290
1306
  abortSignal
1291
1307
  } = {}) {
1292
- let normalizedKey = getNormalizedPath(filePath);
1308
+ let normalizedKey = getNormalizedPath(fileKey);
1293
1309
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1294
1310
  if (checkFileExists) {
1295
1311
  const isExists = yield this.fileExists(normalizedKey);
@@ -1308,13 +1324,12 @@ var S3Stream = class _S3Stream extends S3File {
1308
1324
  }
1309
1325
  // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1310
1326
  streamVideoFile(_0) {
1311
- return __async(this, arguments, function* ({
1312
- filePath,
1327
+ return __async(this, arguments, function* (fileKey, {
1313
1328
  Range,
1314
1329
  abortSignal
1315
- }) {
1330
+ } = {}) {
1316
1331
  var _a2;
1317
- let normalizedKey = getNormalizedPath(filePath);
1332
+ let normalizedKey = getNormalizedPath(fileKey);
1318
1333
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1319
1334
  try {
1320
1335
  const cmd = new GetObjectCommand2(__spreadValues({
@@ -1338,7 +1353,7 @@ var S3Stream = class _S3Stream extends S3File {
1338
1353
  } catch (error) {
1339
1354
  (_a2 = this.logger) == null ? void 0 : _a2.warn(this.reqId, "getS3VideoStream error", {
1340
1355
  Bucket: this.bucket,
1341
- filePath: normalizedKey,
1356
+ fileKey: normalizedKey,
1342
1357
  Range,
1343
1358
  error
1344
1359
  });
@@ -1396,8 +1411,7 @@ var S3Stream = class _S3Stream extends S3File {
1396
1411
  const onClose = () => abort.abort();
1397
1412
  req.once("close", onClose);
1398
1413
  try {
1399
- const result = yield this.streamVideoFile({
1400
- filePath: normalizedKey,
1414
+ const result = yield this.streamVideoFile(normalizedKey, {
1401
1415
  Range,
1402
1416
  abortSignal: abort.signal
1403
1417
  });
@@ -1462,11 +1476,10 @@ var S3Stream = class _S3Stream extends S3File {
1462
1476
  }
1463
1477
  // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1464
1478
  getStreamFileCtrl(_0) {
1465
- return __async(this, arguments, function* ({
1466
- filePath,
1479
+ return __async(this, arguments, function* (fileKey, {
1467
1480
  filename,
1468
1481
  forDownloading = false
1469
- }) {
1482
+ } = {}) {
1470
1483
  return (req, res, next) => __async(this, null, function* () {
1471
1484
  var _a2, _b;
1472
1485
  const abort = new AbortController();
@@ -1477,7 +1490,7 @@ var S3Stream = class _S3Stream extends S3File {
1477
1490
  (_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
1478
1491
  };
1479
1492
  req.once("close", onClose);
1480
- let normalizedKey = getNormalizedPath(filePath);
1493
+ let normalizedKey = getNormalizedPath(fileKey);
1481
1494
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1482
1495
  try {
1483
1496
  const isExists = yield this.fileExists(normalizedKey);
@@ -1506,7 +1519,7 @@ var S3Stream = class _S3Stream extends S3File {
1506
1519
  }
1507
1520
  stream.on("error", (err) => {
1508
1521
  var _a3, _b2;
1509
- (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "Stream error", { filePath: normalizedKey, error: err });
1522
+ (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "Stream error", { fileKey: normalizedKey, error: err });
1510
1523
  abort.abort();
1511
1524
  (_b2 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _b2.call(stream);
1512
1525
  });
@@ -1526,7 +1539,7 @@ var S3Stream = class _S3Stream extends S3File {
1526
1539
  if (isBenignStreamError) {
1527
1540
  return;
1528
1541
  }
1529
- (_b = this.logger) == null ? void 0 : _b.error(this.reqId, "Failed to stream file", { filePath: normalizedKey, error });
1542
+ (_b = this.logger) == null ? void 0 : _b.error(this.reqId, "Failed to stream file", { fileKey: normalizedKey, error });
1530
1543
  if (!res.headersSent) {
1531
1544
  next(error);
1532
1545
  } else if (!res.writableEnded) {
@@ -1543,33 +1556,30 @@ var S3Stream = class _S3Stream extends S3File {
1543
1556
  }
1544
1557
  // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1545
1558
  getStreamZipFileCtr(_0) {
1546
- return __async(this, arguments, function* ({
1547
- filePath,
1559
+ return __async(this, arguments, function* (fileKey, {
1548
1560
  filename: _filename,
1549
1561
  compressionLevel = 5
1550
- }) {
1562
+ } = {}) {
1551
1563
  return (req, res, next) => __async(this, null, function* () {
1552
1564
  var _a2, _b, _c, _d, _e;
1553
- const filePaths = [].concat(filePath).map((filePath2) => getNormalizedPath(filePath2)).filter((v) => v && v !== "/");
1554
- if (!filePaths.length) {
1555
- throw new Error("No file keys provided");
1556
- }
1557
- let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1558
- filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1559
1565
  const abort = new AbortController();
1560
- const onClose = () => {
1561
- abort.abort();
1562
- };
1563
- req.once("close", onClose);
1566
+ const onClose = () => abort.abort();
1564
1567
  try {
1565
- (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: filePaths.length });
1566
- const downloadPromises = filePaths.map((filePath2) => __async(this, null, function* () {
1568
+ const fileKeys = [].concat(fileKey).map((fileKey2) => getNormalizedPath(fileKey2)).filter((v) => v && v !== "/");
1569
+ if (!fileKeys.length) {
1570
+ throw new Error("No file keys provided");
1571
+ }
1572
+ let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1573
+ filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1574
+ req.once("close", onClose);
1575
+ (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: fileKeys.length });
1576
+ const downloadPromises = fileKeys.map((fileKey2) => __async(this, null, function* () {
1567
1577
  var _a3, _b2, _c2;
1568
1578
  try {
1569
1579
  if (abort.signal.aborted) return null;
1570
- const stream = yield this.streamObjectFile(filePath2, { abortSignal: abort.signal });
1580
+ const stream = yield this.streamObjectFile(fileKey2, { abortSignal: abort.signal });
1571
1581
  if (!stream) {
1572
- (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { filePath: filePath2 });
1582
+ (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { fileKey: fileKey2 });
1573
1583
  return null;
1574
1584
  }
1575
1585
  const chunks = [];
@@ -1593,14 +1603,14 @@ var S3Stream = class _S3Stream extends S3File {
1593
1603
  }
1594
1604
  }
1595
1605
  const buffer = Buffer3.concat(chunks);
1596
- const fileName = filePath2.split("/").pop() || filePath2;
1606
+ const fileName = fileKey2.split("/").pop() || fileKey2;
1597
1607
  (_b2 = this.logger) == null ? void 0 : _b2.debug(this.reqId, "File downloaded", {
1598
- filePath: filePath2,
1608
+ fileKey: fileKey2,
1599
1609
  sizeMB: (buffer.length / (1024 * 1024)).toFixed(2)
1600
1610
  });
1601
- return { buffer, name: fileName, path: filePath2 };
1611
+ return { buffer, name: fileName, path: fileKey2 };
1602
1612
  } catch (error2) {
1603
- (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { filePath: filePath2, error: error2 });
1613
+ (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { fileKey: fileKey2, error: error2 });
1604
1614
  return null;
1605
1615
  }
1606
1616
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hdriel/aws-utils",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Simplified AWS SDK (v3) utilities for S3 (upload, download, streaming) with TypeScript support",
5
5
  "author": "Hadriel Benjo (https://github.com/hdriel)",
6
6
  "type": "module",