@hdriel/aws-utils 1.0.3 → 1.1.1

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/dist/index.cjs CHANGED
@@ -24,6 +24,18 @@ var __spreadValues = (a, b) => {
24
24
  return a;
25
25
  };
26
26
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
27
+ var __objRest = (source, exclude) => {
28
+ var target = {};
29
+ for (var prop in source)
30
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
31
+ target[prop] = source[prop];
32
+ if (source != null && __getOwnPropSymbols)
33
+ for (var prop of __getOwnPropSymbols(source)) {
34
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
35
+ target[prop] = source[prop];
36
+ }
37
+ return target;
38
+ };
27
39
  var __export = (target, all) => {
28
40
  for (var name in all)
29
41
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -75,7 +87,8 @@ __export(index_exports, {
75
87
  AWSConfigSharingUtil: () => AWSConfigSharingUtil,
76
88
  IAMUtil: () => IAMUtil,
77
89
  LambdaUtil: () => LambdaUtil,
78
- S3BucketUtil: () => S3BucketUtil,
90
+ S3LocalstackUtil: () => S3LocalstackUtil,
91
+ S3Util: () => S3Util,
79
92
  SNSUtil: () => SNSUtil
80
93
  });
81
94
  module.exports = __toCommonJS(index_exports);
@@ -254,20 +267,19 @@ var LambdaUtil = class {
254
267
  }
255
268
  };
256
269
 
257
- // src/aws/s3-bucket.ts
258
- var import_ms = __toESM(require("ms"), 1);
270
+ // src/aws/s3/s3-util.localstack.ts
271
+ var import_client_s36 = require("@aws-sdk/client-s3");
272
+
273
+ // src/aws/s3/s3-stream.ts
259
274
  var import_pathe = __toESM(require("pathe"), 1);
260
- var import_http = __toESM(require("http"), 1);
261
- var import_https = __toESM(require("https"), 1);
262
275
  var import_stream = require("stream");
263
276
  var import_util = require("util");
264
- var import_lib_storage = require("@aws-sdk/lib-storage");
265
- var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
266
- var import_node_http_handler = require("@smithy/node-http-handler");
267
- var import_buffer = require("buffer");
277
+ var import_buffer2 = require("buffer");
268
278
  var import_archiver = __toESM(require("archiver"), 1);
269
- var import_node_stream = require("stream");
270
- var import_client_s3 = require("@aws-sdk/client-s3");
279
+ var import_node_stream2 = require("stream");
280
+ var import_multer_s3 = __toESM(require("multer-s3"), 1);
281
+ var import_multer = __toESM(require("multer"), 1);
282
+ var import_client_s35 = require("@aws-sdk/client-s3");
271
283
 
272
284
  // src/utils/consts.ts
273
285
  var ACLs = /* @__PURE__ */ ((ACLs2) => {
@@ -281,11 +293,8 @@ var ACLs = /* @__PURE__ */ ((ACLs2) => {
281
293
  var import_p_limit = __toESM(require("p-limit"), 1);
282
294
  var s3Limiter = (0, import_p_limit.default)(4);
283
295
 
284
- // src/aws/s3-bucket.ts
285
- var import_multer = __toESM(require("multer"), 1);
286
- var import_multer_s3 = __toESM(require("multer-s3"), 1);
296
+ // src/utils/helpers.ts
287
297
  var import_bytes = __toESM(require("bytes"), 1);
288
- var pump = (0, import_util.promisify)(import_stream.pipeline);
289
298
  var parseRangeHeader = (range, contentLength, chunkSize) => {
290
299
  if (!range || !range.startsWith("bytes=")) return null;
291
300
  const rangeParts = range.replace("bytes=", "").split("-");
@@ -298,34 +307,60 @@ var parseRangeHeader = (range, contentLength, chunkSize) => {
298
307
  }
299
308
  return [start, Math.min(end, end)];
300
309
  };
301
- var getNormalizedPath = (directoryPath) => decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\/+/, "").replace(/\/+$/, "")) || "");
302
- var S3BucketUtil = class _S3BucketUtil {
310
+ var getNormalizedPath = (directoryPath) => {
311
+ return decodeURIComponent((directoryPath == null ? void 0 : directoryPath.trim().replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/+/g, "/")) || "");
312
+ };
313
+ var getFileSize = (maxFileSize, defaultMaxFileSize) => {
314
+ var _a2;
315
+ const fileSizeUnitValue = (_a2 = maxFileSize != null ? maxFileSize : defaultMaxFileSize) != null ? _a2 : "";
316
+ const fileSize = typeof fileSizeUnitValue === "number" ? fileSizeUnitValue : (0, import_bytes.default)(fileSizeUnitValue);
317
+ return fileSize != null ? fileSize : void 0;
318
+ };
319
+
320
+ // src/aws/s3/s3-file.ts
321
+ var import_buffer = require("buffer");
322
+ var import_node_stream = require("stream");
323
+ var import_ms = __toESM(require("ms"), 1);
324
+ var import_lib_storage = require("@aws-sdk/lib-storage");
325
+ var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
326
+ var import_client_s34 = require("@aws-sdk/client-s3");
327
+
328
+ // src/aws/s3/s3-directory.ts
329
+ var import_client_s33 = require("@aws-sdk/client-s3");
330
+
331
+ // src/aws/s3/s3-bucket.ts
332
+ var import_client_s32 = require("@aws-sdk/client-s3");
333
+
334
+ // src/aws/s3/s3-base.ts
335
+ var import_http = __toESM(require("http"), 1);
336
+ var import_https = __toESM(require("https"), 1);
337
+ var import_node_http_handler = require("@smithy/node-http-handler");
338
+ var import_client_s3 = require("@aws-sdk/client-s3");
339
+ var S3Base = class {
303
340
  constructor({
304
341
  logger: logger2,
305
- bucket,
306
342
  reqId,
307
343
  accessKeyId = AWSConfigSharingUtil.accessKeyId,
308
344
  secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
309
345
  endpoint = AWSConfigSharingUtil.endpoint,
310
346
  region = AWSConfigSharingUtil.region,
311
347
  s3ForcePathStyle = true,
312
- maxUploadFileSizeRestriction = "10GB"
348
+ // @ts-ignore
349
+ localstack = false
313
350
  }) {
314
351
  __publicField(this, "s3Client");
315
- __publicField(this, "bucket");
316
352
  __publicField(this, "endpoint");
317
353
  __publicField(this, "region");
318
354
  __publicField(this, "logger");
319
355
  __publicField(this, "reqId");
320
- __publicField(this, "maxUploadFileSizeRestriction");
356
+ __publicField(this, "localstack", false);
321
357
  const credentials = { accessKeyId, secretAccessKey };
322
358
  const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
323
359
  this.endpoint = endpoint;
324
360
  this.region = region;
325
- this.bucket = bucket;
326
361
  this.logger = logger2;
327
362
  this.reqId = reqId != null ? reqId : null;
328
- this.maxUploadFileSizeRestriction = maxUploadFileSizeRestriction;
363
+ this.localstack = localstack;
329
364
  const s3ClientParams = __spreadProps(__spreadValues(__spreadValues({}, options), s3ForcePathStyle && { forcePathStyle: s3ForcePathStyle }), {
330
365
  requestHandler: new import_node_http_handler.NodeHttpHandler({
331
366
  httpAgent: new import_http.default.Agent({ keepAlive: true, maxSockets: 300 }),
@@ -336,18 +371,40 @@ var S3BucketUtil = class _S3BucketUtil {
336
371
  });
337
372
  this.s3Client = new import_client_s3.S3Client(s3ClientParams);
338
373
  }
339
- get link() {
340
- return this.endpoint === "http://localhost:4566" ? `${this.endpoint}/${this.bucket}/` : `https://s3.${this.region}.amazonaws.com/${this.bucket}/`;
341
- }
342
374
  execute(command, options) {
343
375
  return __async(this, null, function* () {
344
376
  return this.s3Client.send(command, options);
345
377
  });
346
378
  }
347
- // ##### BUCKET BLOCK ##########################
379
+ };
380
+
381
+ // src/aws/s3/s3-bucket.ts
382
+ var S3Bucket = class extends S3Base {
383
+ constructor(_a2) {
384
+ var _b = _a2, { bucket } = _b, props = __objRest(_b, ["bucket"]);
385
+ super(props);
386
+ __publicField(this, "_bucket");
387
+ __publicField(this, "initializedBucket", "");
388
+ this._bucket = decodeURIComponent(bucket);
389
+ }
390
+ get link() {
391
+ return this.endpoint === "http://localhost:4566" ? `${this.endpoint}/${this.bucket}/` : `https://s3.${this.region}.amazonaws.com/${this.bucket}/`;
392
+ }
393
+ get bucket() {
394
+ return this._bucket;
395
+ }
396
+ changeBucket(bucket) {
397
+ this._bucket = decodeURIComponent(bucket);
398
+ this.initializedBucket = "";
399
+ }
348
400
  getBucketList() {
349
- return __async(this, arguments, function* (options = {}, includePublicAccess = false) {
350
- const command = new import_client_s3.ListBucketsCommand(options);
401
+ return __async(this, arguments, function* (_c = {}) {
402
+ var _d = _c, {
403
+ includePublicAccess
404
+ } = _d, options = __objRest(_d, [
405
+ "includePublicAccess"
406
+ ]);
407
+ const command = new import_client_s32.ListBucketsCommand(options);
351
408
  const response = yield this.execute(command);
352
409
  const responseData = (response == null ? void 0 : response.Buckets) || null;
353
410
  if (!responseData) return null;
@@ -355,7 +412,7 @@ var S3BucketUtil = class _S3BucketUtil {
355
412
  yield Promise.allSettled(
356
413
  responseData.map((data) => __async(this, null, function* () {
357
414
  const result = yield this.execute(
358
- new import_client_s3.GetPublicAccessBlockCommand({ Bucket: data.Name })
415
+ new import_client_s32.GetPublicAccessBlockCommand({ Bucket: data.Name })
359
416
  );
360
417
  data.PublicAccessBlockConfiguration = result.PublicAccessBlockConfiguration;
361
418
  }))
@@ -369,7 +426,7 @@ var S3BucketUtil = class _S3BucketUtil {
369
426
  var _a2, _b;
370
427
  const bucketName = this.bucket;
371
428
  try {
372
- yield this.execute(new import_client_s3.HeadBucketCommand({ Bucket: bucketName }));
429
+ yield this.execute(new import_client_s32.HeadBucketCommand({ Bucket: bucketName }));
373
430
  return true;
374
431
  } catch (err) {
375
432
  if (err.name !== "NotFound" && ((_a2 = err.$metadata) == null ? void 0 : _a2.httpStatusCode) !== 404) {
@@ -390,9 +447,9 @@ var S3BucketUtil = class _S3BucketUtil {
390
447
  (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
391
448
  return;
392
449
  }
393
- const data = yield this.execute(new import_client_s3.CreateBucketCommand({ Bucket: bucketName }));
450
+ const data = yield this.execute(new import_client_s32.CreateBucketCommand({ Bucket: bucketName }));
394
451
  CREATE_PUBLICK_ACCESS_BLOCK: {
395
- const command = new import_client_s3.PutPublicAccessBlockCommand({
452
+ const command = new import_client_s32.PutPublicAccessBlockCommand({
396
453
  Bucket: bucketName,
397
454
  PublicAccessBlockConfiguration: {
398
455
  BlockPublicAcls: false,
@@ -416,7 +473,7 @@ var S3BucketUtil = class _S3BucketUtil {
416
473
  }
417
474
  ]
418
475
  };
419
- const command = new import_client_s3.PutBucketPolicyCommand({ Bucket: bucketName, Policy: JSON.stringify(policy) });
476
+ const command = new import_client_s32.PutBucketPolicyCommand({ Bucket: bucketName, Policy: JSON.stringify(policy) });
420
477
  yield this.execute(command);
421
478
  }
422
479
  (_b = this.logger) == null ? void 0 : _b.info(this.reqId, `Public bucket created successfully.`, { bucketName });
@@ -437,21 +494,28 @@ var S3BucketUtil = class _S3BucketUtil {
437
494
  }, includeConstraintLocation && {
438
495
  CreateBucketConfiguration: { LocationConstraint: this.region }
439
496
  });
440
- const data = yield this.execute(new import_client_s3.CreateBucketCommand(createParams));
497
+ const data = yield this.execute(new import_client_s32.CreateBucketCommand(createParams));
441
498
  (_b = this.logger) == null ? void 0 : _b.info(this.reqId, `Private bucket created successfully.`, { bucketName });
442
499
  return data;
443
500
  });
444
501
  }
445
502
  initBucket() {
446
- return __async(this, arguments, function* (acl = "private" /* private */, includeConstraintLocation = false) {
503
+ return __async(this, arguments, function* (acl = "private" /* private */, {
504
+ includeConstraintLocation = false,
505
+ skipInitializedBucket = false
506
+ } = {}) {
447
507
  var _a2;
448
508
  const bucketName = this.bucket;
509
+ if (skipInitializedBucket && this.initializedBucket === bucketName) {
510
+ return;
511
+ }
449
512
  const isExists = yield this.isBucketExists();
450
513
  if (isExists) {
451
514
  (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
452
515
  return;
453
516
  }
454
517
  const data = acl === "private" /* private */ ? yield this.initAsPrivateBucket(includeConstraintLocation) : yield this.initAsPublicBucket();
518
+ this.initializedBucket = bucketName;
455
519
  return data;
456
520
  });
457
521
  }
@@ -460,14 +524,14 @@ var S3BucketUtil = class _S3BucketUtil {
460
524
  let ContinuationToken = void 0;
461
525
  do {
462
526
  const listResp = yield this.execute(
463
- new import_client_s3.ListObjectsV2Command({
527
+ new import_client_s32.ListObjectsV2Command({
464
528
  Bucket: this.bucket,
465
529
  ContinuationToken
466
530
  })
467
531
  );
468
532
  if (listResp.Contents && listResp.Contents.length > 0) {
469
533
  yield this.execute(
470
- new import_client_s3.DeleteObjectsCommand({
534
+ new import_client_s32.DeleteObjectsCommand({
471
535
  Bucket: this.bucket,
472
536
  Delete: {
473
537
  Objects: listResp.Contents.map((obj) => ({ Key: obj.Key }))
@@ -492,7 +556,7 @@ var S3BucketUtil = class _S3BucketUtil {
492
556
  try {
493
557
  try {
494
558
  const headBucketResponse = yield this.execute(
495
- new import_client_s3.HeadBucketCommand(__spreadValues({ Bucket: bucketName }, options))
559
+ new import_client_s32.HeadBucketCommand(__spreadValues({ Bucket: bucketName }, options))
496
560
  );
497
561
  (_a2 = this.logger) == null ? void 0 : _a2.debug("bucketInfo", "HeadBucketCommandOutput", headBucketResponse);
498
562
  info.exists = true;
@@ -516,7 +580,7 @@ var S3BucketUtil = class _S3BucketUtil {
516
580
  }
517
581
  try {
518
582
  const aclResponse = yield this.execute(
519
- new import_client_s3.GetBucketAclCommand({ Bucket: bucketName })
583
+ new import_client_s32.GetBucketAclCommand({ Bucket: bucketName })
520
584
  );
521
585
  (_e = this.logger) == null ? void 0 : _e.debug("bucketInfo", "GetBucketAclCommandOutput", aclResponse);
522
586
  info.acl = (_f = aclResponse.Grants) == null ? void 0 : _f.map((grant) => {
@@ -531,7 +595,7 @@ var S3BucketUtil = class _S3BucketUtil {
531
595
  }
532
596
  try {
533
597
  const publicAccessResponse = yield this.execute(
534
- new import_client_s3.GetPublicAccessBlockCommand({ Bucket: bucketName })
598
+ new import_client_s32.GetPublicAccessBlockCommand({ Bucket: bucketName })
535
599
  );
536
600
  (_h = this.logger) == null ? void 0 : _h.debug("bucketInfo", "GetPublicAccessBlockCommandOutput", publicAccessResponse);
537
601
  info.publicAccessBlock = publicAccessResponse.PublicAccessBlockConfiguration;
@@ -542,7 +606,7 @@ var S3BucketUtil = class _S3BucketUtil {
542
606
  }
543
607
  try {
544
608
  const policyResponse = yield this.execute(
545
- new import_client_s3.GetBucketPolicyCommand({ Bucket: bucketName })
609
+ new import_client_s32.GetBucketPolicyCommand({ Bucket: bucketName })
546
610
  );
547
611
  (_j = this.logger) == null ? void 0 : _j.debug("bucketInfo", "GetBucketPolicyCommandOutput", policyResponse);
548
612
  if (policyResponse.Policy) {
@@ -555,7 +619,7 @@ var S3BucketUtil = class _S3BucketUtil {
555
619
  }
556
620
  try {
557
621
  const versioningResponse = yield this.execute(
558
- new import_client_s3.GetBucketVersioningCommand({ Bucket: bucketName })
622
+ new import_client_s32.GetBucketVersioningCommand({ Bucket: bucketName })
559
623
  );
560
624
  (_l = this.logger) == null ? void 0 : _l.debug("bucketInfo", "GetBucketVersioningCommandOutput", versioningResponse);
561
625
  info.versioning = versioningResponse.Status || "Disabled";
@@ -564,7 +628,7 @@ var S3BucketUtil = class _S3BucketUtil {
564
628
  }
565
629
  try {
566
630
  const encryptionResponse = yield this.execute(
567
- new import_client_s3.GetBucketEncryptionCommand({ Bucket: bucketName })
631
+ new import_client_s32.GetBucketEncryptionCommand({ Bucket: bucketName })
568
632
  );
569
633
  (_n = this.logger) == null ? void 0 : _n.debug("bucketInfo", "GetBucketEncryptionCommandOutput", encryptionResponse);
570
634
  info.encryption = {
@@ -600,21 +664,46 @@ var S3BucketUtil = class _S3BucketUtil {
600
664
  yield this.emptyBucket();
601
665
  }
602
666
  const createParams = { Bucket: bucketName };
603
- const data = yield this.execute(new import_client_s3.DeleteBucketCommand(createParams));
667
+ const data = yield this.execute(new import_client_s32.DeleteBucketCommand(createParams));
604
668
  return data;
605
669
  });
606
670
  }
607
- // ##### DIRECTORY BLOCK ##########################
671
+ };
672
+
673
+ // src/aws/s3/s3-directory.ts
674
+ var S3Directory = class extends S3Bucket {
675
+ constructor(props) {
676
+ super(props);
677
+ }
678
+ // todo: checked!
679
+ directoryExists(directoryPath) {
680
+ return __async(this, null, function* () {
681
+ var _a2;
682
+ try {
683
+ const normalizedKey = getNormalizedPath(directoryPath);
684
+ if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
685
+ const command = new import_client_s33.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
686
+ yield this.execute(command);
687
+ return true;
688
+ } catch (error) {
689
+ if (error.name === "NotFound" || ((_a2 = error.$metadata) == null ? void 0 : _a2.httpStatusCode) === 404) {
690
+ return false;
691
+ }
692
+ throw error;
693
+ }
694
+ });
695
+ }
696
+ // todo: checked!
608
697
  createDirectory(directoryPath) {
609
698
  return __async(this, null, function* () {
610
699
  let normalizedPath = getNormalizedPath(directoryPath);
611
- if (!normalizedPath) throw new Error("No directory path provided");
612
- if (normalizedPath === "/") normalizedPath = "";
613
- const command = new import_client_s3.PutObjectCommand({ Bucket: this.bucket, Key: `${normalizedPath}/` });
700
+ if (!normalizedPath || normalizedPath === "/") throw new Error("No directory path provided");
701
+ const command = new import_client_s33.PutObjectCommand({ Bucket: this.bucket, Key: `${normalizedPath}/` });
614
702
  const result = yield this.execute(command);
615
703
  return result;
616
704
  });
617
705
  }
706
+ // todo: checked!
618
707
  deleteDirectory(directoryPath) {
619
708
  return __async(this, null, function* () {
620
709
  var _a2, _b, _c, _d, _e, _f, _g;
@@ -625,7 +714,7 @@ var S3BucketUtil = class _S3BucketUtil {
625
714
  let ContinuationToken = void 0;
626
715
  do {
627
716
  const listResp = yield this.execute(
628
- new import_client_s3.ListObjectsV2Command({
717
+ new import_client_s33.ListObjectsV2Command({
629
718
  Bucket: this.bucket,
630
719
  Prefix: normalizedPath,
631
720
  ContinuationToken
@@ -633,7 +722,7 @@ var S3BucketUtil = class _S3BucketUtil {
633
722
  );
634
723
  if (listResp.Contents && listResp.Contents.length > 0) {
635
724
  const deleteResult = yield this.execute(
636
- new import_client_s3.DeleteObjectsCommand({
725
+ new import_client_s33.DeleteObjectsCommand({
637
726
  Bucket: this.bucket,
638
727
  Delete: {
639
728
  Objects: listResp.Contents.map((obj) => ({ Key: obj.Key })),
@@ -652,7 +741,7 @@ var S3BucketUtil = class _S3BucketUtil {
652
741
  ContinuationToken = listResp.NextContinuationToken;
653
742
  } while (ContinuationToken);
654
743
  if (totalDeletedCount === 0) {
655
- const directoryExists = yield this.fileExists(normalizedPath);
744
+ const directoryExists = yield this.directoryExists(normalizedPath);
656
745
  if (!directoryExists) {
657
746
  (_d = this.logger) == null ? void 0 : _d.debug(this.reqId, `Directory not found`, { directoryPath: normalizedPath });
658
747
  return null;
@@ -660,7 +749,7 @@ var S3BucketUtil = class _S3BucketUtil {
660
749
  }
661
750
  try {
662
751
  yield this.execute(
663
- new import_client_s3.DeleteObjectCommand({
752
+ new import_client_s33.DeleteObjectCommand({
664
753
  Bucket: this.bucket,
665
754
  Key: normalizedPath
666
755
  })
@@ -684,33 +773,64 @@ var S3BucketUtil = class _S3BucketUtil {
684
773
  };
685
774
  });
686
775
  }
776
+ // todo: checked!
687
777
  directoryList(directoryPath) {
688
778
  return __async(this, null, function* () {
779
+ var _a2;
689
780
  let normalizedPath = getNormalizedPath(directoryPath);
690
781
  if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
691
- else normalizedPath = "";
692
- const result = yield this.execute(
693
- new import_client_s3.ListObjectsV2Command({
694
- Bucket: this.bucket,
695
- Prefix: normalizedPath,
696
- Delimiter: "/"
697
- })
698
- );
782
+ else normalizedPath = this.localstack ? "" : "/";
783
+ let result;
784
+ if (normalizedPath === "") {
785
+ const [fileResponse, { CommonPrefixes }] = yield Promise.all([
786
+ this.execute(
787
+ new import_client_s33.ListObjectsV2Command({
788
+ Bucket: this.bucket,
789
+ Prefix: "/",
790
+ Delimiter: "/"
791
+ })
792
+ ),
793
+ yield this.execute(
794
+ new import_client_s33.ListObjectsV2Command({
795
+ Bucket: this.bucket,
796
+ Prefix: "",
797
+ Delimiter: "/"
798
+ })
799
+ )
800
+ ]);
801
+ result = fileResponse;
802
+ result.CommonPrefixes = CommonPrefixes;
803
+ } else {
804
+ result = yield this.execute(
805
+ new import_client_s33.ListObjectsV2Command({
806
+ Bucket: this.bucket,
807
+ Prefix: normalizedPath,
808
+ Delimiter: "/"
809
+ })
810
+ );
811
+ }
812
+ (_a2 = this.logger) == null ? void 0 : _a2.debug(null, "#### directoryList", {
813
+ normalizedPath,
814
+ CommonPrefixes: result.CommonPrefixes,
815
+ ContentFile: result.Contents
816
+ });
699
817
  const directories = (result.CommonPrefixes || []).map((prefix) => prefix.Prefix).map((prefix) => {
700
818
  const relativePath = prefix.replace(normalizedPath, "");
701
819
  const dir = relativePath.replace(/\/$/, "");
702
820
  return dir;
703
821
  }).filter((dir) => dir);
704
822
  const files = (result.Contents || []).filter((content) => {
705
- var _a2;
706
- return content.Key !== normalizedPath && !((_a2 = content.Key) == null ? void 0 : _a2.endsWith("/"));
823
+ var _a3;
824
+ return content.Key !== normalizedPath && !((_a3 = content.Key) == null ? void 0 : _a3.endsWith("/"));
707
825
  }).map((content) => __spreadProps(__spreadValues({}, content), {
708
826
  Name: content.Key.replace(normalizedPath, "") || content.Key,
827
+ Location: `${this.link}${content.Key.replace(/^\//, "")}`,
709
828
  LastModified: new Date(content.LastModified)
710
829
  }));
711
830
  return { directories, files };
712
831
  });
713
832
  }
833
+ // todo: checked!
714
834
  directoryListPaginated(_0) {
715
835
  return __async(this, arguments, function* (directoryPath, {
716
836
  pageSize = 100,
@@ -725,8 +845,9 @@ var S3BucketUtil = class _S3BucketUtil {
725
845
  let allDirectories = [];
726
846
  let allFiles = [];
727
847
  while (currentPage <= pageNumber) {
728
- const result = yield this.execute(
729
- new import_client_s3.ListObjectsV2Command({
848
+ let result;
849
+ result = yield this.execute(
850
+ new import_client_s33.ListObjectsV2Command({
730
851
  Bucket: this.bucket,
731
852
  Prefix: normalizedPath,
732
853
  Delimiter: "/",
@@ -744,6 +865,7 @@ var S3BucketUtil = class _S3BucketUtil {
744
865
  return content.Key !== normalizedPath && !((_a2 = content.Key) == null ? void 0 : _a2.endsWith("/"));
745
866
  }).map((content) => __spreadProps(__spreadValues({}, content), {
746
867
  Name: content.Key.replace(normalizedPath, "") || content.Key,
868
+ Location: `${this.link}${content.Key.replace(/^\//, "")}`,
747
869
  LastModified: new Date(content.LastModified)
748
870
  }));
749
871
  }
@@ -766,19 +888,21 @@ var S3BucketUtil = class _S3BucketUtil {
766
888
  */
767
889
  directoryListRecursive(directoryPath) {
768
890
  return __async(this, null, function* () {
891
+ var _a2;
769
892
  let normalizedPath = getNormalizedPath(directoryPath);
770
893
  if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
771
- else normalizedPath = "";
894
+ else normalizedPath = "/";
772
895
  const allDirectories = [];
773
896
  const allFiles = [];
774
897
  let ContinuationToken = void 0;
775
898
  do {
776
- const command = new import_client_s3.ListObjectsV2Command({
777
- Bucket: this.bucket,
778
- Prefix: normalizedPath,
779
- ContinuationToken
780
- });
781
- const result = yield this.execute(command);
899
+ const result = yield this.execute(
900
+ new import_client_s33.ListObjectsV2Command({
901
+ Bucket: this.bucket,
902
+ Prefix: normalizedPath,
903
+ ContinuationToken
904
+ })
905
+ );
782
906
  if (result.Contents) {
783
907
  for (const content of result.Contents) {
784
908
  const fullPath = content.Key;
@@ -790,7 +914,8 @@ var S3BucketUtil = class _S3BucketUtil {
790
914
  allFiles.push(__spreadProps(__spreadValues({}, content), {
791
915
  Name: filename,
792
916
  Path: fullPath,
793
- LastModified: new Date(content.LastModified)
917
+ Location: content.Key ? `${this.link}${(_a2 = content.Key) == null ? void 0 : _a2.replace(/^\//, "")}` : "",
918
+ LastModified: content.LastModified ? new Date(content.LastModified) : null
794
919
  }));
795
920
  }
796
921
  }
@@ -846,6 +971,7 @@ var S3BucketUtil = class _S3BucketUtil {
846
971
  treeNode.children.push({
847
972
  path: "/" + file.Key,
848
973
  name: file.Name,
974
+ location: `${this.link}${file.Key.replace(/^\//, "")}`,
849
975
  type: "file",
850
976
  size: file.Size,
851
977
  lastModified: file.LastModified
@@ -859,14 +985,18 @@ var S3BucketUtil = class _S3BucketUtil {
859
985
  return treeNode;
860
986
  });
861
987
  }
862
- // ##### FILES BLOCK ##########################
988
+ };
989
+
990
+ // src/aws/s3/s3-file.ts
991
+ var S3File = class extends S3Directory {
992
+ constructor(props) {
993
+ super(props);
994
+ }
863
995
  fileInfo(filePath) {
864
996
  return __async(this, null, function* () {
865
997
  const normalizedKey = getNormalizedPath(filePath);
866
- if (!normalizedKey || normalizedKey === "/") {
867
- throw new Error("No file key provided");
868
- }
869
- const command = new import_client_s3.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
998
+ if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
999
+ const command = new import_client_s34.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
870
1000
  return yield this.execute(command);
871
1001
  });
872
1002
  }
@@ -875,9 +1005,9 @@ var S3BucketUtil = class _S3BucketUtil {
875
1005
  var _a2, _b;
876
1006
  let normalizedPath = getNormalizedPath(directoryPath);
877
1007
  if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
878
- else normalizedPath = "";
1008
+ else normalizedPath = this.localstack ? "" : "/";
879
1009
  const prefix = normalizedPath + (fileNamePrefix || "");
880
- const command = new import_client_s3.ListObjectsCommand({
1010
+ const command = new import_client_s34.ListObjectsCommand({
881
1011
  Bucket: this.bucket,
882
1012
  Prefix: prefix,
883
1013
  Delimiter: "/"
@@ -885,9 +1015,10 @@ var S3BucketUtil = class _S3BucketUtil {
885
1015
  const result = yield this.execute(command);
886
1016
  const files = ((_a2 = result.Contents) != null ? _a2 : []).filter((v) => v).map(
887
1017
  (content) => {
888
- var _a3, _b2;
1018
+ var _a3, _b2, _c;
889
1019
  return __spreadProps(__spreadValues({}, content), {
890
1020
  Name: (_b2 = (_a3 = content.Key) == null ? void 0 : _a3.replace(prefix, "")) != null ? _b2 : content.Key,
1021
+ Location: content.Key ? `${this.link}${(_c = content.Key) == null ? void 0 : _c.replace(/^\//, "")}` : "",
891
1022
  LastModified: content.LastModified ? new Date(content.LastModified) : null
892
1023
  });
893
1024
  }
@@ -896,6 +1027,7 @@ var S3BucketUtil = class _S3BucketUtil {
896
1027
  return files;
897
1028
  });
898
1029
  }
1030
+ // todo: checked!
899
1031
  fileListInfoPaginated(_0) {
900
1032
  return __async(this, arguments, function* (directoryPath, {
901
1033
  fileNamePrefix,
@@ -913,7 +1045,7 @@ var S3BucketUtil = class _S3BucketUtil {
913
1045
  let resultFiles = [];
914
1046
  while (currentPage <= pageNumber) {
915
1047
  const result = yield this.execute(
916
- new import_client_s3.ListObjectsV2Command({
1048
+ new import_client_s34.ListObjectsV2Command({
917
1049
  Bucket: this.bucket,
918
1050
  Prefix: prefix,
919
1051
  Delimiter: "/",
@@ -927,6 +1059,7 @@ var S3BucketUtil = class _S3BucketUtil {
927
1059
  var _a3, _b2;
928
1060
  return __spreadProps(__spreadValues({}, content), {
929
1061
  Name: (_b2 = (_a3 = content.Key) == null ? void 0 : _a3.replace(prefix, "")) != null ? _b2 : content.Key,
1062
+ Location: content.Key ? `${this.link}${content.Key.replace(/^\//, "")}` : "",
930
1063
  LastModified: content.LastModified ? new Date(content.LastModified) : null
931
1064
  });
932
1065
  }
@@ -950,41 +1083,48 @@ var S3BucketUtil = class _S3BucketUtil {
950
1083
  };
951
1084
  });
952
1085
  }
953
- taggingFile(filePath, tagVersion = "1.0.0") {
1086
+ // todo: checked!
1087
+ taggingFile(filePath, tag) {
954
1088
  return __async(this, null, function* () {
1089
+ var _a2;
1090
+ let normalizedKey = "";
1091
+ const tags = [].concat(tag);
955
1092
  try {
956
- const normalizedKey = getNormalizedPath(filePath);
1093
+ normalizedKey = getNormalizedPath(filePath);
957
1094
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
958
- const command = new import_client_s3.PutObjectTaggingCommand({
1095
+ const command = new import_client_s34.PutObjectTaggingCommand({
959
1096
  Bucket: this.bucket,
960
1097
  Key: normalizedKey,
961
- Tagging: { TagSet: [{ Key: "version", Value: tagVersion }] }
1098
+ Tagging: { TagSet: tags }
962
1099
  });
963
1100
  yield this.execute(command);
964
1101
  return true;
965
- } catch (e) {
1102
+ } catch (error) {
1103
+ (_a2 = this.logger) == null ? void 0 : _a2.warn(null, "failed to tagging file", { errMsg: error.message, fileKey: normalizedKey, tags });
966
1104
  return false;
967
1105
  }
968
1106
  });
969
1107
  }
1108
+ // todo: checked!
970
1109
  fileVersion(filePath) {
971
1110
  return __async(this, null, function* () {
972
1111
  var _a2, _b;
973
1112
  const normalizedKey = getNormalizedPath(filePath);
974
1113
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
975
- const command = new import_client_s3.GetObjectTaggingCommand({ Bucket: this.bucket, Key: normalizedKey });
1114
+ const command = new import_client_s34.GetObjectTaggingCommand({ Bucket: this.bucket, Key: normalizedKey });
976
1115
  const result = yield this.execute(command);
977
1116
  const tag = (_a2 = result.TagSet) == null ? void 0 : _a2.find((tag2) => tag2.Key === "version");
978
1117
  return (_b = tag == null ? void 0 : tag.Value) != null ? _b : "";
979
1118
  });
980
1119
  }
1120
+ // todo: checked!
981
1121
  fileUrl(filePath, expiresIn = "15m") {
982
1122
  return __async(this, null, function* () {
983
1123
  var _a2;
984
- const normalizedKey = getNormalizedPath(filePath);
1124
+ let normalizedKey = getNormalizedPath(filePath);
985
1125
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
986
1126
  const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : (0, import_ms.default)(expiresIn) / 1e3;
987
- const command = new import_client_s3.GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1127
+ const command = new import_client_s34.GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
988
1128
  const url = yield (0, import_s3_request_presigner.getSignedUrl)(this.s3Client, command, {
989
1129
  expiresIn: expiresInSeconds
990
1130
  // is using 3600 it's will expire in 1 hour (default is 900 seconds = 15 minutes)
@@ -999,7 +1139,7 @@ var S3BucketUtil = class _S3BucketUtil {
999
1139
  const normalizedKey = getNormalizedPath(filePath);
1000
1140
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1001
1141
  try {
1002
- const command = new import_client_s3.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1142
+ const command = new import_client_s34.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1003
1143
  const headObject = yield this.execute(command);
1004
1144
  const bytes2 = (_a2 = headObject.ContentLength) != null ? _a2 : 0;
1005
1145
  switch (unit) {
@@ -1021,13 +1161,14 @@ var S3BucketUtil = class _S3BucketUtil {
1021
1161
  }
1022
1162
  });
1023
1163
  }
1164
+ // todo: checked!
1024
1165
  fileExists(filePath) {
1025
1166
  return __async(this, null, function* () {
1026
1167
  var _a2;
1027
1168
  try {
1028
1169
  const normalizedKey = getNormalizedPath(filePath);
1029
1170
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1030
- const command = new import_client_s3.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1171
+ const command = new import_client_s34.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1031
1172
  yield this.execute(command);
1032
1173
  return true;
1033
1174
  } catch (error) {
@@ -1038,11 +1179,12 @@ var S3BucketUtil = class _S3BucketUtil {
1038
1179
  }
1039
1180
  });
1040
1181
  }
1182
+ // todo: checked!
1041
1183
  fileContent(filePath, format = "buffer") {
1042
1184
  return __async(this, null, function* () {
1043
- const normalizedKey = getNormalizedPath(filePath);
1185
+ let normalizedKey = getNormalizedPath(filePath);
1044
1186
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1045
- const command = new import_client_s3.GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1187
+ const command = new import_client_s34.GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1046
1188
  const result = yield this.execute(command);
1047
1189
  if (!result.Body) {
1048
1190
  throw new Error("File body is empty");
@@ -1071,6 +1213,7 @@ var S3BucketUtil = class _S3BucketUtil {
1071
1213
  return buffer;
1072
1214
  });
1073
1215
  }
1216
+ // todo: checked!
1074
1217
  uploadFile(_0, _1) {
1075
1218
  return __async(this, arguments, function* (filePath, fileData, acl = "private" /* private */, version = "1.0.0") {
1076
1219
  const normalizedKey = getNormalizedPath(filePath);
@@ -1094,38 +1237,130 @@ var S3BucketUtil = class _S3BucketUtil {
1094
1237
  };
1095
1238
  });
1096
1239
  }
1240
+ // todo: checked!
1097
1241
  deleteFile(filePath) {
1098
1242
  return __async(this, null, function* () {
1099
1243
  const normalizedKey = getNormalizedPath(filePath);
1100
1244
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1101
- const command = new import_client_s3.DeleteObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1245
+ const command = new import_client_s34.DeleteObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1102
1246
  return yield this.execute(command);
1103
1247
  });
1104
1248
  }
1105
- // ##### STREAMING BLOCK ##########################
1249
+ };
1250
+
1251
+ // src/aws/s3/s3-stream.ts
1252
+ var pump = (0, import_util.promisify)(import_stream.pipeline);
1253
+ var S3Stream = class _S3Stream extends S3File {
1254
+ constructor(_a2) {
1255
+ var _b = _a2, { maxUploadFileSizeRestriction = "10GB" } = _b, props = __objRest(_b, ["maxUploadFileSizeRestriction"]);
1256
+ super(props);
1257
+ __publicField(this, "maxUploadFileSizeRestriction");
1258
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1259
+ __publicField(this, "getImageFileViewCtrl", ({
1260
+ fileKey: _fileKey,
1261
+ queryField = "file",
1262
+ cachingAge = 31536e3
1263
+ } = {}) => {
1264
+ return (req, res, next) => __async(this, null, function* () {
1265
+ var _a2, _b, _c, _d, _e;
1266
+ let fileKey = _fileKey || (((_a2 = req.query) == null ? void 0 : _a2[queryField]) ? decodeURIComponent((_b = req.query) == null ? void 0 : _b[queryField]) : void 0);
1267
+ if (!fileKey || fileKey === "/") {
1268
+ (_d = this.logger) == null ? void 0 : _d.warn(req.id, "image file view required file query field", {
1269
+ fileKey: (_c = req.query) == null ? void 0 : _c[queryField],
1270
+ queryField
1271
+ });
1272
+ next("image file key is required");
1273
+ return;
1274
+ }
1275
+ try {
1276
+ const imageBuffer = yield this.fileContent(fileKey, "buffer");
1277
+ const ext = import_pathe.default.extname(fileKey).slice(1).toLowerCase();
1278
+ const mimeTypeMap = {
1279
+ jpg: "image/jpeg",
1280
+ jpeg: "image/jpeg",
1281
+ png: "image/png",
1282
+ gif: "image/gif",
1283
+ webp: "image/webp",
1284
+ svg: "image/svg+xml",
1285
+ ico: "image/x-icon"
1286
+ };
1287
+ const contentType = mimeTypeMap[ext] || "application/octet-stream";
1288
+ res.setHeader("Content-Type", contentType);
1289
+ if (cachingAge) res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1290
+ res.setHeader("Content-Length", imageBuffer.length);
1291
+ res.status(200).send(imageBuffer);
1292
+ } catch (error) {
1293
+ (_e = this.logger) == null ? void 0 : _e.warn(req.id, "image view fileKey not found", {
1294
+ fileKey,
1295
+ localstack: this.localstack
1296
+ });
1297
+ next(`Failed to retrieve image file: ${error.message}`);
1298
+ }
1299
+ });
1300
+ });
1301
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1302
+ __publicField(this, "getPdfFileViewCtrl", ({
1303
+ fileKey: _fileKey,
1304
+ queryField = "file",
1305
+ cachingAge = 31536e3
1306
+ } = {}) => {
1307
+ return (req, res, next) => __async(this, null, function* () {
1308
+ var _a2, _b;
1309
+ let fileKey = _fileKey || (((_a2 = req.query) == null ? void 0 : _a2[queryField]) ? decodeURIComponent((_b = req.query) == null ? void 0 : _b[queryField]) : void 0);
1310
+ if (!fileKey) {
1311
+ next("pdf file key is required");
1312
+ return;
1313
+ }
1314
+ try {
1315
+ const fileBuffer = yield this.fileContent(fileKey, "buffer");
1316
+ const ext = import_pathe.default.extname(fileKey).slice(1).toLowerCase();
1317
+ const mimeTypeMap = {
1318
+ pdf: "application/pdf",
1319
+ txt: "text/plain",
1320
+ doc: "application/msword",
1321
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1322
+ xls: "application/vnd.ms-excel",
1323
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1324
+ ppt: "application/vnd.ms-powerpoint",
1325
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1326
+ };
1327
+ const contentType = mimeTypeMap[ext] || "application/octet-stream";
1328
+ res.setHeader("Content-Type", contentType);
1329
+ res.setHeader("Content-Disposition", `inline; filename="${import_pathe.default.basename(fileKey)}"`);
1330
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1331
+ res.setHeader("Content-Length", fileBuffer.length);
1332
+ res.status(200).send(fileBuffer);
1333
+ } catch (error) {
1334
+ next(`Failed to retrieve pdf file: ${error.message}`);
1335
+ }
1336
+ });
1337
+ });
1338
+ this.maxUploadFileSizeRestriction = maxUploadFileSizeRestriction;
1339
+ }
1106
1340
  streamObjectFile(_0) {
1107
1341
  return __async(this, arguments, function* (filePath, {
1108
1342
  Range,
1109
1343
  checkFileExists = true,
1110
1344
  abortSignal
1111
1345
  } = {}) {
1112
- const normalizedKey = getNormalizedPath(filePath);
1346
+ let normalizedKey = getNormalizedPath(filePath);
1113
1347
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1114
1348
  if (checkFileExists) {
1115
1349
  const isExists = yield this.fileExists(normalizedKey);
1116
1350
  if (!isExists) return null;
1117
1351
  }
1118
- const command = new import_client_s3.GetObjectCommand(__spreadValues({
1352
+ const command = new import_client_s35.GetObjectCommand(__spreadValues({
1119
1353
  Bucket: this.bucket,
1120
1354
  Key: normalizedKey
1121
1355
  }, Range ? { Range } : {}));
1122
1356
  const response = yield this.execute(command, { abortSignal });
1123
- if (!response.Body || !(response.Body instanceof import_node_stream.Readable)) {
1357
+ if (!response.Body || !(response.Body instanceof import_node_stream2.Readable)) {
1124
1358
  throw new Error("Invalid response body: not a Readable stream");
1125
1359
  }
1126
1360
  return response.Body;
1127
1361
  });
1128
1362
  }
1363
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1129
1364
  streamVideoFile(_0) {
1130
1365
  return __async(this, arguments, function* ({
1131
1366
  filePath,
@@ -1133,10 +1368,10 @@ var S3BucketUtil = class _S3BucketUtil {
1133
1368
  abortSignal
1134
1369
  }) {
1135
1370
  var _a2;
1136
- const normalizedKey = getNormalizedPath(filePath);
1371
+ let normalizedKey = getNormalizedPath(filePath);
1137
1372
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1138
1373
  try {
1139
- const cmd = new import_client_s3.GetObjectCommand(__spreadValues({
1374
+ const cmd = new import_client_s35.GetObjectCommand(__spreadValues({
1140
1375
  Bucket: this.bucket,
1141
1376
  Key: normalizedKey
1142
1377
  }, Range ? { Range } : {}));
@@ -1165,143 +1400,121 @@ var S3BucketUtil = class _S3BucketUtil {
1165
1400
  }
1166
1401
  });
1167
1402
  }
1168
- getStreamZipFileCtr(_0) {
1403
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1404
+ getStreamVideoFileCtrl(_0) {
1169
1405
  return __async(this, arguments, function* ({
1170
- filePath,
1171
- filename: _filename,
1172
- compressionLevel = 5
1406
+ fileKey,
1407
+ allowedWhitelist,
1408
+ contentType = "video/mp4",
1409
+ streamTimeoutMS = 3e4,
1410
+ bufferMB = 5
1173
1411
  }) {
1174
1412
  return (req, res, next) => __async(this, null, function* () {
1175
- var _a2, _b, _c, _d, _e;
1176
- const filePaths = [].concat(filePath).map((filePath2) => getNormalizedPath(filePath2)).filter((v) => v && v !== "/");
1177
- if (!filePaths.length) {
1178
- throw new Error("No file keys provided");
1413
+ var _a2, _b, _c, _d, _e, _f;
1414
+ let normalizedKey = getNormalizedPath(fileKey);
1415
+ if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1416
+ const isExists = yield this.fileExists(normalizedKey);
1417
+ const fileSize = yield this.sizeOf(normalizedKey);
1418
+ let Range;
1419
+ if (!isExists) {
1420
+ next(Error(`File does not exist: "${normalizedKey}"`));
1421
+ return;
1179
1422
  }
1180
- let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1181
- filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1182
- const abort = new AbortController();
1183
- const onClose = () => {
1184
- abort.abort();
1185
- };
1186
- req.once("close", onClose);
1187
1423
  try {
1188
- (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: filePaths.length });
1189
- const downloadPromises = filePaths.map((filePath2) => __async(this, null, function* () {
1190
- var _a3, _b2, _c2;
1191
- try {
1192
- if (abort.signal.aborted) return null;
1193
- const stream = yield this.streamObjectFile(filePath2, { abortSignal: abort.signal });
1194
- if (!stream) {
1195
- (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { filePath: filePath2 });
1196
- return null;
1197
- }
1198
- const chunks = [];
1199
- try {
1200
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
1201
- const chunk = temp.value;
1202
- if (abort.signal.aborted) {
1203
- stream.destroy();
1204
- return null;
1205
- }
1206
- chunks.push(import_buffer.Buffer.from(chunk));
1207
- }
1208
- } catch (temp) {
1209
- error = [temp];
1210
- } finally {
1211
- try {
1212
- more && (temp = iter.return) && (yield temp.call(iter));
1213
- } finally {
1214
- if (error)
1215
- throw error[0];
1216
- }
1217
- }
1218
- const buffer = import_buffer.Buffer.concat(chunks);
1219
- const fileName = filePath2.split("/").pop() || filePath2;
1220
- (_b2 = this.logger) == null ? void 0 : _b2.debug(this.reqId, "File downloaded", {
1221
- filePath: filePath2,
1222
- sizeMB: (buffer.length / (1024 * 1024)).toFixed(2)
1223
- });
1224
- return { buffer, name: fileName, path: filePath2 };
1225
- } catch (error2) {
1226
- (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { filePath: filePath2, error: error2 });
1227
- return null;
1228
- }
1229
- }));
1230
- const fileBuffers = (yield Promise.all(downloadPromises)).filter(Boolean);
1231
- if (abort.signal.aborted || fileBuffers.length === 0) {
1232
- req.off("close", onClose);
1233
- if (fileBuffers.length === 0) {
1234
- next(new Error("No files available to zip"));
1235
- }
1236
- return;
1237
- }
1238
- (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "All files downloaded, measuring zip size...", {
1239
- fileCount: fileBuffers.length,
1240
- totalSizeMB: (fileBuffers.reduce((sum, f) => sum + f.buffer.length, 0) / (1024 * 1024)).toFixed(2)
1241
- });
1242
- const measureArchive = (0, import_archiver.default)("zip", { zlib: { level: compressionLevel } });
1243
- let actualZipSize = 0;
1244
- measureArchive.on("data", (chunk) => {
1245
- actualZipSize += chunk.length;
1246
- });
1247
- for (const file of fileBuffers) {
1248
- if (abort.signal.aborted) break;
1249
- measureArchive.append(file.buffer, { name: file.name });
1424
+ if (req.method === "HEAD") {
1425
+ res.setHeader("Content-Type", contentType);
1426
+ res.setHeader("Accept-Ranges", "bytes");
1427
+ if (fileSize) res.setHeader("Content-Length", String(fileSize));
1428
+ return res.status(200).end();
1250
1429
  }
1251
- yield measureArchive.finalize();
1252
- if (abort.signal.aborted) {
1253
- req.off("close", onClose);
1430
+ const bufferSize = bufferMB;
1431
+ const CHUNK_SIZE = __pow(10, 6) * bufferSize;
1432
+ const rangeValues = parseRangeHeader(req.headers.range, fileSize, CHUNK_SIZE);
1433
+ let [start, end] = rangeValues || [];
1434
+ if (!rangeValues || start < 0 || start >= fileSize || end < 0 || end >= fileSize || start > end) {
1435
+ res.status(416).send("Requested Range Not Satisfiable");
1254
1436
  return;
1255
1437
  }
1256
- (_c = this.logger) == null ? void 0 : _c.info(this.reqId, "Zip size calculated", {
1257
- actualZipSize,
1258
- sizeMB: (actualZipSize / (1024 * 1024)).toFixed(2)
1259
- });
1260
- const actualArchive = (0, import_archiver.default)("zip", { zlib: { level: compressionLevel } });
1261
- res.setHeader("Content-Type", "application/zip");
1262
- res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
1263
- res.setHeader("Content-Length", String(actualZipSize));
1264
- res.setHeader("Access-Control-Expose-Headers", "Content-Type, Content-Disposition, Content-Length");
1265
- actualArchive.on("error", (err) => {
1266
- var _a3;
1267
- (_a3 = this.logger) == null ? void 0 : _a3.error(this.reqId, "Archive error", { error: err });
1268
- abort.abort();
1269
- if (!res.headersSent) {
1270
- next(err);
1271
- }
1438
+ res.statusCode = 206;
1439
+ const chunkLength = end - start + 1;
1440
+ res.setHeader("Content-Length", chunkLength);
1441
+ res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`);
1442
+ res.setHeader("Accept-Ranges", "bytes");
1443
+ res.setHeader("Content-Type", "video/mp4");
1444
+ Range = `bytes=${start}-${end}`;
1445
+ } catch (error) {
1446
+ next(error);
1447
+ return;
1448
+ }
1449
+ const abort = new AbortController();
1450
+ const onClose = () => abort.abort();
1451
+ req.once("close", onClose);
1452
+ try {
1453
+ const result = yield this.streamVideoFile({
1454
+ filePath: normalizedKey,
1455
+ Range,
1456
+ abortSignal: abort.signal
1272
1457
  });
1273
- actualArchive.pipe(res);
1274
- for (const file of fileBuffers) {
1275
- if (abort.signal.aborted) break;
1276
- actualArchive.append(file.buffer, { name: file.name });
1458
+ const { body, meta } = result;
1459
+ const origin = Array.isArray(allowedWhitelist) ? allowedWhitelist.includes((_a2 = req.headers.origin) != null ? _a2 : "") ? req.headers.origin : void 0 : allowedWhitelist;
1460
+ if (origin) {
1461
+ res.setHeader("Access-Control-Allow-Origin", origin);
1462
+ res.setHeader("Vary", "Origin");
1277
1463
  }
1278
- yield actualArchive.finalize();
1279
- (_d = this.logger) == null ? void 0 : _d.info(this.reqId, "Zip download completed", {
1280
- fileCount: fileBuffers.length,
1281
- totalSize: actualZipSize
1464
+ const finalContentType = contentType.startsWith("video/") ? contentType : `video/${contentType}`;
1465
+ res.setHeader("Content-Type", (_b = meta.contentType) != null ? _b : finalContentType);
1466
+ res.setHeader("Accept-Ranges", (_c = meta.acceptRanges) != null ? _c : "bytes");
1467
+ if (Range && meta.contentRange) {
1468
+ res.status(206);
1469
+ res.setHeader("Content-Range", meta.contentRange);
1470
+ if (typeof meta.contentLength === "number") {
1471
+ res.setHeader("Content-Length", String(meta.contentLength));
1472
+ }
1473
+ } else if (fileSize) {
1474
+ res.setHeader("Content-Length", String(fileSize));
1475
+ }
1476
+ if (meta.etag) res.setHeader("ETag", meta.etag);
1477
+ if (meta.lastModified) res.setHeader("Last-Modified", meta.lastModified.toUTCString());
1478
+ const timeout = setTimeout(() => {
1479
+ abort.abort();
1480
+ if (!res.headersSent) res.status(504);
1481
+ res.end();
1482
+ }, streamTimeoutMS);
1483
+ res.once("close", () => {
1484
+ var _a3;
1485
+ clearTimeout(timeout);
1486
+ (_a3 = body.destroy) == null ? void 0 : _a3.call(body);
1487
+ req.off("close", onClose);
1282
1488
  });
1283
- req.off("close", onClose);
1489
+ yield pump(body, res);
1490
+ clearTimeout(timeout);
1284
1491
  } catch (error) {
1285
- abort.abort();
1286
- const isBenignError = (error == null ? void 0 : error.code) === "ERR_STREAM_PREMATURE_CLOSE" || (error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.code) === "ECONNRESET";
1287
- if (isBenignError) {
1492
+ const isBenignStreamError = (error == null ? void 0 : error.code) === "ERR_STREAM_PREMATURE_CLOSE" || (error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.code) === "ECONNRESET";
1493
+ if (isBenignStreamError) {
1288
1494
  return;
1289
1495
  }
1290
1496
  if (!res.headersSent) {
1291
- (_e = this.logger) == null ? void 0 : _e.error(this.reqId, "Failed to create zip archive", { error });
1497
+ (_f = this.logger) == null ? void 0 : _f.warn(req.id, "caught exception in stream controller", {
1498
+ error: (_d = error == null ? void 0 : error.message) != null ? _d : String(error),
1499
+ key: fileKey,
1500
+ url: req.originalUrl,
1501
+ userId: (_e = req.user) == null ? void 0 : _e._id
1502
+ });
1292
1503
  next(error);
1293
- } else if (!res.writableEnded) {
1504
+ return;
1505
+ }
1506
+ if (!res.writableEnded) {
1294
1507
  try {
1295
1508
  res.end();
1296
1509
  } catch (e) {
1297
1510
  }
1298
1511
  }
1299
- } finally {
1300
- req.off("close", onClose);
1512
+ return;
1301
1513
  }
1302
1514
  });
1303
1515
  });
1304
1516
  }
1517
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1305
1518
  getStreamFileCtrl(_0) {
1306
1519
  return __async(this, arguments, function* ({ filePath, filename }) {
1307
1520
  return (req, res, next) => __async(this, null, function* () {
@@ -1314,7 +1527,7 @@ var S3BucketUtil = class _S3BucketUtil {
1314
1527
  (_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
1315
1528
  };
1316
1529
  req.once("close", onClose);
1317
- const normalizedKey = getNormalizedPath(filePath);
1530
+ let normalizedKey = getNormalizedPath(filePath);
1318
1531
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1319
1532
  try {
1320
1533
  const isExists = yield this.fileExists(normalizedKey);
@@ -1376,115 +1589,140 @@ var S3BucketUtil = class _S3BucketUtil {
1376
1589
  });
1377
1590
  });
1378
1591
  }
1379
- getStreamVideoFileCtrl(_0) {
1592
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1593
+ getStreamZipFileCtr(_0) {
1380
1594
  return __async(this, arguments, function* ({
1381
- fileKey,
1382
- allowedWhitelist,
1383
- contentType = "video/mp4",
1384
- streamTimeoutMS = 3e4,
1385
- bufferMB = 5
1595
+ filePath,
1596
+ filename: _filename,
1597
+ compressionLevel = 5
1386
1598
  }) {
1387
1599
  return (req, res, next) => __async(this, null, function* () {
1388
- var _a2, _b, _c, _d, _e, _f;
1389
- const normalizedKey = getNormalizedPath(fileKey);
1390
- if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1391
- const isExists = yield this.fileExists(normalizedKey);
1392
- const fileSize = yield this.sizeOf(normalizedKey);
1393
- let Range;
1394
- if (!isExists) {
1395
- next(Error(`File does not exist: "${normalizedKey}"`));
1396
- return;
1397
- }
1398
- try {
1399
- if (req.method === "HEAD") {
1400
- res.setHeader("Content-Type", contentType);
1401
- res.setHeader("Accept-Ranges", "bytes");
1402
- if (fileSize) res.setHeader("Content-Length", String(fileSize));
1403
- return res.status(200).end();
1404
- }
1405
- const bufferSize = bufferMB;
1406
- const CHUNK_SIZE = __pow(10, 6) * bufferSize;
1407
- const rangeValues = parseRangeHeader(req.headers.range, fileSize, CHUNK_SIZE);
1408
- let [start, end] = rangeValues || [];
1409
- if (!rangeValues || start < 0 || start >= fileSize || end < 0 || end >= fileSize || start > end) {
1410
- res.status(416).send("Requested Range Not Satisfiable");
1411
- return;
1412
- }
1413
- res.statusCode = 206;
1414
- const chunkLength = end - start + 1;
1415
- res.setHeader("Content-Length", chunkLength);
1416
- res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`);
1417
- res.setHeader("Accept-Ranges", "bytes");
1418
- res.setHeader("Content-Type", "video/mp4");
1419
- Range = `bytes=${start}-${end}`;
1420
- } catch (error) {
1421
- next(error);
1422
- return;
1600
+ var _a2, _b, _c, _d, _e;
1601
+ const filePaths = [].concat(filePath).map((filePath2) => getNormalizedPath(filePath2)).filter((v) => v && v !== "/");
1602
+ if (!filePaths.length) {
1603
+ throw new Error("No file keys provided");
1423
1604
  }
1605
+ let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1606
+ filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1424
1607
  const abort = new AbortController();
1425
- const onClose = () => abort.abort();
1608
+ const onClose = () => {
1609
+ abort.abort();
1610
+ };
1426
1611
  req.once("close", onClose);
1427
1612
  try {
1428
- const result = yield this.streamVideoFile({
1429
- filePath: normalizedKey,
1430
- Range,
1431
- abortSignal: abort.signal
1613
+ (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: filePaths.length });
1614
+ const downloadPromises = filePaths.map((filePath2) => __async(this, null, function* () {
1615
+ var _a3, _b2, _c2;
1616
+ try {
1617
+ if (abort.signal.aborted) return null;
1618
+ const stream = yield this.streamObjectFile(filePath2, { abortSignal: abort.signal });
1619
+ if (!stream) {
1620
+ (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { filePath: filePath2 });
1621
+ return null;
1622
+ }
1623
+ const chunks = [];
1624
+ try {
1625
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
1626
+ const chunk = temp.value;
1627
+ if (abort.signal.aborted) {
1628
+ stream.destroy();
1629
+ return null;
1630
+ }
1631
+ chunks.push(import_buffer2.Buffer.from(chunk));
1632
+ }
1633
+ } catch (temp) {
1634
+ error = [temp];
1635
+ } finally {
1636
+ try {
1637
+ more && (temp = iter.return) && (yield temp.call(iter));
1638
+ } finally {
1639
+ if (error)
1640
+ throw error[0];
1641
+ }
1642
+ }
1643
+ const buffer = import_buffer2.Buffer.concat(chunks);
1644
+ const fileName = filePath2.split("/").pop() || filePath2;
1645
+ (_b2 = this.logger) == null ? void 0 : _b2.debug(this.reqId, "File downloaded", {
1646
+ filePath: filePath2,
1647
+ sizeMB: (buffer.length / (1024 * 1024)).toFixed(2)
1648
+ });
1649
+ return { buffer, name: fileName, path: filePath2 };
1650
+ } catch (error2) {
1651
+ (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { filePath: filePath2, error: error2 });
1652
+ return null;
1653
+ }
1654
+ }));
1655
+ const fileBuffers = (yield Promise.all(downloadPromises)).filter(Boolean);
1656
+ if (abort.signal.aborted || fileBuffers.length === 0) {
1657
+ req.off("close", onClose);
1658
+ if (fileBuffers.length === 0) {
1659
+ next(new Error("No files available to zip"));
1660
+ }
1661
+ return;
1662
+ }
1663
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "All files downloaded, measuring zip size...", {
1664
+ fileCount: fileBuffers.length,
1665
+ totalSizeMB: (fileBuffers.reduce((sum, f) => sum + f.buffer.length, 0) / (1024 * 1024)).toFixed(2)
1432
1666
  });
1433
- const { body, meta } = result;
1434
- const origin = Array.isArray(allowedWhitelist) ? allowedWhitelist.includes((_a2 = req.headers.origin) != null ? _a2 : "") ? req.headers.origin : void 0 : allowedWhitelist;
1435
- if (origin) {
1436
- res.setHeader("Access-Control-Allow-Origin", origin);
1437
- res.setHeader("Vary", "Origin");
1667
+ const measureArchive = (0, import_archiver.default)("zip", { zlib: { level: compressionLevel } });
1668
+ let actualZipSize = 0;
1669
+ measureArchive.on("data", (chunk) => {
1670
+ actualZipSize += chunk.length;
1671
+ });
1672
+ for (const file of fileBuffers) {
1673
+ if (abort.signal.aborted) break;
1674
+ measureArchive.append(file.buffer, { name: file.name });
1438
1675
  }
1439
- const finalContentType = contentType.startsWith("video/") ? contentType : `video/${contentType}`;
1440
- res.setHeader("Content-Type", (_b = meta.contentType) != null ? _b : finalContentType);
1441
- res.setHeader("Accept-Ranges", (_c = meta.acceptRanges) != null ? _c : "bytes");
1442
- if (Range && meta.contentRange) {
1443
- res.status(206);
1444
- res.setHeader("Content-Range", meta.contentRange);
1445
- if (typeof meta.contentLength === "number") {
1446
- res.setHeader("Content-Length", String(meta.contentLength));
1447
- }
1448
- } else if (fileSize) {
1449
- res.setHeader("Content-Length", String(fileSize));
1676
+ yield measureArchive.finalize();
1677
+ if (abort.signal.aborted) {
1678
+ req.off("close", onClose);
1679
+ return;
1450
1680
  }
1451
- if (meta.etag) res.setHeader("ETag", meta.etag);
1452
- if (meta.lastModified) res.setHeader("Last-Modified", meta.lastModified.toUTCString());
1453
- const timeout = setTimeout(() => {
1454
- abort.abort();
1455
- if (!res.headersSent) res.status(504);
1456
- res.end();
1457
- }, streamTimeoutMS);
1458
- res.once("close", () => {
1681
+ (_c = this.logger) == null ? void 0 : _c.info(this.reqId, "Zip size calculated", {
1682
+ actualZipSize,
1683
+ sizeMB: (actualZipSize / (1024 * 1024)).toFixed(2)
1684
+ });
1685
+ const actualArchive = (0, import_archiver.default)("zip", { zlib: { level: compressionLevel } });
1686
+ res.setHeader("Content-Type", "application/zip");
1687
+ res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
1688
+ res.setHeader("Content-Length", String(actualZipSize));
1689
+ res.setHeader("Access-Control-Expose-Headers", "Content-Type, Content-Disposition, Content-Length");
1690
+ actualArchive.on("error", (err) => {
1459
1691
  var _a3;
1460
- clearTimeout(timeout);
1461
- (_a3 = body.destroy) == null ? void 0 : _a3.call(body);
1462
- req.off("close", onClose);
1692
+ (_a3 = this.logger) == null ? void 0 : _a3.error(this.reqId, "Archive error", { error: err });
1693
+ abort.abort();
1694
+ if (!res.headersSent) {
1695
+ next(err);
1696
+ }
1463
1697
  });
1464
- yield pump(body, res);
1465
- clearTimeout(timeout);
1698
+ actualArchive.pipe(res);
1699
+ for (const file of fileBuffers) {
1700
+ if (abort.signal.aborted) break;
1701
+ actualArchive.append(file.buffer, { name: file.name });
1702
+ }
1703
+ yield actualArchive.finalize();
1704
+ (_d = this.logger) == null ? void 0 : _d.info(this.reqId, "Zip download completed", {
1705
+ fileCount: fileBuffers.length,
1706
+ totalSize: actualZipSize
1707
+ });
1708
+ req.off("close", onClose);
1466
1709
  } catch (error) {
1467
- const isBenignStreamError = (error == null ? void 0 : error.code) === "ERR_STREAM_PREMATURE_CLOSE" || (error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.code) === "ECONNRESET";
1468
- if (isBenignStreamError) {
1710
+ abort.abort();
1711
+ const isBenignError = (error == null ? void 0 : error.code) === "ERR_STREAM_PREMATURE_CLOSE" || (error == null ? void 0 : error.name) === "AbortError" || (error == null ? void 0 : error.code) === "ECONNRESET";
1712
+ if (isBenignError) {
1469
1713
  return;
1470
1714
  }
1471
1715
  if (!res.headersSent) {
1472
- (_f = this.logger) == null ? void 0 : _f.warn(req.id, "caught exception in stream controller", {
1473
- error: (_d = error == null ? void 0 : error.message) != null ? _d : String(error),
1474
- key: fileKey,
1475
- url: req.originalUrl,
1476
- userId: (_e = req.user) == null ? void 0 : _e._id
1477
- });
1716
+ (_e = this.logger) == null ? void 0 : _e.error(this.reqId, "Failed to create zip archive", { error });
1478
1717
  next(error);
1479
- return;
1480
- }
1481
- if (!res.writableEnded) {
1718
+ } else if (!res.writableEnded) {
1482
1719
  try {
1483
1720
  res.end();
1484
1721
  } catch (e) {
1485
1722
  }
1486
1723
  }
1487
- return;
1724
+ } finally {
1725
+ req.off("close", onClose);
1488
1726
  }
1489
1727
  });
1490
1728
  });
@@ -1502,19 +1740,6 @@ var S3BucketUtil = class _S3BucketUtil {
1502
1740
  return cb(new Error(errorMsg));
1503
1741
  };
1504
1742
  }
1505
- getFileSize(maxFileSize) {
1506
- var _a2;
1507
- const fileSizeUnitValue = maxFileSize != null ? maxFileSize : this.maxUploadFileSizeRestriction;
1508
- const fileSize = typeof fileSizeUnitValue === "number" ? fileSizeUnitValue : (0, import_bytes.default)(fileSizeUnitValue);
1509
- if (!fileSize) {
1510
- (_a2 = this.logger) == null ? void 0 : _a2.warn(this.reqId, "Failed to convert fileSize restriction, proceeding without limit", {
1511
- maxFileSize,
1512
- maxUploadFileSizeRestriction: this.maxUploadFileSizeRestriction
1513
- });
1514
- return void 0;
1515
- }
1516
- return fileSize;
1517
- }
1518
1743
  getUploadFileMW(directoryPath, {
1519
1744
  acl = "private" /* private */,
1520
1745
  maxFileSize,
@@ -1526,10 +1751,10 @@ var S3BucketUtil = class _S3BucketUtil {
1526
1751
  let normalizedPath = getNormalizedPath(directoryPath);
1527
1752
  if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1528
1753
  else normalizedPath = "";
1529
- const fileSize = this.getFileSize(maxFileSize);
1754
+ const fileSize = getFileSize(maxFileSize, this.maxUploadFileSizeRestriction);
1530
1755
  const fileTypes = [].concat(fileType);
1531
1756
  const fileExts = [].concat(fileExt);
1532
- const fileFilter = (fileTypes == null ? void 0 : fileTypes.length) || (fileExts == null ? void 0 : fileExts.length) ? _S3BucketUtil.fileFilter(fileTypes, fileExts) : void 0;
1757
+ const fileFilter = (fileTypes == null ? void 0 : fileTypes.length) || (fileExts == null ? void 0 : fileExts.length) ? _S3Stream.fileFilter(fileTypes, fileExts) : void 0;
1533
1758
  return (0, import_multer.default)({
1534
1759
  fileFilter,
1535
1760
  limits: __spreadValues({}, fileSize && { fileSize }),
@@ -1566,14 +1791,19 @@ var S3BucketUtil = class _S3BucketUtil {
1566
1791
  * Middleware for uploading a single file
1567
1792
  * Adds the uploaded file info to req.s3File
1568
1793
  */
1569
- uploadSingleFile(fieldName, directory, options = {}) {
1570
- const upload = this.getUploadFileMW(directory, options);
1794
+ uploadSingleFile(fieldName, directoryPath, options = {}) {
1795
+ var _a2;
1796
+ let normalizedPath = getNormalizedPath(directoryPath);
1797
+ if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1798
+ else normalizedPath = "";
1799
+ (_a2 = this.logger) == null ? void 0 : _a2.debug(null, "####### uploadSingleFile", { directoryPath, normalizedPath, fieldName });
1800
+ const upload = this.getUploadFileMW(normalizedPath, options);
1571
1801
  return (req, res, next) => {
1572
1802
  const mw = upload.single(fieldName);
1573
1803
  mw(req, res, (err) => {
1574
- var _a2, _b;
1804
+ var _a3, _b;
1575
1805
  if (err) {
1576
- (_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Single file upload error", { fieldName, error: err.message });
1806
+ (_a3 = this.logger) == null ? void 0 : _a3.error(this.reqId, "Single file upload error", { fieldName, error: err.message });
1577
1807
  return next(err);
1578
1808
  }
1579
1809
  if (req.file) {
@@ -1593,8 +1823,11 @@ var S3BucketUtil = class _S3BucketUtil {
1593
1823
  * Middleware for uploading multiple files with the same field name
1594
1824
  * Adds the uploaded files info to req.s3Files
1595
1825
  */
1596
- uploadMultipleFiles(fieldName, directory, options = {}) {
1597
- const upload = this.getUploadFileMW(directory, options);
1826
+ uploadMultipleFiles(fieldName, directoryPath, options = {}) {
1827
+ let normalizedPath = getNormalizedPath(directoryPath);
1828
+ if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1829
+ else normalizedPath = "";
1830
+ const upload = this.getUploadFileMW(normalizedPath, options);
1598
1831
  return (req, res, next) => {
1599
1832
  const mw = upload.array(fieldName, options.maxFilesCount || void 0);
1600
1833
  mw(req, res, (err) => {
@@ -1615,49 +1848,15 @@ var S3BucketUtil = class _S3BucketUtil {
1615
1848
  });
1616
1849
  };
1617
1850
  }
1618
- /**
1619
- * Middleware for uploading multiple files with different field names
1620
- * Adds the uploaded files info to req.s3FilesByField
1621
- */
1622
- uploadFieldsFiles(fields) {
1623
- const fieldConfigs = fields.map((field) => {
1624
- const upload = this.getUploadFileMW(field.directory, field.options || {});
1625
- return {
1626
- name: field.name,
1627
- maxCount: field.maxCount || 1,
1628
- upload,
1629
- directory: field.directory
1630
- };
1631
- });
1632
- return (req, res, next) => __async(this, null, function* () {
1633
- const multerFields = fieldConfigs.map((f) => ({ name: f.name, maxCount: f.maxCount }));
1634
- const upload = this.getUploadFileMW(fieldConfigs[0].directory);
1635
- const mw = upload.fields(multerFields);
1636
- mw(req, res, (err) => {
1637
- var _a2, _b;
1638
- if (err) {
1639
- (_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Fields upload error", { error: err.message });
1640
- return next(err);
1641
- }
1642
- if (req.files && typeof req.files === "object" && !Array.isArray(req.files)) {
1643
- req.s3FilesByField = req.files;
1644
- const uploadSummary = Object.entries(req.s3FilesByField).map(([field, files]) => ({
1645
- field,
1646
- count: files.length,
1647
- keys: files.map((f) => f.key)
1648
- }));
1649
- (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Fields uploaded successfully", { uploadSummary });
1650
- }
1651
- next();
1652
- });
1653
- });
1654
- }
1655
1851
  /**
1656
1852
  * Middleware for uploading any files (mixed field names)
1657
1853
  * Adds the uploaded files info to req.s3AllFiles
1658
1854
  */
1659
- uploadAnyFiles(directory, maxCount, options = {}) {
1660
- const upload = this.getUploadFileMW(directory, options);
1855
+ uploadAnyFiles(directoryPath, maxCount, options = {}) {
1856
+ let normalizedPath = getNormalizedPath(directoryPath);
1857
+ if (normalizedPath !== "/" && normalizedPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1858
+ else normalizedPath = "";
1859
+ const upload = this.getUploadFileMW(normalizedPath, options);
1661
1860
  return (req, res, next) => {
1662
1861
  const anyUpload = maxCount ? upload.any() : upload.any();
1663
1862
  anyUpload(req, res, (err) => {
@@ -1680,6 +1879,159 @@ var S3BucketUtil = class _S3BucketUtil {
1680
1879
  });
1681
1880
  };
1682
1881
  }
1882
+ /**
1883
+ * Middleware for uploading multiple files with different field names
1884
+ * Adds the uploaded files info to req.s3FilesByField
1885
+ */
1886
+ // uploadFieldsFiles(
1887
+ // fields: Array<{ name: string; directory: string; maxCount?: number; options?: S3UploadOptions }>
1888
+ // ): RequestHandler {
1889
+ // // Create separate multer instances for each field (since each might have different options)
1890
+ // const fieldConfigs = fields.map((field) => {
1891
+ // const upload = this.getUploadFileMW(field.directory, field.options || {});
1892
+ //
1893
+ // return {
1894
+ // name: getNormalizedPath(field.name),
1895
+ // directory: getNormalizedPath(field.directory),
1896
+ // maxCount: field.maxCount || 1,
1897
+ // upload,
1898
+ // };
1899
+ // });
1900
+ //
1901
+ // return async (
1902
+ // req: Request & { s3FilesByField?: Record<string, UploadedS3File[]> } & any,
1903
+ // res: Response,
1904
+ // next: NextFunction & any
1905
+ // ) => {
1906
+ // // We'll use the first upload instance but with fields configuration
1907
+ // const multerFields = fieldConfigs.map((f) => ({ name: f.name, maxCount: f.maxCount }));
1908
+ // const upload = this.getUploadFileMW(fieldConfigs[0].directory);
1909
+ //
1910
+ // const mw: RequestHandler & any = upload.fields(multerFields);
1911
+ // mw(req, res, (err: any) => {
1912
+ // if (err) {
1913
+ // this.logger?.error(this.reqId, 'Fields upload error', { error: err.message });
1914
+ // return next(err);
1915
+ // }
1916
+ //
1917
+ // if (req.files && typeof req.files === 'object' && !Array.isArray(req.files)) {
1918
+ // req.s3FilesByField = req.files as Record<string, UploadedS3File[]>;
1919
+ //
1920
+ // const uploadSummary = Object.entries(req.s3FilesByField).map(([field, files]: any) => ({
1921
+ // field,
1922
+ // count: files.length,
1923
+ // keys: files.map((f: any) => f.key),
1924
+ // }));
1925
+ //
1926
+ // this.logger?.info(this.reqId, 'Fields uploaded successfully', { uploadSummary });
1927
+ // }
1928
+ //
1929
+ // next();
1930
+ // });
1931
+ // };
1932
+ // }
1933
+ };
1934
+
1935
+ // src/aws/s3/s3-util.ts
1936
+ var S3Util = class extends S3Stream {
1937
+ constructor(props) {
1938
+ super(props);
1939
+ }
1940
+ };
1941
+
1942
+ // src/aws/s3/s3-util.localstack.ts
1943
+ var S3LocalstackUtil = class extends S3Util {
1944
+ constructor(props) {
1945
+ super(__spreadProps(__spreadValues({}, props), { localstack: true }));
1946
+ }
1947
+ // todo: checked!
1948
+ directoryList(directoryPath) {
1949
+ return __async(this, null, function* () {
1950
+ var _a2;
1951
+ let normalizedPath = getNormalizedPath(directoryPath);
1952
+ if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1953
+ else normalizedPath = "";
1954
+ let result;
1955
+ result = yield this.execute(
1956
+ new import_client_s36.ListObjectsV2Command({
1957
+ Bucket: this.bucket,
1958
+ Prefix: normalizedPath,
1959
+ Delimiter: "/"
1960
+ })
1961
+ );
1962
+ (_a2 = this.logger) == null ? void 0 : _a2.debug(null, "#### directoryList", {
1963
+ normalizedPath,
1964
+ CommonPrefixes: result.CommonPrefixes,
1965
+ ContentFile: result.Contents
1966
+ });
1967
+ const directories = (result.CommonPrefixes || []).map((prefix) => prefix.Prefix).map((prefix) => {
1968
+ const relativePath = prefix.replace(normalizedPath, "");
1969
+ const dir = relativePath.replace(/\/$/, "");
1970
+ return dir;
1971
+ }).filter((dir) => dir);
1972
+ const files = (result.Contents || []).filter((content) => {
1973
+ var _a3;
1974
+ return content.Key !== normalizedPath && !((_a3 = content.Key) == null ? void 0 : _a3.endsWith("/"));
1975
+ }).map((content) => __spreadProps(__spreadValues({}, content), {
1976
+ Name: content.Key.replace(normalizedPath, "") || content.Key,
1977
+ Location: `${this.link}${content.Key.replace(/^\//, "")}`,
1978
+ LastModified: new Date(content.LastModified)
1979
+ }));
1980
+ return { directories, files };
1981
+ });
1982
+ }
1983
+ // todo: checked!
1984
+ directoryListPaginated(_0) {
1985
+ return __async(this, arguments, function* (directoryPath, {
1986
+ pageSize = 100,
1987
+ pageNumber = 0
1988
+ // 0-based: page 0 = items 0-99, page 1 = items 100-199, page 2 = items 200-299
1989
+ } = {}) {
1990
+ let normalizedPath = getNormalizedPath(directoryPath);
1991
+ if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1992
+ else normalizedPath = this.localstack ? "" : "/";
1993
+ let continuationToken = void 0;
1994
+ let currentPage = 0;
1995
+ let allDirectories = [];
1996
+ let allFiles = [];
1997
+ while (currentPage <= pageNumber) {
1998
+ let result;
1999
+ result = yield this.execute(
2000
+ new import_client_s36.ListObjectsV2Command({
2001
+ Bucket: this.bucket,
2002
+ Prefix: normalizedPath,
2003
+ Delimiter: "/",
2004
+ MaxKeys: pageSize,
2005
+ ContinuationToken: continuationToken
2006
+ })
2007
+ );
2008
+ if (currentPage === pageNumber) {
2009
+ allDirectories = (result.CommonPrefixes || []).map((prefix) => prefix.Prefix).map((prefix) => {
2010
+ const relativePath = prefix.replace(normalizedPath, "");
2011
+ return relativePath.replace(/\/$/, "");
2012
+ }).filter((dir) => dir);
2013
+ allFiles = (result.Contents || []).filter((content) => {
2014
+ var _a2;
2015
+ return content.Key !== normalizedPath && !((_a2 = content.Key) == null ? void 0 : _a2.endsWith("/"));
2016
+ }).map((content) => __spreadProps(__spreadValues({}, content), {
2017
+ Name: content.Key.replace(normalizedPath, "") || content.Key,
2018
+ Location: `${this.link}${content.Key.replace(/^\//, "")}`,
2019
+ LastModified: new Date(content.LastModified)
2020
+ }));
2021
+ }
2022
+ continuationToken = result.NextContinuationToken;
2023
+ if (!result.IsTruncated || !continuationToken) {
2024
+ break;
2025
+ }
2026
+ currentPage++;
2027
+ }
2028
+ return {
2029
+ directories: allDirectories,
2030
+ files: allFiles,
2031
+ totalFetched: allFiles.length + allDirectories.length
2032
+ };
2033
+ });
2034
+ }
1683
2035
  };
1684
2036
 
1685
2037
  // src/aws/sns.ts
@@ -1718,6 +2070,7 @@ var SNSUtil = class {
1718
2070
  AWSConfigSharingUtil,
1719
2071
  IAMUtil,
1720
2072
  LambdaUtil,
1721
- S3BucketUtil,
2073
+ S3LocalstackUtil,
2074
+ S3Util,
1722
2075
  SNSUtil
1723
2076
  });