@hdriel/aws-utils 1.1.4 → 1.1.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.
package/README.md CHANGED
@@ -114,27 +114,19 @@ please see this project code before using: [aws-utils-demo github link!](https:/
114
114
 
115
115
  ## Core Features
116
116
 
117
- ### 🪣 Bucket Operations
117
+ ### C.R.U.D Bucket Operations
118
118
 
119
- #### Initialize Bucket
120
119
  ```typescript
121
- // Create private bucket
122
- await s3.initBucket('private');
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} );
123
125
 
124
- // Create public bucket
125
- await s3.initBucket('public-read');
126
-
127
- // With location constraint
128
- await s3.initBucket('private', {
129
- includeConstraintLocation: true
130
- });
131
- ```
132
-
133
- #### Bucket Information
134
- ```typescript
126
+ // READ
127
+ const exists = await s3.isBucketExists(); // check for existance bucket
135
128
  const info = await s3.bucketInfo();
136
- console.log(info);
137
- // {
129
+ // info = {
138
130
  // name: 'my-bucket',
139
131
  // region: 'us-east-1',
140
132
  // exists: true,
@@ -144,44 +136,47 @@ console.log(info);
144
136
  // publicAccessBlock: { ... },
145
137
  // policy: { ... }
146
138
  // }
147
- ```
148
139
 
149
- #### Check Bucket Exists
150
- ```typescript
151
- const exists = await s3.isBucketExists();
152
- ```
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 });
153
157
 
154
- #### Delete Bucket
155
- ```typescript
156
- // Delete bucket (must be empty)
157
- await s3.destroyBucket();
158
+ // UPDATE
159
+ s3.changeBucket('another-bucket'); // Switch to different bucket
158
160
 
159
- // Force delete with all contents
160
- 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
161
164
  ```
162
165
 
163
- #### List All Buckets
164
- ```typescript
165
- const buckets = await s3.getBucketList();
166
+ ### 📁 C.R.U.D Directory Operations
166
167
 
167
- // Include public access configuration
168
- const bucketsWithAccess = await s3.getBucketList({
169
- includePublicAccess: true
170
- });
171
- ```
172
-
173
- ### 📁 Directory Operations
168
+ * auto decodeURIComponent for all directory input params
169
+ * handle directory issue (no matter if prefix/postfix slashes)
174
170
 
175
171
  #### Create Directory
176
172
  ```typescript
173
+ // CREATE
177
174
  await s3.createDirectory('/uploads/images');
178
- ```
179
175
 
180
- #### List Directory Contents
181
- ```typescript
176
+ // READ
177
+ const exists = await s3.directoryExists('/uploads/images'); // check for existance directory
182
178
  const { directories, files } = await s3.directoryList('/uploads');
183
-
184
- console.log('Subdirectories:', directories);
179
+ console.log('Subdirectories:', directories); // string[] directories like: ['images', 'test']
185
180
  console.log('Files:', files);
186
181
  // files: [
187
182
  // {
@@ -192,36 +187,24 @@ console.log('Files:', files);
192
187
  // Location: 'https://...'
193
188
  // }
194
189
  // ]
195
- ```
196
190
 
197
- #### Paginated Directory Listing
198
- ```typescript
199
191
  // Get second page with 50 items per page
200
- const result = await s3.directoryListPaginated('/uploads', {
201
- pageSize: 50,
202
- 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, ...)
203
195
  });
204
196
 
205
- console.log(result.directories); // Array of directory names
206
- console.log(result.files); // Array of file objects
207
- console.log(result.totalFetched); // Number of items returned
208
- ```
197
+ // DELETE
198
+ await s3.deleteDirectory('/uploads/temp'); // Delete directory and all contents
209
199
 
210
- #### Delete Directory
211
- ```typescript
212
- // Delete directory and all contents
213
- await s3.deleteDirectory('/uploads/temp');
214
200
  ```
215
201
 
216
- #### Check Directory Exists
217
- ```typescript
218
- const exists = await s3.directoryExists('/uploads/images');
219
- ```
220
202
 
221
- ### 📄 File Operations
203
+ ### 📄 C.R.U.D File Operations
222
204
 
223
- #### Upload File
224
205
  ```typescript
206
+ // CREATE
207
+ // > Upload File
225
208
  import { ACLs } from '@hdriel/aws-utils';
226
209
 
227
210
  // Upload buffer
@@ -232,79 +215,45 @@ await s3.uploadFile('/public/image.jpg', buffer, ACLs.public_read);
232
215
 
233
216
  // Upload with version tag
234
217
  await s3.uploadFile('/docs/v2.pdf', buffer, ACLs.private, '2.0.0');
235
- ```
236
-
237
- #### Check File Exists
238
- ```typescript
239
- const exists = await s3.fileExists('/documents/file.pdf');
240
- ```
241
-
242
- #### Get File Content
243
- ```typescript
244
- // As buffer
245
- const buffer = await s3.fileContent('/documents/file.pdf');
246
218
 
247
- // As base64 string
248
- 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
249
223
 
250
- // As UTF-8 string
251
- const text = await s3.fileContent('/data.json', 'utf8');
252
- ```
253
-
254
- #### File Information
255
- ```typescript
224
+ // READ
225
+ const exists = await s3.fileExists('/documents/file.pdf'); // check for existance file
256
226
  const info = await s3.fileInfo('/documents/file.pdf');
257
- console.log(info.ContentLength);
258
- console.log(info.ContentType);
259
- console.log(info.LastModified);
260
- ```
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
261
236
 
262
- #### List Files
263
- ```typescript
264
- // List all files in directory
265
- const files = await s3.fileListInfo('/documents');
266
237
 
267
- // List files with prefix
268
- 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
269
242
 
270
- // Paginated file listing
271
- const { files, totalFetched } = await s3.fileListInfoPaginated('/documents', {
272
- fileNamePrefix: 'invoice-',
273
- pageSize: 100,
274
- pageNumber: 0
275
- });
276
- ```
277
-
278
- #### File Size
279
- ```typescript
243
+ // > Get File Size
280
244
  const bytes = await s3.sizeOf('/large-file.zip');
281
245
  const kb = await s3.sizeOf('/large-file.zip', 'KB');
282
246
  const mb = await s3.sizeOf('/large-file.zip', 'MB');
283
247
  const gb = await s3.sizeOf('/large-file.zip', 'GB');
284
- ```
285
248
 
286
- #### Delete File
287
- ```typescript
288
- await s3.deleteFile('/documents/old-file.pdf');
289
- ```
249
+ // UPDATE
250
+ // > File Tagging
251
+ await s3.taggingFile('/documents/file.pdf', {Key: 'version', Value: '1.0.0'}); // Tag file with version
290
252
 
291
- #### Generate Presigned URL
292
- ```typescript
293
- // Expires in 15 minutes (default)
294
- const url = await s3.fileUrl('/private/document.pdf');
295
-
296
- // Custom expiration
297
- const url = await s3.fileUrl('/private/document.pdf', '1h');
298
- const url = await s3.fileUrl('/private/document.pdf', 3600); // seconds
299
- ```
300
253
 
301
- #### File Tagging
302
- ```typescript
303
- // Tag file with version
304
- await s3.taggingFile('/documents/file.pdf', '1.0.0');
254
+ // DELETE
255
+ await s3.deleteFile('/documents/old-file.pdf');
305
256
 
306
- // Get file version
307
- const version = await s3.fileVersion('/documents/file.pdf');
308
257
  ```
309
258
 
310
259
  ### 🎬 Streaming & Express.js Integration
@@ -494,11 +443,10 @@ import { S3LocalstackUtil } from '@hdriel/aws-utils';
494
443
 
495
444
  const s3 = new S3LocalstackUtil({
496
445
  bucket: 'test-bucket',
497
- endpoint: 'http://localhost:4566',
498
- region: 'us-east-1',
499
- accessKeyId: 'test',
500
- secretAccessKey: 'test',
501
- 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
502
450
  });
503
451
 
504
452
  // Use same API as S3Util
@@ -547,19 +495,6 @@ services:
547
495
 
548
496
  ### Dynamic Bucket Switching
549
497
 
550
- ```typescript
551
- const s3 = new S3Util({
552
- bucket: 'default-bucket',
553
- // ... other config
554
- });
555
-
556
- // Switch to different bucket
557
- s3.changeBucket('another-bucket');
558
-
559
- // Operations now use 'another-bucket'
560
- await s3.fileExists('/file.txt');
561
- ```
562
-
563
498
  ### Custom Logger Integration
564
499
 
565
500
  ```typescript
@@ -568,9 +503,9 @@ import { Logger } from 'stack-trace-logger';
568
503
  const logger = new Logger('S3Service');
569
504
 
570
505
  const s3 = new S3Util({
571
- bucket: 'my-bucket',
572
- logger,
573
- reqId: 'request-123'
506
+ bucket: 'my-bucket',
507
+ reqId: 'request-123',
508
+ logger,
574
509
  });
575
510
 
576
511
  // All operations will log with your logger
@@ -589,152 +524,16 @@ The utility includes optimized HTTP/HTTPS agents:
589
524
  // - socketTimeout: 30000ms
590
525
  ```
591
526
 
592
- ### Batch Operations
593
-
594
- ```typescript
595
- // Upload multiple files in parallel
596
- const files = [
597
- { path: '/docs/file1.pdf', data: buffer1 },
598
- { path: '/docs/file2.pdf', data: buffer2 },
599
- { path: '/docs/file3.pdf', data: buffer3 }
600
- ];
601
-
602
- await Promise.all(
603
- files.map(file => s3.uploadFile(file.path, file.data))
604
- );
605
-
606
- // Delete multiple files
607
- const filesToDelete = ['/old/file1.txt', '/old/file2.txt'];
608
- await Promise.all(
609
- filesToDelete.map(path => s3.deleteFile(path))
610
- );
611
- ```
612
527
 
613
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)
614
532
 
615
- ```typescript
616
- import express from 'express';
617
- import { S3Util, ACLs } from '@hdriel/aws-utils';
618
-
619
- const app = express();
620
- const s3 = new S3Util({
621
- bucket: process.env.S3_BUCKET!,
622
- region: process.env.AWS_REGION,
623
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
624
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
625
- });
626
-
627
- // Initialize bucket on startup
628
- (async () => {
629
- await s3.initBucket();
630
- console.log('S3 bucket initialized');
631
- })();
632
-
633
- // Upload endpoint
634
- app.post('/api/upload',
635
- s3.uploadSingleFile('file', '/uploads', {
636
- maxFileSize: '10MB',
637
- fileType: ['image', 'application'],
638
- filename: async (req, file) => {
639
- const timestamp = Date.now();
640
- const sanitized = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
641
- return `${timestamp}-${sanitized}`;
642
- }
643
- }),
644
- async (req, res) => {
645
- const { key, location, size } = req.s3File!;
646
-
647
- // Generate temporary URL
648
- const url = await s3.fileUrl(key, '1h');
649
-
650
- res.json({ key, location, size, temporaryUrl: url });
651
- }
652
- );
653
-
654
- // Download endpoint
655
- app.get('/api/download/:key(*)',
656
- async (req, res, next) => {
657
- const key = decodeURIComponent(req.params.key);
658
- const ctrl = await s3.getStreamFileCtrl({
659
- filePath: key,
660
- forDownloading: true
661
- });
662
- ctrl(req, res, next);
663
- }
664
- );
665
-
666
- // List files endpoint
667
- app.get('/api/files', async (req, res) => {
668
- const { page = '0', size = '50' } = req.query;
669
-
670
- const result = await s3.directoryListPaginated('/uploads', {
671
- pageNumber: parseInt(page as string),
672
- pageSize: parseInt(size as string)
673
- });
674
-
675
- res.json(result);
676
- });
677
-
678
- // Delete file endpoint
679
- app.delete('/api/files/:key(*)', async (req, res) => {
680
- const key = decodeURIComponent(req.params.key);
681
- await s3.deleteFile(key);
682
- res.json({ success: true });
683
- });
684
-
685
- // Video streaming endpoint
686
- app.get('/api/video/:id',
687
- async (req, res, next) => {
688
- const videoPath = `/videos/${req.params.id}.mp4`;
689
- const ctrl = await s3.getStreamVideoFileCtrl({
690
- fileKey: videoPath,
691
- contentType: 'video/mp4',
692
- bufferMB: 5
693
- });
694
- ctrl(req, res, next);
695
- }
696
- );
697
-
698
- app.listen(3000, () => {
699
- console.log('Server running on port 3000');
700
- });
701
- ```
702
-
703
- ## 🚀 Performance Tips
704
-
705
- 1. **Use Pagination**: For large directories, always use paginated methods
706
- 2. **Stream Large Files**: Use streaming methods instead of loading entire files into memory
707
- 3. **Connection Pooling**: The built-in connection pooling is optimized for concurrent requests
708
- 4. **Batch Operations**: Use `Promise.all()` for parallel operations when possible
709
- 5. **Presigned URLs**: Generate presigned URLs for direct client uploads/downloads when appropriate
710
-
711
- ## 🛡️ Error Handling
712
-
713
- ```typescript
714
- try {
715
- await s3.uploadFile('/docs/file.pdf', buffer);
716
- } catch (error) {
717
- if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
718
- console.error('File not found');
719
- } else {
720
- console.error('Upload failed:', error);
721
- }
722
- }
723
- ```
724
533
 
725
534
  ## 📝 TypeScript Support
726
535
 
727
- This package is written in TypeScript and includes full type definitions:
728
-
729
- ```typescript
730
- import type {
731
- ContentFile,
732
- FileUploadResponse,
733
- TreeDirectoryItem,
734
- UploadedS3File,
735
- S3UploadOptions
736
- } from '@hdriel/aws-utils';
737
- ```
536
+ This package is written in TypeScript and includes full type definitions
738
537
 
739
538
  ## 👤 Author
740
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,10 +960,16 @@ 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
- fileListInfo(directoryPath, fileNamePrefix) {
972
+ fileList(directoryPath, fileNamePrefix) {
961
973
  return __async(this, null, function* () {
962
974
  var _a2, _b;
963
975
  let normalizedPath = getNormalizedPath(directoryPath);
@@ -985,7 +997,7 @@ var S3File = class extends S3Directory {
985
997
  });
986
998
  }
987
999
  // todo: checked!
988
- fileListInfoPaginated(_0) {
1000
+ fileListPaginated(_0) {
989
1001
  return __async(this, arguments, function* (directoryPath, {
990
1002
  fileNamePrefix,
991
1003
  pageNumber = 0,
@@ -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,11 +242,15 @@ 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>;
246
- fileListInfo(directoryPath?: string, fileNamePrefix?: string): Promise<(ContentFile & {
245
+ fileInfo(filePath: string): Promise<HeadObjectCommandOutput & {
246
+ Name: string;
247
+ Location: string;
248
+ Key: string;
249
+ }>;
250
+ fileList(directoryPath?: string, fileNamePrefix?: string): Promise<(ContentFile & {
247
251
  Location: string;
248
252
  })[]>;
249
- fileListInfoPaginated(directoryPath?: string, { fileNamePrefix, pageNumber, // 0-based: page 0 = items 0-99, page 1 = items 100-199, page 2 = items 200-299
253
+ fileListPaginated(directoryPath?: string, { fileNamePrefix, pageNumber, // 0-based: page 0 = items 0-99, page 1 = items 100-199, page 2 = items 200-299
250
254
  pageSize, }?: {
251
255
  fileNamePrefix?: string;
252
256
  pageSize?: number;
@@ -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,11 +242,15 @@ 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>;
246
- fileListInfo(directoryPath?: string, fileNamePrefix?: string): Promise<(ContentFile & {
245
+ fileInfo(filePath: string): Promise<HeadObjectCommandOutput & {
246
+ Name: string;
247
+ Location: string;
248
+ Key: string;
249
+ }>;
250
+ fileList(directoryPath?: string, fileNamePrefix?: string): Promise<(ContentFile & {
247
251
  Location: string;
248
252
  })[]>;
249
- fileListInfoPaginated(directoryPath?: string, { fileNamePrefix, pageNumber, // 0-based: page 0 = items 0-99, page 1 = items 100-199, page 2 = items 200-299
253
+ fileListPaginated(directoryPath?: string, { fileNamePrefix, pageNumber, // 0-based: page 0 = items 0-99, page 1 = items 100-199, page 2 = items 200-299
250
254
  pageSize, }?: {
251
255
  fileNamePrefix?: string;
252
256
  pageSize?: number;
@@ -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,10 +949,16 @@ 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
- fileListInfo(directoryPath, fileNamePrefix) {
961
+ fileList(directoryPath, fileNamePrefix) {
950
962
  return __async(this, null, function* () {
951
963
  var _a2, _b;
952
964
  let normalizedPath = getNormalizedPath(directoryPath);
@@ -974,7 +986,7 @@ var S3File = class extends S3Directory {
974
986
  });
975
987
  }
976
988
  // todo: checked!
977
- fileListInfoPaginated(_0) {
989
+ fileListPaginated(_0) {
978
990
  return __async(this, arguments, function* (directoryPath, {
979
991
  fileNamePrefix,
980
992
  pageNumber = 0,
@@ -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.4",
3
+ "version": "1.1.6",
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",