@hdriel/aws-utils 1.0.4 → 1.1.2

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.js CHANGED
@@ -19,6 +19,18 @@ var __spreadValues = (a, b) => {
19
19
  return a;
20
20
  };
21
21
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
+ var __objRest = (source, exclude) => {
23
+ var target = {};
24
+ for (var prop in source)
25
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
26
+ target[prop] = source[prop];
27
+ if (source != null && __getOwnPropSymbols)
28
+ for (var prop of __getOwnPropSymbols(source)) {
29
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
30
+ target[prop] = source[prop];
31
+ }
32
+ return target;
33
+ };
22
34
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
23
35
  var __async = (__this, __arguments, generator) => {
24
36
  return new Promise((resolve, reject) => {
@@ -216,42 +228,19 @@ var LambdaUtil = class {
216
228
  }
217
229
  };
218
230
 
219
- // src/aws/s3-bucket.ts
220
- import ms from "ms";
231
+ // src/aws/s3/s3-util.localstack.ts
232
+ import { ListObjectsV2Command as ListObjectsV2Command4 } from "@aws-sdk/client-s3";
233
+
234
+ // src/aws/s3/s3-stream.ts
221
235
  import path from "pathe";
222
- import http from "http";
223
- import https from "https";
224
236
  import { pipeline } from "stream";
225
237
  import { promisify } from "util";
226
- import { Upload } from "@aws-sdk/lib-storage";
227
- import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
228
- import { NodeHttpHandler } from "@smithy/node-http-handler";
229
- import { Buffer as Buffer2 } from "buffer";
238
+ import { Buffer as Buffer3 } from "buffer";
230
239
  import archiver from "archiver";
231
- import { Readable } from "stream";
232
- import {
233
- CreateBucketCommand,
234
- GetObjectCommand,
235
- S3Client,
236
- DeleteBucketCommand,
237
- HeadBucketCommand,
238
- PutPublicAccessBlockCommand,
239
- PutBucketPolicyCommand,
240
- ListBucketsCommand,
241
- GetBucketPolicyCommand,
242
- GetBucketVersioningCommand,
243
- GetBucketEncryptionCommand,
244
- GetPublicAccessBlockCommand,
245
- GetBucketAclCommand,
246
- PutObjectCommand,
247
- HeadObjectCommand,
248
- ListObjectsCommand,
249
- PutObjectTaggingCommand,
250
- GetObjectTaggingCommand,
251
- DeleteObjectCommand,
252
- ListObjectsV2Command,
253
- DeleteObjectsCommand
254
- } from "@aws-sdk/client-s3";
240
+ import { Readable as Readable2 } from "stream";
241
+ import multerS3 from "multer-s3";
242
+ import multer from "multer";
243
+ import { GetObjectCommand as GetObjectCommand2 } from "@aws-sdk/client-s3";
255
244
 
256
245
  // src/utils/consts.ts
257
246
  var ACLs = /* @__PURE__ */ ((ACLs2) => {
@@ -265,11 +254,8 @@ var ACLs = /* @__PURE__ */ ((ACLs2) => {
265
254
  import pLimit from "p-limit";
266
255
  var s3Limiter = pLimit(4);
267
256
 
268
- // src/aws/s3-bucket.ts
269
- import multer from "multer";
270
- import multerS3 from "multer-s3";
257
+ // src/utils/helpers.ts
271
258
  import bytes from "bytes";
272
- var pump = promisify(pipeline);
273
259
  var parseRangeHeader = (range, contentLength, chunkSize) => {
274
260
  if (!range || !range.startsWith("bytes=")) return null;
275
261
  const rangeParts = range.replace("bytes=", "").split("-");
@@ -282,34 +268,88 @@ var parseRangeHeader = (range, contentLength, chunkSize) => {
282
268
  }
283
269
  return [start, Math.min(end, end)];
284
270
  };
285
- var getNormalizedPath = (directoryPath) => decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\/+/, "").replace(/\/+$/, "")) || "");
286
- var S3BucketUtil = class _S3BucketUtil {
271
+ var getNormalizedPath = (directoryPath) => {
272
+ return decodeURIComponent((directoryPath == null ? void 0 : directoryPath.trim().replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/+/g, "/")) || "");
273
+ };
274
+ var getFileSize = (maxFileSize, defaultMaxFileSize) => {
275
+ var _a2;
276
+ const fileSizeUnitValue = (_a2 = maxFileSize != null ? maxFileSize : defaultMaxFileSize) != null ? _a2 : "";
277
+ const fileSize = typeof fileSizeUnitValue === "number" ? fileSizeUnitValue : bytes(fileSizeUnitValue);
278
+ return fileSize != null ? fileSize : void 0;
279
+ };
280
+
281
+ // src/aws/s3/s3-file.ts
282
+ import { Buffer as Buffer2 } from "buffer";
283
+ import "stream";
284
+ import ms from "ms";
285
+ import { Upload } from "@aws-sdk/lib-storage";
286
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
287
+ import {
288
+ DeleteObjectCommand as DeleteObjectCommand2,
289
+ GetObjectCommand,
290
+ GetObjectTaggingCommand,
291
+ HeadObjectCommand as HeadObjectCommand2,
292
+ ListObjectsCommand,
293
+ ListObjectsV2Command as ListObjectsV2Command3,
294
+ PutObjectTaggingCommand
295
+ } from "@aws-sdk/client-s3";
296
+
297
+ // src/aws/s3/s3-directory.ts
298
+ import {
299
+ DeleteObjectCommand,
300
+ DeleteObjectsCommand as DeleteObjectsCommand2,
301
+ HeadObjectCommand,
302
+ ListObjectsV2Command as ListObjectsV2Command2,
303
+ PutObjectCommand
304
+ } from "@aws-sdk/client-s3";
305
+
306
+ // src/aws/s3/s3-bucket.ts
307
+ import {
308
+ CreateBucketCommand,
309
+ DeleteBucketCommand,
310
+ DeleteObjectsCommand,
311
+ GetBucketAclCommand,
312
+ GetBucketEncryptionCommand,
313
+ GetBucketPolicyCommand,
314
+ GetBucketVersioningCommand,
315
+ GetPublicAccessBlockCommand,
316
+ HeadBucketCommand,
317
+ ListBucketsCommand,
318
+ ListObjectsV2Command,
319
+ PutBucketPolicyCommand,
320
+ PutPublicAccessBlockCommand
321
+ } from "@aws-sdk/client-s3";
322
+
323
+ // src/aws/s3/s3-base.ts
324
+ import http from "http";
325
+ import https from "https";
326
+ import { NodeHttpHandler } from "@smithy/node-http-handler";
327
+ import { S3Client } from "@aws-sdk/client-s3";
328
+ var S3Base = class {
287
329
  constructor({
288
330
  logger: logger2,
289
- bucket,
290
331
  reqId,
291
332
  accessKeyId = AWSConfigSharingUtil.accessKeyId,
292
333
  secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
293
334
  endpoint = AWSConfigSharingUtil.endpoint,
294
335
  region = AWSConfigSharingUtil.region,
295
336
  s3ForcePathStyle = true,
296
- maxUploadFileSizeRestriction = "10GB"
337
+ // @ts-ignore
338
+ localstack = false
297
339
  }) {
298
340
  __publicField(this, "s3Client");
299
- __publicField(this, "bucket");
300
341
  __publicField(this, "endpoint");
301
342
  __publicField(this, "region");
302
343
  __publicField(this, "logger");
303
344
  __publicField(this, "reqId");
304
- __publicField(this, "maxUploadFileSizeRestriction");
345
+ __publicField(this, "localstack", false);
305
346
  const credentials = { accessKeyId, secretAccessKey };
306
347
  const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
307
348
  this.endpoint = endpoint;
308
349
  this.region = region;
309
- this.bucket = bucket;
310
350
  this.logger = logger2;
311
351
  this.reqId = reqId != null ? reqId : null;
312
- this.maxUploadFileSizeRestriction = maxUploadFileSizeRestriction;
352
+ this.localstack = localstack;
313
353
  const s3ClientParams = __spreadProps(__spreadValues(__spreadValues({}, options), s3ForcePathStyle && { forcePathStyle: s3ForcePathStyle }), {
314
354
  requestHandler: new NodeHttpHandler({
315
355
  httpAgent: new http.Agent({ keepAlive: true, maxSockets: 300 }),
@@ -320,17 +360,39 @@ var S3BucketUtil = class _S3BucketUtil {
320
360
  });
321
361
  this.s3Client = new S3Client(s3ClientParams);
322
362
  }
323
- get link() {
324
- return this.endpoint === "http://localhost:4566" ? `${this.endpoint}/${this.bucket}/` : `https://s3.${this.region}.amazonaws.com/${this.bucket}/`;
325
- }
326
363
  execute(command, options) {
327
364
  return __async(this, null, function* () {
328
365
  return this.s3Client.send(command, options);
329
366
  });
330
367
  }
331
- // ##### BUCKET BLOCK ##########################
368
+ };
369
+
370
+ // src/aws/s3/s3-bucket.ts
371
+ var S3Bucket = class extends S3Base {
372
+ constructor(_a2) {
373
+ var _b = _a2, { bucket } = _b, props = __objRest(_b, ["bucket"]);
374
+ super(props);
375
+ __publicField(this, "_bucket");
376
+ __publicField(this, "initializedBucket", "");
377
+ this._bucket = decodeURIComponent(bucket);
378
+ }
379
+ get link() {
380
+ return this.endpoint === "http://localhost:4566" ? `${this.endpoint}/${this.bucket}/` : `https://s3.${this.region}.amazonaws.com/${this.bucket}/`;
381
+ }
382
+ get bucket() {
383
+ return this._bucket;
384
+ }
385
+ changeBucket(bucket) {
386
+ this._bucket = decodeURIComponent(bucket);
387
+ this.initializedBucket = "";
388
+ }
332
389
  getBucketList() {
333
- return __async(this, arguments, function* (options = {}, includePublicAccess = false) {
390
+ return __async(this, arguments, function* (_c = {}) {
391
+ var _d = _c, {
392
+ includePublicAccess
393
+ } = _d, options = __objRest(_d, [
394
+ "includePublicAccess"
395
+ ]);
334
396
  const command = new ListBucketsCommand(options);
335
397
  const response = yield this.execute(command);
336
398
  const responseData = (response == null ? void 0 : response.Buckets) || null;
@@ -427,15 +489,22 @@ var S3BucketUtil = class _S3BucketUtil {
427
489
  });
428
490
  }
429
491
  initBucket() {
430
- return __async(this, arguments, function* (acl = "private" /* private */, includeConstraintLocation = false) {
492
+ return __async(this, arguments, function* (acl = "private" /* private */, {
493
+ includeConstraintLocation = false,
494
+ skipInitializedBucket = false
495
+ } = {}) {
431
496
  var _a2;
432
497
  const bucketName = this.bucket;
498
+ if (skipInitializedBucket && this.initializedBucket === bucketName) {
499
+ return;
500
+ }
433
501
  const isExists = yield this.isBucketExists();
434
502
  if (isExists) {
435
503
  (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
436
504
  return;
437
505
  }
438
506
  const data = acl === "private" /* private */ ? yield this.initAsPrivateBucket(includeConstraintLocation) : yield this.initAsPublicBucket();
507
+ this.initializedBucket = bucketName;
439
508
  return data;
440
509
  });
441
510
  }
@@ -588,17 +657,42 @@ var S3BucketUtil = class _S3BucketUtil {
588
657
  return data;
589
658
  });
590
659
  }
591
- // ##### DIRECTORY BLOCK ##########################
660
+ };
661
+
662
+ // src/aws/s3/s3-directory.ts
663
+ var S3Directory = class extends S3Bucket {
664
+ constructor(props) {
665
+ super(props);
666
+ }
667
+ // todo: checked!
668
+ directoryExists(directoryPath) {
669
+ return __async(this, null, function* () {
670
+ var _a2;
671
+ try {
672
+ const normalizedKey = getNormalizedPath(directoryPath);
673
+ if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
674
+ const command = new HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
675
+ yield this.execute(command);
676
+ return true;
677
+ } catch (error) {
678
+ if (error.name === "NotFound" || ((_a2 = error.$metadata) == null ? void 0 : _a2.httpStatusCode) === 404) {
679
+ return false;
680
+ }
681
+ throw error;
682
+ }
683
+ });
684
+ }
685
+ // todo: checked!
592
686
  createDirectory(directoryPath) {
593
687
  return __async(this, null, function* () {
594
688
  let normalizedPath = getNormalizedPath(directoryPath);
595
- if (!normalizedPath) throw new Error("No directory path provided");
596
- if (normalizedPath === "/") normalizedPath = "";
689
+ if (!normalizedPath || normalizedPath === "/") throw new Error("No directory path provided");
597
690
  const command = new PutObjectCommand({ Bucket: this.bucket, Key: `${normalizedPath}/` });
598
691
  const result = yield this.execute(command);
599
692
  return result;
600
693
  });
601
694
  }
695
+ // todo: checked!
602
696
  deleteDirectory(directoryPath) {
603
697
  return __async(this, null, function* () {
604
698
  var _a2, _b, _c, _d, _e, _f, _g;
@@ -609,7 +703,7 @@ var S3BucketUtil = class _S3BucketUtil {
609
703
  let ContinuationToken = void 0;
610
704
  do {
611
705
  const listResp = yield this.execute(
612
- new ListObjectsV2Command({
706
+ new ListObjectsV2Command2({
613
707
  Bucket: this.bucket,
614
708
  Prefix: normalizedPath,
615
709
  ContinuationToken
@@ -617,7 +711,7 @@ var S3BucketUtil = class _S3BucketUtil {
617
711
  );
618
712
  if (listResp.Contents && listResp.Contents.length > 0) {
619
713
  const deleteResult = yield this.execute(
620
- new DeleteObjectsCommand({
714
+ new DeleteObjectsCommand2({
621
715
  Bucket: this.bucket,
622
716
  Delete: {
623
717
  Objects: listResp.Contents.map((obj) => ({ Key: obj.Key })),
@@ -636,7 +730,7 @@ var S3BucketUtil = class _S3BucketUtil {
636
730
  ContinuationToken = listResp.NextContinuationToken;
637
731
  } while (ContinuationToken);
638
732
  if (totalDeletedCount === 0) {
639
- const directoryExists = yield this.fileExists(normalizedPath);
733
+ const directoryExists = yield this.directoryExists(normalizedPath);
640
734
  if (!directoryExists) {
641
735
  (_d = this.logger) == null ? void 0 : _d.debug(this.reqId, `Directory not found`, { directoryPath: normalizedPath });
642
736
  return null;
@@ -668,34 +762,64 @@ var S3BucketUtil = class _S3BucketUtil {
668
762
  };
669
763
  });
670
764
  }
765
+ // todo: checked!
671
766
  directoryList(directoryPath) {
672
767
  return __async(this, null, function* () {
768
+ var _a2;
673
769
  let normalizedPath = getNormalizedPath(directoryPath);
674
770
  if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
675
- else normalizedPath = "";
676
- const result = yield this.execute(
677
- new ListObjectsV2Command({
678
- Bucket: this.bucket,
679
- Prefix: normalizedPath,
680
- Delimiter: "/"
681
- })
682
- );
771
+ else normalizedPath = this.localstack ? "" : "/";
772
+ let result;
773
+ if (normalizedPath === "") {
774
+ const [fileResponse, { CommonPrefixes }] = yield Promise.all([
775
+ this.execute(
776
+ new ListObjectsV2Command2({
777
+ Bucket: this.bucket,
778
+ Prefix: "/",
779
+ Delimiter: "/"
780
+ })
781
+ ),
782
+ yield this.execute(
783
+ new ListObjectsV2Command2({
784
+ Bucket: this.bucket,
785
+ Prefix: "",
786
+ Delimiter: "/"
787
+ })
788
+ )
789
+ ]);
790
+ result = fileResponse;
791
+ result.CommonPrefixes = CommonPrefixes;
792
+ } else {
793
+ result = yield this.execute(
794
+ new ListObjectsV2Command2({
795
+ Bucket: this.bucket,
796
+ Prefix: normalizedPath,
797
+ Delimiter: "/"
798
+ })
799
+ );
800
+ }
801
+ (_a2 = this.logger) == null ? void 0 : _a2.debug(null, "#### directoryList", {
802
+ normalizedPath,
803
+ CommonPrefixes: result.CommonPrefixes,
804
+ ContentFile: result.Contents
805
+ });
683
806
  const directories = (result.CommonPrefixes || []).map((prefix) => prefix.Prefix).map((prefix) => {
684
807
  const relativePath = prefix.replace(normalizedPath, "");
685
808
  const dir = relativePath.replace(/\/$/, "");
686
809
  return dir;
687
810
  }).filter((dir) => dir);
688
811
  const files = (result.Contents || []).filter((content) => {
689
- var _a2;
690
- return content.Key !== normalizedPath && !((_a2 = content.Key) == null ? void 0 : _a2.endsWith("/"));
812
+ var _a3;
813
+ return content.Key !== normalizedPath && !((_a3 = content.Key) == null ? void 0 : _a3.endsWith("/"));
691
814
  }).map((content) => __spreadProps(__spreadValues({}, content), {
692
815
  Name: content.Key.replace(normalizedPath, "") || content.Key,
693
- Location: `${this.link}${content.Key}`,
816
+ Location: `${this.link}${content.Key.replace(/^\//, "")}`,
694
817
  LastModified: new Date(content.LastModified)
695
818
  }));
696
819
  return { directories, files };
697
820
  });
698
821
  }
822
+ // todo: checked!
699
823
  directoryListPaginated(_0) {
700
824
  return __async(this, arguments, function* (directoryPath, {
701
825
  pageSize = 100,
@@ -710,8 +834,9 @@ var S3BucketUtil = class _S3BucketUtil {
710
834
  let allDirectories = [];
711
835
  let allFiles = [];
712
836
  while (currentPage <= pageNumber) {
713
- const result = yield this.execute(
714
- new ListObjectsV2Command({
837
+ let result;
838
+ result = yield this.execute(
839
+ new ListObjectsV2Command2({
715
840
  Bucket: this.bucket,
716
841
  Prefix: normalizedPath,
717
842
  Delimiter: "/",
@@ -729,7 +854,7 @@ var S3BucketUtil = class _S3BucketUtil {
729
854
  return content.Key !== normalizedPath && !((_a2 = content.Key) == null ? void 0 : _a2.endsWith("/"));
730
855
  }).map((content) => __spreadProps(__spreadValues({}, content), {
731
856
  Name: content.Key.replace(normalizedPath, "") || content.Key,
732
- Location: `${this.link}${content.Key}`,
857
+ Location: `${this.link}${content.Key.replace(/^\//, "")}`,
733
858
  LastModified: new Date(content.LastModified)
734
859
  }));
735
860
  }
@@ -752,19 +877,21 @@ var S3BucketUtil = class _S3BucketUtil {
752
877
  */
753
878
  directoryListRecursive(directoryPath) {
754
879
  return __async(this, null, function* () {
880
+ var _a2;
755
881
  let normalizedPath = getNormalizedPath(directoryPath);
756
882
  if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
757
- else normalizedPath = "";
883
+ else normalizedPath = "/";
758
884
  const allDirectories = [];
759
885
  const allFiles = [];
760
886
  let ContinuationToken = void 0;
761
887
  do {
762
- const command = new ListObjectsV2Command({
763
- Bucket: this.bucket,
764
- Prefix: normalizedPath,
765
- ContinuationToken
766
- });
767
- const result = yield this.execute(command);
888
+ const result = yield this.execute(
889
+ new ListObjectsV2Command2({
890
+ Bucket: this.bucket,
891
+ Prefix: normalizedPath,
892
+ ContinuationToken
893
+ })
894
+ );
768
895
  if (result.Contents) {
769
896
  for (const content of result.Contents) {
770
897
  const fullPath = content.Key;
@@ -776,8 +903,8 @@ var S3BucketUtil = class _S3BucketUtil {
776
903
  allFiles.push(__spreadProps(__spreadValues({}, content), {
777
904
  Name: filename,
778
905
  Path: fullPath,
779
- Location: `${this.link}${content.Key}`,
780
- LastModified: new Date(content.LastModified)
906
+ Location: content.Key ? `${this.link}${(_a2 = content.Key) == null ? void 0 : _a2.replace(/^\//, "")}` : "",
907
+ LastModified: content.LastModified ? new Date(content.LastModified) : null
781
908
  }));
782
909
  }
783
910
  }
@@ -833,7 +960,7 @@ var S3BucketUtil = class _S3BucketUtil {
833
960
  treeNode.children.push({
834
961
  path: "/" + file.Key,
835
962
  name: file.Name,
836
- location: `${this.link}${file.Key}`,
963
+ location: `${this.link}${file.Key.replace(/^\//, "")}`,
837
964
  type: "file",
838
965
  size: file.Size,
839
966
  lastModified: file.LastModified
@@ -847,14 +974,18 @@ var S3BucketUtil = class _S3BucketUtil {
847
974
  return treeNode;
848
975
  });
849
976
  }
850
- // ##### FILES BLOCK ##########################
977
+ };
978
+
979
+ // src/aws/s3/s3-file.ts
980
+ var S3File = class extends S3Directory {
981
+ constructor(props) {
982
+ super(props);
983
+ }
851
984
  fileInfo(filePath) {
852
985
  return __async(this, null, function* () {
853
986
  const normalizedKey = getNormalizedPath(filePath);
854
- if (!normalizedKey || normalizedKey === "/") {
855
- throw new Error("No file key provided");
856
- }
857
- const command = new HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
987
+ if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
988
+ const command = new HeadObjectCommand2({ Bucket: this.bucket, Key: normalizedKey });
858
989
  return yield this.execute(command);
859
990
  });
860
991
  }
@@ -863,7 +994,7 @@ var S3BucketUtil = class _S3BucketUtil {
863
994
  var _a2, _b;
864
995
  let normalizedPath = getNormalizedPath(directoryPath);
865
996
  if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
866
- else normalizedPath = "";
997
+ else normalizedPath = this.localstack ? "" : "/";
867
998
  const prefix = normalizedPath + (fileNamePrefix || "");
868
999
  const command = new ListObjectsCommand({
869
1000
  Bucket: this.bucket,
@@ -873,10 +1004,10 @@ var S3BucketUtil = class _S3BucketUtil {
873
1004
  const result = yield this.execute(command);
874
1005
  const files = ((_a2 = result.Contents) != null ? _a2 : []).filter((v) => v).map(
875
1006
  (content) => {
876
- var _a3, _b2;
1007
+ var _a3, _b2, _c;
877
1008
  return __spreadProps(__spreadValues({}, content), {
878
1009
  Name: (_b2 = (_a3 = content.Key) == null ? void 0 : _a3.replace(prefix, "")) != null ? _b2 : content.Key,
879
- Location: `${this.link}${content.Key}`,
1010
+ Location: content.Key ? `${this.link}${(_c = content.Key) == null ? void 0 : _c.replace(/^\//, "")}` : "",
880
1011
  LastModified: content.LastModified ? new Date(content.LastModified) : null
881
1012
  });
882
1013
  }
@@ -885,6 +1016,7 @@ var S3BucketUtil = class _S3BucketUtil {
885
1016
  return files;
886
1017
  });
887
1018
  }
1019
+ // todo: checked!
888
1020
  fileListInfoPaginated(_0) {
889
1021
  return __async(this, arguments, function* (directoryPath, {
890
1022
  fileNamePrefix,
@@ -902,7 +1034,7 @@ var S3BucketUtil = class _S3BucketUtil {
902
1034
  let resultFiles = [];
903
1035
  while (currentPage <= pageNumber) {
904
1036
  const result = yield this.execute(
905
- new ListObjectsV2Command({
1037
+ new ListObjectsV2Command3({
906
1038
  Bucket: this.bucket,
907
1039
  Prefix: prefix,
908
1040
  Delimiter: "/",
@@ -916,7 +1048,7 @@ var S3BucketUtil = class _S3BucketUtil {
916
1048
  var _a3, _b2;
917
1049
  return __spreadProps(__spreadValues({}, content), {
918
1050
  Name: (_b2 = (_a3 = content.Key) == null ? void 0 : _a3.replace(prefix, "")) != null ? _b2 : content.Key,
919
- Location: `${this.link}${content.Key}`,
1051
+ Location: content.Key ? `${this.link}${content.Key.replace(/^\//, "")}` : "",
920
1052
  LastModified: content.LastModified ? new Date(content.LastModified) : null
921
1053
  });
922
1054
  }
@@ -940,23 +1072,29 @@ var S3BucketUtil = class _S3BucketUtil {
940
1072
  };
941
1073
  });
942
1074
  }
943
- taggingFile(filePath, tagVersion = "1.0.0") {
1075
+ // todo: checked!
1076
+ taggingFile(filePath, tag) {
944
1077
  return __async(this, null, function* () {
1078
+ var _a2;
1079
+ let normalizedKey = "";
1080
+ const tags = [].concat(tag);
945
1081
  try {
946
- const normalizedKey = getNormalizedPath(filePath);
1082
+ normalizedKey = getNormalizedPath(filePath);
947
1083
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
948
1084
  const command = new PutObjectTaggingCommand({
949
1085
  Bucket: this.bucket,
950
1086
  Key: normalizedKey,
951
- Tagging: { TagSet: [{ Key: "version", Value: tagVersion }] }
1087
+ Tagging: { TagSet: tags }
952
1088
  });
953
1089
  yield this.execute(command);
954
1090
  return true;
955
- } catch (e) {
1091
+ } catch (error) {
1092
+ (_a2 = this.logger) == null ? void 0 : _a2.warn(null, "failed to tagging file", { errMsg: error.message, fileKey: normalizedKey, tags });
956
1093
  return false;
957
1094
  }
958
1095
  });
959
1096
  }
1097
+ // todo: checked!
960
1098
  fileVersion(filePath) {
961
1099
  return __async(this, null, function* () {
962
1100
  var _a2, _b;
@@ -968,10 +1106,11 @@ var S3BucketUtil = class _S3BucketUtil {
968
1106
  return (_b = tag == null ? void 0 : tag.Value) != null ? _b : "";
969
1107
  });
970
1108
  }
1109
+ // todo: checked!
971
1110
  fileUrl(filePath, expiresIn = "15m") {
972
1111
  return __async(this, null, function* () {
973
1112
  var _a2;
974
- const normalizedKey = getNormalizedPath(filePath);
1113
+ let normalizedKey = getNormalizedPath(filePath);
975
1114
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
976
1115
  const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : ms(expiresIn) / 1e3;
977
1116
  const command = new GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
@@ -989,7 +1128,7 @@ var S3BucketUtil = class _S3BucketUtil {
989
1128
  const normalizedKey = getNormalizedPath(filePath);
990
1129
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
991
1130
  try {
992
- const command = new HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1131
+ const command = new HeadObjectCommand2({ Bucket: this.bucket, Key: normalizedKey });
993
1132
  const headObject = yield this.execute(command);
994
1133
  const bytes2 = (_a2 = headObject.ContentLength) != null ? _a2 : 0;
995
1134
  switch (unit) {
@@ -1011,13 +1150,14 @@ var S3BucketUtil = class _S3BucketUtil {
1011
1150
  }
1012
1151
  });
1013
1152
  }
1153
+ // todo: checked!
1014
1154
  fileExists(filePath) {
1015
1155
  return __async(this, null, function* () {
1016
1156
  var _a2;
1017
1157
  try {
1018
1158
  const normalizedKey = getNormalizedPath(filePath);
1019
1159
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1020
- const command = new HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1160
+ const command = new HeadObjectCommand2({ Bucket: this.bucket, Key: normalizedKey });
1021
1161
  yield this.execute(command);
1022
1162
  return true;
1023
1163
  } catch (error) {
@@ -1028,9 +1168,10 @@ var S3BucketUtil = class _S3BucketUtil {
1028
1168
  }
1029
1169
  });
1030
1170
  }
1171
+ // todo: checked!
1031
1172
  fileContent(filePath, format = "buffer") {
1032
1173
  return __async(this, null, function* () {
1033
- const normalizedKey = getNormalizedPath(filePath);
1174
+ let normalizedKey = getNormalizedPath(filePath);
1034
1175
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1035
1176
  const command = new GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1036
1177
  const result = yield this.execute(command);
@@ -1061,6 +1202,7 @@ var S3BucketUtil = class _S3BucketUtil {
1061
1202
  return buffer;
1062
1203
  });
1063
1204
  }
1205
+ // todo: checked!
1064
1206
  uploadFile(_0, _1) {
1065
1207
  return __async(this, arguments, function* (filePath, fileData, acl = "private" /* private */, version = "1.0.0") {
1066
1208
  const normalizedKey = getNormalizedPath(filePath);
@@ -1084,38 +1226,130 @@ var S3BucketUtil = class _S3BucketUtil {
1084
1226
  };
1085
1227
  });
1086
1228
  }
1229
+ // todo: checked!
1087
1230
  deleteFile(filePath) {
1088
1231
  return __async(this, null, function* () {
1089
1232
  const normalizedKey = getNormalizedPath(filePath);
1090
1233
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1091
- const command = new DeleteObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1234
+ const command = new DeleteObjectCommand2({ Bucket: this.bucket, Key: normalizedKey });
1092
1235
  return yield this.execute(command);
1093
1236
  });
1094
1237
  }
1095
- // ##### STREAMING BLOCK ##########################
1238
+ };
1239
+
1240
+ // src/aws/s3/s3-stream.ts
1241
+ var pump = promisify(pipeline);
1242
+ var S3Stream = class _S3Stream extends S3File {
1243
+ constructor(_a2) {
1244
+ var _b = _a2, { maxUploadFileSizeRestriction = "10GB" } = _b, props = __objRest(_b, ["maxUploadFileSizeRestriction"]);
1245
+ super(props);
1246
+ __publicField(this, "maxUploadFileSizeRestriction");
1247
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1248
+ __publicField(this, "getImageFileViewCtrl", ({
1249
+ fileKey: _fileKey,
1250
+ queryField = "file",
1251
+ cachingAge = 31536e3
1252
+ } = {}) => {
1253
+ return (req, res, next) => __async(this, null, function* () {
1254
+ var _a2, _b, _c, _d, _e;
1255
+ let fileKey = _fileKey || (((_a2 = req.query) == null ? void 0 : _a2[queryField]) ? decodeURIComponent((_b = req.query) == null ? void 0 : _b[queryField]) : void 0);
1256
+ if (!fileKey || fileKey === "/") {
1257
+ (_d = this.logger) == null ? void 0 : _d.warn(req.id, "image file view required file query field", {
1258
+ fileKey: (_c = req.query) == null ? void 0 : _c[queryField],
1259
+ queryField
1260
+ });
1261
+ next("image file key is required");
1262
+ return;
1263
+ }
1264
+ try {
1265
+ const imageBuffer = yield this.fileContent(fileKey, "buffer");
1266
+ const ext = path.extname(fileKey).slice(1).toLowerCase();
1267
+ const mimeTypeMap = {
1268
+ jpg: "image/jpeg",
1269
+ jpeg: "image/jpeg",
1270
+ png: "image/png",
1271
+ gif: "image/gif",
1272
+ webp: "image/webp",
1273
+ svg: "image/svg+xml",
1274
+ ico: "image/x-icon"
1275
+ };
1276
+ const contentType = mimeTypeMap[ext] || "application/octet-stream";
1277
+ res.setHeader("Content-Type", contentType);
1278
+ if (cachingAge) res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1279
+ res.setHeader("Content-Length", imageBuffer.length);
1280
+ res.status(200).send(imageBuffer);
1281
+ } catch (error) {
1282
+ (_e = this.logger) == null ? void 0 : _e.warn(req.id, "image view fileKey not found", {
1283
+ fileKey,
1284
+ localstack: this.localstack
1285
+ });
1286
+ next(`Failed to retrieve image file: ${error.message}`);
1287
+ }
1288
+ });
1289
+ });
1290
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1291
+ __publicField(this, "getPdfFileViewCtrl", ({
1292
+ fileKey: _fileKey,
1293
+ queryField = "file",
1294
+ cachingAge = 31536e3
1295
+ } = {}) => {
1296
+ return (req, res, next) => __async(this, null, function* () {
1297
+ var _a2, _b;
1298
+ let fileKey = _fileKey || (((_a2 = req.query) == null ? void 0 : _a2[queryField]) ? decodeURIComponent((_b = req.query) == null ? void 0 : _b[queryField]) : void 0);
1299
+ if (!fileKey) {
1300
+ next("pdf file key is required");
1301
+ return;
1302
+ }
1303
+ try {
1304
+ const fileBuffer = yield this.fileContent(fileKey, "buffer");
1305
+ const ext = path.extname(fileKey).slice(1).toLowerCase();
1306
+ const mimeTypeMap = {
1307
+ pdf: "application/pdf",
1308
+ txt: "text/plain",
1309
+ doc: "application/msword",
1310
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1311
+ xls: "application/vnd.ms-excel",
1312
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1313
+ ppt: "application/vnd.ms-powerpoint",
1314
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1315
+ };
1316
+ const contentType = mimeTypeMap[ext] || "application/octet-stream";
1317
+ res.setHeader("Content-Type", contentType);
1318
+ res.setHeader("Content-Disposition", `inline; filename="${path.basename(fileKey)}"`);
1319
+ res.setHeader("Cache-Control", `public, max-age=${cachingAge}`);
1320
+ res.setHeader("Content-Length", fileBuffer.length);
1321
+ res.status(200).send(fileBuffer);
1322
+ } catch (error) {
1323
+ next(`Failed to retrieve pdf file: ${error.message}`);
1324
+ }
1325
+ });
1326
+ });
1327
+ this.maxUploadFileSizeRestriction = maxUploadFileSizeRestriction;
1328
+ }
1096
1329
  streamObjectFile(_0) {
1097
1330
  return __async(this, arguments, function* (filePath, {
1098
1331
  Range,
1099
1332
  checkFileExists = true,
1100
1333
  abortSignal
1101
1334
  } = {}) {
1102
- const normalizedKey = getNormalizedPath(filePath);
1335
+ let normalizedKey = getNormalizedPath(filePath);
1103
1336
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1104
1337
  if (checkFileExists) {
1105
1338
  const isExists = yield this.fileExists(normalizedKey);
1106
1339
  if (!isExists) return null;
1107
1340
  }
1108
- const command = new GetObjectCommand(__spreadValues({
1341
+ const command = new GetObjectCommand2(__spreadValues({
1109
1342
  Bucket: this.bucket,
1110
1343
  Key: normalizedKey
1111
1344
  }, Range ? { Range } : {}));
1112
1345
  const response = yield this.execute(command, { abortSignal });
1113
- if (!response.Body || !(response.Body instanceof Readable)) {
1346
+ if (!response.Body || !(response.Body instanceof Readable2)) {
1114
1347
  throw new Error("Invalid response body: not a Readable stream");
1115
1348
  }
1116
1349
  return response.Body;
1117
1350
  });
1118
1351
  }
1352
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1119
1353
  streamVideoFile(_0) {
1120
1354
  return __async(this, arguments, function* ({
1121
1355
  filePath,
@@ -1123,10 +1357,10 @@ var S3BucketUtil = class _S3BucketUtil {
1123
1357
  abortSignal
1124
1358
  }) {
1125
1359
  var _a2;
1126
- const normalizedKey = getNormalizedPath(filePath);
1360
+ let normalizedKey = getNormalizedPath(filePath);
1127
1361
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1128
1362
  try {
1129
- const cmd = new GetObjectCommand(__spreadValues({
1363
+ const cmd = new GetObjectCommand2(__spreadValues({
1130
1364
  Bucket: this.bucket,
1131
1365
  Key: normalizedKey
1132
1366
  }, Range ? { Range } : {}));
@@ -1155,145 +1389,127 @@ var S3BucketUtil = class _S3BucketUtil {
1155
1389
  }
1156
1390
  });
1157
1391
  }
1158
- getStreamZipFileCtr(_0) {
1392
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1393
+ getStreamVideoFileCtrl(_0) {
1159
1394
  return __async(this, arguments, function* ({
1160
- filePath,
1161
- filename: _filename,
1162
- compressionLevel = 5
1395
+ fileKey,
1396
+ allowedWhitelist,
1397
+ contentType = "video/mp4",
1398
+ streamTimeoutMS = 3e4,
1399
+ bufferMB = 5
1163
1400
  }) {
1164
1401
  return (req, res, next) => __async(this, null, function* () {
1165
- var _a2, _b, _c, _d, _e;
1166
- const filePaths = [].concat(filePath).map((filePath2) => getNormalizedPath(filePath2)).filter((v) => v && v !== "/");
1167
- if (!filePaths.length) {
1168
- throw new Error("No file keys provided");
1402
+ var _a2, _b, _c, _d, _e, _f;
1403
+ let normalizedKey = getNormalizedPath(fileKey);
1404
+ if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1405
+ const isExists = yield this.fileExists(normalizedKey);
1406
+ const fileSize = yield this.sizeOf(normalizedKey);
1407
+ let Range;
1408
+ if (!isExists) {
1409
+ next(Error(`File does not exist: "${normalizedKey}"`));
1410
+ return;
1411
+ }
1412
+ try {
1413
+ if (req.method === "HEAD") {
1414
+ res.setHeader("Content-Type", contentType);
1415
+ res.setHeader("Accept-Ranges", "bytes");
1416
+ if (fileSize) res.setHeader("Content-Length", String(fileSize));
1417
+ return res.status(200).end();
1418
+ }
1419
+ const bufferSize = bufferMB;
1420
+ const CHUNK_SIZE = __pow(10, 6) * bufferSize;
1421
+ const rangeValues = parseRangeHeader(req.headers.range, fileSize, CHUNK_SIZE);
1422
+ let [start, end] = rangeValues || [];
1423
+ if (!rangeValues || start < 0 || start >= fileSize || end < 0 || end >= fileSize || start > end) {
1424
+ res.status(416).send("Requested Range Not Satisfiable");
1425
+ return;
1426
+ }
1427
+ res.statusCode = 206;
1428
+ const chunkLength = end - start + 1;
1429
+ res.setHeader("Content-Length", chunkLength);
1430
+ res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`);
1431
+ res.setHeader("Accept-Ranges", "bytes");
1432
+ res.setHeader("Content-Type", "video/mp4");
1433
+ Range = `bytes=${start}-${end}`;
1434
+ } catch (error) {
1435
+ next(error);
1436
+ return;
1169
1437
  }
1170
- let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1171
- filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1172
1438
  const abort = new AbortController();
1173
- const onClose = () => {
1174
- abort.abort();
1175
- };
1439
+ const onClose = () => abort.abort();
1176
1440
  req.once("close", onClose);
1177
1441
  try {
1178
- (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: filePaths.length });
1179
- const downloadPromises = filePaths.map((filePath2) => __async(this, null, function* () {
1180
- var _a3, _b2, _c2;
1181
- try {
1182
- if (abort.signal.aborted) return null;
1183
- const stream = yield this.streamObjectFile(filePath2, { abortSignal: abort.signal });
1184
- if (!stream) {
1185
- (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { filePath: filePath2 });
1186
- return null;
1187
- }
1188
- const chunks = [];
1189
- try {
1190
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
1191
- const chunk = temp.value;
1192
- if (abort.signal.aborted) {
1193
- stream.destroy();
1194
- return null;
1195
- }
1196
- chunks.push(Buffer2.from(chunk));
1197
- }
1198
- } catch (temp) {
1199
- error = [temp];
1200
- } finally {
1201
- try {
1202
- more && (temp = iter.return) && (yield temp.call(iter));
1203
- } finally {
1204
- if (error)
1205
- throw error[0];
1206
- }
1207
- }
1208
- const buffer = Buffer2.concat(chunks);
1209
- const fileName = filePath2.split("/").pop() || filePath2;
1210
- (_b2 = this.logger) == null ? void 0 : _b2.debug(this.reqId, "File downloaded", {
1211
- filePath: filePath2,
1212
- sizeMB: (buffer.length / (1024 * 1024)).toFixed(2)
1213
- });
1214
- return { buffer, name: fileName, path: filePath2 };
1215
- } catch (error2) {
1216
- (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { filePath: filePath2, error: error2 });
1217
- return null;
1218
- }
1219
- }));
1220
- const fileBuffers = (yield Promise.all(downloadPromises)).filter(Boolean);
1221
- if (abort.signal.aborted || fileBuffers.length === 0) {
1222
- req.off("close", onClose);
1223
- if (fileBuffers.length === 0) {
1224
- next(new Error("No files available to zip"));
1225
- }
1226
- return;
1227
- }
1228
- (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "All files downloaded, measuring zip size...", {
1229
- fileCount: fileBuffers.length,
1230
- totalSizeMB: (fileBuffers.reduce((sum, f) => sum + f.buffer.length, 0) / (1024 * 1024)).toFixed(2)
1231
- });
1232
- const measureArchive = archiver("zip", { zlib: { level: compressionLevel } });
1233
- let actualZipSize = 0;
1234
- measureArchive.on("data", (chunk) => {
1235
- actualZipSize += chunk.length;
1442
+ const result = yield this.streamVideoFile({
1443
+ filePath: normalizedKey,
1444
+ Range,
1445
+ abortSignal: abort.signal
1236
1446
  });
1237
- for (const file of fileBuffers) {
1238
- if (abort.signal.aborted) break;
1239
- measureArchive.append(file.buffer, { name: file.name });
1240
- }
1241
- yield measureArchive.finalize();
1242
- if (abort.signal.aborted) {
1243
- req.off("close", onClose);
1244
- return;
1447
+ const { body, meta } = result;
1448
+ const origin = Array.isArray(allowedWhitelist) ? allowedWhitelist.includes((_a2 = req.headers.origin) != null ? _a2 : "") ? req.headers.origin : void 0 : allowedWhitelist;
1449
+ if (origin) {
1450
+ res.setHeader("Access-Control-Allow-Origin", origin);
1451
+ res.setHeader("Vary", "Origin");
1245
1452
  }
1246
- (_c = this.logger) == null ? void 0 : _c.info(this.reqId, "Zip size calculated", {
1247
- actualZipSize,
1248
- sizeMB: (actualZipSize / (1024 * 1024)).toFixed(2)
1249
- });
1250
- const actualArchive = archiver("zip", { zlib: { level: compressionLevel } });
1251
- res.setHeader("Content-Type", "application/zip");
1252
- res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
1253
- res.setHeader("Content-Length", String(actualZipSize));
1254
- res.setHeader("Access-Control-Expose-Headers", "Content-Type, Content-Disposition, Content-Length");
1255
- actualArchive.on("error", (err) => {
1256
- var _a3;
1257
- (_a3 = this.logger) == null ? void 0 : _a3.error(this.reqId, "Archive error", { error: err });
1258
- abort.abort();
1259
- if (!res.headersSent) {
1260
- next(err);
1453
+ const finalContentType = contentType.startsWith("video/") ? contentType : `video/${contentType}`;
1454
+ res.setHeader("Content-Type", (_b = meta.contentType) != null ? _b : finalContentType);
1455
+ res.setHeader("Accept-Ranges", (_c = meta.acceptRanges) != null ? _c : "bytes");
1456
+ if (Range && meta.contentRange) {
1457
+ res.status(206);
1458
+ res.setHeader("Content-Range", meta.contentRange);
1459
+ if (typeof meta.contentLength === "number") {
1460
+ res.setHeader("Content-Length", String(meta.contentLength));
1261
1461
  }
1262
- });
1263
- actualArchive.pipe(res);
1264
- for (const file of fileBuffers) {
1265
- if (abort.signal.aborted) break;
1266
- actualArchive.append(file.buffer, { name: file.name });
1462
+ } else if (fileSize) {
1463
+ res.setHeader("Content-Length", String(fileSize));
1267
1464
  }
1268
- yield actualArchive.finalize();
1269
- (_d = this.logger) == null ? void 0 : _d.info(this.reqId, "Zip download completed", {
1270
- fileCount: fileBuffers.length,
1271
- totalSize: actualZipSize
1465
+ if (meta.etag) res.setHeader("ETag", meta.etag);
1466
+ if (meta.lastModified) res.setHeader("Last-Modified", meta.lastModified.toUTCString());
1467
+ const timeout = setTimeout(() => {
1468
+ abort.abort();
1469
+ if (!res.headersSent) res.status(504);
1470
+ res.end();
1471
+ }, streamTimeoutMS);
1472
+ res.once("close", () => {
1473
+ var _a3;
1474
+ clearTimeout(timeout);
1475
+ (_a3 = body.destroy) == null ? void 0 : _a3.call(body);
1476
+ req.off("close", onClose);
1272
1477
  });
1273
- req.off("close", onClose);
1478
+ yield pump(body, res);
1479
+ clearTimeout(timeout);
1274
1480
  } catch (error) {
1275
- abort.abort();
1276
- 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";
1277
- if (isBenignError) {
1481
+ 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";
1482
+ if (isBenignStreamError) {
1278
1483
  return;
1279
1484
  }
1280
1485
  if (!res.headersSent) {
1281
- (_e = this.logger) == null ? void 0 : _e.error(this.reqId, "Failed to create zip archive", { error });
1486
+ (_f = this.logger) == null ? void 0 : _f.warn(req.id, "caught exception in stream controller", {
1487
+ error: (_d = error == null ? void 0 : error.message) != null ? _d : String(error),
1488
+ key: fileKey,
1489
+ url: req.originalUrl,
1490
+ userId: (_e = req.user) == null ? void 0 : _e._id
1491
+ });
1282
1492
  next(error);
1283
- } else if (!res.writableEnded) {
1493
+ return;
1494
+ }
1495
+ if (!res.writableEnded) {
1284
1496
  try {
1285
1497
  res.end();
1286
1498
  } catch (e) {
1287
1499
  }
1288
1500
  }
1289
- } finally {
1290
- req.off("close", onClose);
1501
+ return;
1291
1502
  }
1292
1503
  });
1293
1504
  });
1294
1505
  }
1506
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1295
1507
  getStreamFileCtrl(_0) {
1296
- return __async(this, arguments, function* ({ filePath, filename }) {
1508
+ return __async(this, arguments, function* ({
1509
+ filePath,
1510
+ filename,
1511
+ forDownloading = false
1512
+ }) {
1297
1513
  return (req, res, next) => __async(this, null, function* () {
1298
1514
  var _a2, _b;
1299
1515
  const abort = new AbortController();
@@ -1304,7 +1520,7 @@ var S3BucketUtil = class _S3BucketUtil {
1304
1520
  (_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
1305
1521
  };
1306
1522
  req.once("close", onClose);
1307
- const normalizedKey = getNormalizedPath(filePath);
1523
+ let normalizedKey = getNormalizedPath(filePath);
1308
1524
  if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1309
1525
  try {
1310
1526
  const isExists = yield this.fileExists(normalizedKey);
@@ -1325,7 +1541,9 @@ var S3BucketUtil = class _S3BucketUtil {
1325
1541
  const fileInfo = yield this.fileInfo(normalizedKey);
1326
1542
  const fileName = filename || normalizedKey.split("/").pop() || "download";
1327
1543
  res.setHeader("Content-Type", fileInfo.ContentType || "application/octet-stream");
1328
- res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
1544
+ if (forDownloading) {
1545
+ res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
1546
+ }
1329
1547
  if (fileInfo.ContentLength) {
1330
1548
  res.setHeader("Content-Length", String(fileInfo.ContentLength));
1331
1549
  }
@@ -1366,115 +1584,140 @@ var S3BucketUtil = class _S3BucketUtil {
1366
1584
  });
1367
1585
  });
1368
1586
  }
1369
- getStreamVideoFileCtrl(_0) {
1587
+ // todo: LOCALSTACK SANITY CHECKED - WORKING WELL, DON'T TOUCH!
1588
+ getStreamZipFileCtr(_0) {
1370
1589
  return __async(this, arguments, function* ({
1371
- fileKey,
1372
- allowedWhitelist,
1373
- contentType = "video/mp4",
1374
- streamTimeoutMS = 3e4,
1375
- bufferMB = 5
1590
+ filePath,
1591
+ filename: _filename,
1592
+ compressionLevel = 5
1376
1593
  }) {
1377
1594
  return (req, res, next) => __async(this, null, function* () {
1378
- var _a2, _b, _c, _d, _e, _f;
1379
- const normalizedKey = getNormalizedPath(fileKey);
1380
- if (!normalizedKey || normalizedKey === "/") throw new Error("No file key provided");
1381
- const isExists = yield this.fileExists(normalizedKey);
1382
- const fileSize = yield this.sizeOf(normalizedKey);
1383
- let Range;
1384
- if (!isExists) {
1385
- next(Error(`File does not exist: "${normalizedKey}"`));
1386
- return;
1387
- }
1388
- try {
1389
- if (req.method === "HEAD") {
1390
- res.setHeader("Content-Type", contentType);
1391
- res.setHeader("Accept-Ranges", "bytes");
1392
- if (fileSize) res.setHeader("Content-Length", String(fileSize));
1393
- return res.status(200).end();
1394
- }
1395
- const bufferSize = bufferMB;
1396
- const CHUNK_SIZE = __pow(10, 6) * bufferSize;
1397
- const rangeValues = parseRangeHeader(req.headers.range, fileSize, CHUNK_SIZE);
1398
- let [start, end] = rangeValues || [];
1399
- if (!rangeValues || start < 0 || start >= fileSize || end < 0 || end >= fileSize || start > end) {
1400
- res.status(416).send("Requested Range Not Satisfiable");
1401
- return;
1402
- }
1403
- res.statusCode = 206;
1404
- const chunkLength = end - start + 1;
1405
- res.setHeader("Content-Length", chunkLength);
1406
- res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`);
1407
- res.setHeader("Accept-Ranges", "bytes");
1408
- res.setHeader("Content-Type", "video/mp4");
1409
- Range = `bytes=${start}-${end}`;
1410
- } catch (error) {
1411
- next(error);
1412
- return;
1595
+ var _a2, _b, _c, _d, _e;
1596
+ const filePaths = [].concat(filePath).map((filePath2) => getNormalizedPath(filePath2)).filter((v) => v && v !== "/");
1597
+ if (!filePaths.length) {
1598
+ throw new Error("No file keys provided");
1413
1599
  }
1600
+ let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1601
+ filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1414
1602
  const abort = new AbortController();
1415
- const onClose = () => abort.abort();
1603
+ const onClose = () => {
1604
+ abort.abort();
1605
+ };
1416
1606
  req.once("close", onClose);
1417
1607
  try {
1418
- const result = yield this.streamVideoFile({
1419
- filePath: normalizedKey,
1420
- Range,
1421
- abortSignal: abort.signal
1608
+ (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: filePaths.length });
1609
+ const downloadPromises = filePaths.map((filePath2) => __async(this, null, function* () {
1610
+ var _a3, _b2, _c2;
1611
+ try {
1612
+ if (abort.signal.aborted) return null;
1613
+ const stream = yield this.streamObjectFile(filePath2, { abortSignal: abort.signal });
1614
+ if (!stream) {
1615
+ (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { filePath: filePath2 });
1616
+ return null;
1617
+ }
1618
+ const chunks = [];
1619
+ try {
1620
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
1621
+ const chunk = temp.value;
1622
+ if (abort.signal.aborted) {
1623
+ stream.destroy();
1624
+ return null;
1625
+ }
1626
+ chunks.push(Buffer3.from(chunk));
1627
+ }
1628
+ } catch (temp) {
1629
+ error = [temp];
1630
+ } finally {
1631
+ try {
1632
+ more && (temp = iter.return) && (yield temp.call(iter));
1633
+ } finally {
1634
+ if (error)
1635
+ throw error[0];
1636
+ }
1637
+ }
1638
+ const buffer = Buffer3.concat(chunks);
1639
+ const fileName = filePath2.split("/").pop() || filePath2;
1640
+ (_b2 = this.logger) == null ? void 0 : _b2.debug(this.reqId, "File downloaded", {
1641
+ filePath: filePath2,
1642
+ sizeMB: (buffer.length / (1024 * 1024)).toFixed(2)
1643
+ });
1644
+ return { buffer, name: fileName, path: filePath2 };
1645
+ } catch (error2) {
1646
+ (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { filePath: filePath2, error: error2 });
1647
+ return null;
1648
+ }
1649
+ }));
1650
+ const fileBuffers = (yield Promise.all(downloadPromises)).filter(Boolean);
1651
+ if (abort.signal.aborted || fileBuffers.length === 0) {
1652
+ req.off("close", onClose);
1653
+ if (fileBuffers.length === 0) {
1654
+ next(new Error("No files available to zip"));
1655
+ }
1656
+ return;
1657
+ }
1658
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "All files downloaded, measuring zip size...", {
1659
+ fileCount: fileBuffers.length,
1660
+ totalSizeMB: (fileBuffers.reduce((sum, f) => sum + f.buffer.length, 0) / (1024 * 1024)).toFixed(2)
1422
1661
  });
1423
- const { body, meta } = result;
1424
- const origin = Array.isArray(allowedWhitelist) ? allowedWhitelist.includes((_a2 = req.headers.origin) != null ? _a2 : "") ? req.headers.origin : void 0 : allowedWhitelist;
1425
- if (origin) {
1426
- res.setHeader("Access-Control-Allow-Origin", origin);
1427
- res.setHeader("Vary", "Origin");
1662
+ const measureArchive = archiver("zip", { zlib: { level: compressionLevel } });
1663
+ let actualZipSize = 0;
1664
+ measureArchive.on("data", (chunk) => {
1665
+ actualZipSize += chunk.length;
1666
+ });
1667
+ for (const file of fileBuffers) {
1668
+ if (abort.signal.aborted) break;
1669
+ measureArchive.append(file.buffer, { name: file.name });
1428
1670
  }
1429
- const finalContentType = contentType.startsWith("video/") ? contentType : `video/${contentType}`;
1430
- res.setHeader("Content-Type", (_b = meta.contentType) != null ? _b : finalContentType);
1431
- res.setHeader("Accept-Ranges", (_c = meta.acceptRanges) != null ? _c : "bytes");
1432
- if (Range && meta.contentRange) {
1433
- res.status(206);
1434
- res.setHeader("Content-Range", meta.contentRange);
1435
- if (typeof meta.contentLength === "number") {
1436
- res.setHeader("Content-Length", String(meta.contentLength));
1437
- }
1438
- } else if (fileSize) {
1439
- res.setHeader("Content-Length", String(fileSize));
1671
+ yield measureArchive.finalize();
1672
+ if (abort.signal.aborted) {
1673
+ req.off("close", onClose);
1674
+ return;
1440
1675
  }
1441
- if (meta.etag) res.setHeader("ETag", meta.etag);
1442
- if (meta.lastModified) res.setHeader("Last-Modified", meta.lastModified.toUTCString());
1443
- const timeout = setTimeout(() => {
1444
- abort.abort();
1445
- if (!res.headersSent) res.status(504);
1446
- res.end();
1447
- }, streamTimeoutMS);
1448
- res.once("close", () => {
1676
+ (_c = this.logger) == null ? void 0 : _c.info(this.reqId, "Zip size calculated", {
1677
+ actualZipSize,
1678
+ sizeMB: (actualZipSize / (1024 * 1024)).toFixed(2)
1679
+ });
1680
+ const actualArchive = archiver("zip", { zlib: { level: compressionLevel } });
1681
+ res.setHeader("Content-Type", "application/zip");
1682
+ res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
1683
+ res.setHeader("Content-Length", String(actualZipSize));
1684
+ res.setHeader("Access-Control-Expose-Headers", "Content-Type, Content-Disposition, Content-Length");
1685
+ actualArchive.on("error", (err) => {
1449
1686
  var _a3;
1450
- clearTimeout(timeout);
1451
- (_a3 = body.destroy) == null ? void 0 : _a3.call(body);
1452
- req.off("close", onClose);
1687
+ (_a3 = this.logger) == null ? void 0 : _a3.error(this.reqId, "Archive error", { error: err });
1688
+ abort.abort();
1689
+ if (!res.headersSent) {
1690
+ next(err);
1691
+ }
1453
1692
  });
1454
- yield pump(body, res);
1455
- clearTimeout(timeout);
1693
+ actualArchive.pipe(res);
1694
+ for (const file of fileBuffers) {
1695
+ if (abort.signal.aborted) break;
1696
+ actualArchive.append(file.buffer, { name: file.name });
1697
+ }
1698
+ yield actualArchive.finalize();
1699
+ (_d = this.logger) == null ? void 0 : _d.info(this.reqId, "Zip download completed", {
1700
+ fileCount: fileBuffers.length,
1701
+ totalSize: actualZipSize
1702
+ });
1703
+ req.off("close", onClose);
1456
1704
  } catch (error) {
1457
- 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";
1458
- if (isBenignStreamError) {
1705
+ abort.abort();
1706
+ 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";
1707
+ if (isBenignError) {
1459
1708
  return;
1460
1709
  }
1461
1710
  if (!res.headersSent) {
1462
- (_f = this.logger) == null ? void 0 : _f.warn(req.id, "caught exception in stream controller", {
1463
- error: (_d = error == null ? void 0 : error.message) != null ? _d : String(error),
1464
- key: fileKey,
1465
- url: req.originalUrl,
1466
- userId: (_e = req.user) == null ? void 0 : _e._id
1467
- });
1711
+ (_e = this.logger) == null ? void 0 : _e.error(this.reqId, "Failed to create zip archive", { error });
1468
1712
  next(error);
1469
- return;
1470
- }
1471
- if (!res.writableEnded) {
1713
+ } else if (!res.writableEnded) {
1472
1714
  try {
1473
1715
  res.end();
1474
1716
  } catch (e) {
1475
1717
  }
1476
1718
  }
1477
- return;
1719
+ } finally {
1720
+ req.off("close", onClose);
1478
1721
  }
1479
1722
  });
1480
1723
  });
@@ -1492,19 +1735,6 @@ var S3BucketUtil = class _S3BucketUtil {
1492
1735
  return cb(new Error(errorMsg));
1493
1736
  };
1494
1737
  }
1495
- getFileSize(maxFileSize) {
1496
- var _a2;
1497
- const fileSizeUnitValue = maxFileSize != null ? maxFileSize : this.maxUploadFileSizeRestriction;
1498
- const fileSize = typeof fileSizeUnitValue === "number" ? fileSizeUnitValue : bytes(fileSizeUnitValue);
1499
- if (!fileSize) {
1500
- (_a2 = this.logger) == null ? void 0 : _a2.warn(this.reqId, "Failed to convert fileSize restriction, proceeding without limit", {
1501
- maxFileSize,
1502
- maxUploadFileSizeRestriction: this.maxUploadFileSizeRestriction
1503
- });
1504
- return void 0;
1505
- }
1506
- return fileSize;
1507
- }
1508
1738
  getUploadFileMW(directoryPath, {
1509
1739
  acl = "private" /* private */,
1510
1740
  maxFileSize,
@@ -1516,10 +1746,10 @@ var S3BucketUtil = class _S3BucketUtil {
1516
1746
  let normalizedPath = getNormalizedPath(directoryPath);
1517
1747
  if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1518
1748
  else normalizedPath = "";
1519
- const fileSize = this.getFileSize(maxFileSize);
1749
+ const fileSize = getFileSize(maxFileSize, this.maxUploadFileSizeRestriction);
1520
1750
  const fileTypes = [].concat(fileType);
1521
1751
  const fileExts = [].concat(fileExt);
1522
- const fileFilter = (fileTypes == null ? void 0 : fileTypes.length) || (fileExts == null ? void 0 : fileExts.length) ? _S3BucketUtil.fileFilter(fileTypes, fileExts) : void 0;
1752
+ const fileFilter = (fileTypes == null ? void 0 : fileTypes.length) || (fileExts == null ? void 0 : fileExts.length) ? _S3Stream.fileFilter(fileTypes, fileExts) : void 0;
1523
1753
  return multer({
1524
1754
  fileFilter,
1525
1755
  limits: __spreadValues({}, fileSize && { fileSize }),
@@ -1556,14 +1786,19 @@ var S3BucketUtil = class _S3BucketUtil {
1556
1786
  * Middleware for uploading a single file
1557
1787
  * Adds the uploaded file info to req.s3File
1558
1788
  */
1559
- uploadSingleFile(fieldName, directory, options = {}) {
1560
- const upload = this.getUploadFileMW(directory, options);
1789
+ uploadSingleFile(fieldName, directoryPath, options = {}) {
1790
+ var _a2;
1791
+ let normalizedPath = getNormalizedPath(directoryPath);
1792
+ if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1793
+ else normalizedPath = "";
1794
+ (_a2 = this.logger) == null ? void 0 : _a2.debug(null, "####### uploadSingleFile", { directoryPath, normalizedPath, fieldName });
1795
+ const upload = this.getUploadFileMW(normalizedPath, options);
1561
1796
  return (req, res, next) => {
1562
1797
  const mw = upload.single(fieldName);
1563
1798
  mw(req, res, (err) => {
1564
- var _a2, _b;
1799
+ var _a3, _b;
1565
1800
  if (err) {
1566
- (_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Single file upload error", { fieldName, error: err.message });
1801
+ (_a3 = this.logger) == null ? void 0 : _a3.error(this.reqId, "Single file upload error", { fieldName, error: err.message });
1567
1802
  return next(err);
1568
1803
  }
1569
1804
  if (req.file) {
@@ -1583,8 +1818,11 @@ var S3BucketUtil = class _S3BucketUtil {
1583
1818
  * Middleware for uploading multiple files with the same field name
1584
1819
  * Adds the uploaded files info to req.s3Files
1585
1820
  */
1586
- uploadMultipleFiles(fieldName, directory, options = {}) {
1587
- const upload = this.getUploadFileMW(directory, options);
1821
+ uploadMultipleFiles(fieldName, directoryPath, options = {}) {
1822
+ let normalizedPath = getNormalizedPath(directoryPath);
1823
+ if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1824
+ else normalizedPath = "";
1825
+ const upload = this.getUploadFileMW(normalizedPath, options);
1588
1826
  return (req, res, next) => {
1589
1827
  const mw = upload.array(fieldName, options.maxFilesCount || void 0);
1590
1828
  mw(req, res, (err) => {
@@ -1605,49 +1843,15 @@ var S3BucketUtil = class _S3BucketUtil {
1605
1843
  });
1606
1844
  };
1607
1845
  }
1608
- /**
1609
- * Middleware for uploading multiple files with different field names
1610
- * Adds the uploaded files info to req.s3FilesByField
1611
- */
1612
- uploadFieldsFiles(fields) {
1613
- const fieldConfigs = fields.map((field) => {
1614
- const upload = this.getUploadFileMW(field.directory, field.options || {});
1615
- return {
1616
- name: field.name,
1617
- maxCount: field.maxCount || 1,
1618
- upload,
1619
- directory: field.directory
1620
- };
1621
- });
1622
- return (req, res, next) => __async(this, null, function* () {
1623
- const multerFields = fieldConfigs.map((f) => ({ name: f.name, maxCount: f.maxCount }));
1624
- const upload = this.getUploadFileMW(fieldConfigs[0].directory);
1625
- const mw = upload.fields(multerFields);
1626
- mw(req, res, (err) => {
1627
- var _a2, _b;
1628
- if (err) {
1629
- (_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Fields upload error", { error: err.message });
1630
- return next(err);
1631
- }
1632
- if (req.files && typeof req.files === "object" && !Array.isArray(req.files)) {
1633
- req.s3FilesByField = req.files;
1634
- const uploadSummary = Object.entries(req.s3FilesByField).map(([field, files]) => ({
1635
- field,
1636
- count: files.length,
1637
- keys: files.map((f) => f.key)
1638
- }));
1639
- (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Fields uploaded successfully", { uploadSummary });
1640
- }
1641
- next();
1642
- });
1643
- });
1644
- }
1645
1846
  /**
1646
1847
  * Middleware for uploading any files (mixed field names)
1647
1848
  * Adds the uploaded files info to req.s3AllFiles
1648
1849
  */
1649
- uploadAnyFiles(directory, maxCount, options = {}) {
1650
- const upload = this.getUploadFileMW(directory, options);
1850
+ uploadAnyFiles(directoryPath, maxCount, options = {}) {
1851
+ let normalizedPath = getNormalizedPath(directoryPath);
1852
+ if (normalizedPath !== "/" && normalizedPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1853
+ else normalizedPath = "";
1854
+ const upload = this.getUploadFileMW(normalizedPath, options);
1651
1855
  return (req, res, next) => {
1652
1856
  const anyUpload = maxCount ? upload.any() : upload.any();
1653
1857
  anyUpload(req, res, (err) => {
@@ -1670,6 +1874,159 @@ var S3BucketUtil = class _S3BucketUtil {
1670
1874
  });
1671
1875
  };
1672
1876
  }
1877
+ /**
1878
+ * Middleware for uploading multiple files with different field names
1879
+ * Adds the uploaded files info to req.s3FilesByField
1880
+ */
1881
+ // uploadFieldsFiles(
1882
+ // fields: Array<{ name: string; directory: string; maxCount?: number; options?: S3UploadOptions }>
1883
+ // ): RequestHandler {
1884
+ // // Create separate multer instances for each field (since each might have different options)
1885
+ // const fieldConfigs = fields.map((field) => {
1886
+ // const upload = this.getUploadFileMW(field.directory, field.options || {});
1887
+ //
1888
+ // return {
1889
+ // name: getNormalizedPath(field.name),
1890
+ // directory: getNormalizedPath(field.directory),
1891
+ // maxCount: field.maxCount || 1,
1892
+ // upload,
1893
+ // };
1894
+ // });
1895
+ //
1896
+ // return async (
1897
+ // req: Request & { s3FilesByField?: Record<string, UploadedS3File[]> } & any,
1898
+ // res: Response,
1899
+ // next: NextFunction & any
1900
+ // ) => {
1901
+ // // We'll use the first upload instance but with fields configuration
1902
+ // const multerFields = fieldConfigs.map((f) => ({ name: f.name, maxCount: f.maxCount }));
1903
+ // const upload = this.getUploadFileMW(fieldConfigs[0].directory);
1904
+ //
1905
+ // const mw: RequestHandler & any = upload.fields(multerFields);
1906
+ // mw(req, res, (err: any) => {
1907
+ // if (err) {
1908
+ // this.logger?.error(this.reqId, 'Fields upload error', { error: err.message });
1909
+ // return next(err);
1910
+ // }
1911
+ //
1912
+ // if (req.files && typeof req.files === 'object' && !Array.isArray(req.files)) {
1913
+ // req.s3FilesByField = req.files as Record<string, UploadedS3File[]>;
1914
+ //
1915
+ // const uploadSummary = Object.entries(req.s3FilesByField).map(([field, files]: any) => ({
1916
+ // field,
1917
+ // count: files.length,
1918
+ // keys: files.map((f: any) => f.key),
1919
+ // }));
1920
+ //
1921
+ // this.logger?.info(this.reqId, 'Fields uploaded successfully', { uploadSummary });
1922
+ // }
1923
+ //
1924
+ // next();
1925
+ // });
1926
+ // };
1927
+ // }
1928
+ };
1929
+
1930
+ // src/aws/s3/s3-util.ts
1931
+ var S3Util = class extends S3Stream {
1932
+ constructor(props) {
1933
+ super(props);
1934
+ }
1935
+ };
1936
+
1937
+ // src/aws/s3/s3-util.localstack.ts
1938
+ var S3LocalstackUtil = class extends S3Util {
1939
+ constructor(props) {
1940
+ super(__spreadProps(__spreadValues({}, props), { localstack: true }));
1941
+ }
1942
+ // todo: checked!
1943
+ directoryList(directoryPath) {
1944
+ return __async(this, null, function* () {
1945
+ var _a2;
1946
+ let normalizedPath = getNormalizedPath(directoryPath);
1947
+ if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1948
+ else normalizedPath = "";
1949
+ let result;
1950
+ result = yield this.execute(
1951
+ new ListObjectsV2Command4({
1952
+ Bucket: this.bucket,
1953
+ Prefix: normalizedPath,
1954
+ Delimiter: "/"
1955
+ })
1956
+ );
1957
+ (_a2 = this.logger) == null ? void 0 : _a2.debug(null, "#### directoryList", {
1958
+ normalizedPath,
1959
+ CommonPrefixes: result.CommonPrefixes,
1960
+ ContentFile: result.Contents
1961
+ });
1962
+ const directories = (result.CommonPrefixes || []).map((prefix) => prefix.Prefix).map((prefix) => {
1963
+ const relativePath = prefix.replace(normalizedPath, "");
1964
+ const dir = relativePath.replace(/\/$/, "");
1965
+ return dir;
1966
+ }).filter((dir) => dir);
1967
+ const files = (result.Contents || []).filter((content) => {
1968
+ var _a3;
1969
+ return content.Key !== normalizedPath && !((_a3 = content.Key) == null ? void 0 : _a3.endsWith("/"));
1970
+ }).map((content) => __spreadProps(__spreadValues({}, content), {
1971
+ Name: content.Key.replace(normalizedPath, "") || content.Key,
1972
+ Location: `${this.link}${content.Key.replace(/^\//, "")}`,
1973
+ LastModified: new Date(content.LastModified)
1974
+ }));
1975
+ return { directories, files };
1976
+ });
1977
+ }
1978
+ // todo: checked!
1979
+ directoryListPaginated(_0) {
1980
+ return __async(this, arguments, function* (directoryPath, {
1981
+ pageSize = 100,
1982
+ pageNumber = 0
1983
+ // 0-based: page 0 = items 0-99, page 1 = items 100-199, page 2 = items 200-299
1984
+ } = {}) {
1985
+ let normalizedPath = getNormalizedPath(directoryPath);
1986
+ if (normalizedPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
1987
+ else normalizedPath = this.localstack ? "" : "/";
1988
+ let continuationToken = void 0;
1989
+ let currentPage = 0;
1990
+ let allDirectories = [];
1991
+ let allFiles = [];
1992
+ while (currentPage <= pageNumber) {
1993
+ let result;
1994
+ result = yield this.execute(
1995
+ new ListObjectsV2Command4({
1996
+ Bucket: this.bucket,
1997
+ Prefix: normalizedPath,
1998
+ Delimiter: "/",
1999
+ MaxKeys: pageSize,
2000
+ ContinuationToken: continuationToken
2001
+ })
2002
+ );
2003
+ if (currentPage === pageNumber) {
2004
+ allDirectories = (result.CommonPrefixes || []).map((prefix) => prefix.Prefix).map((prefix) => {
2005
+ const relativePath = prefix.replace(normalizedPath, "");
2006
+ return relativePath.replace(/\/$/, "");
2007
+ }).filter((dir) => dir);
2008
+ allFiles = (result.Contents || []).filter((content) => {
2009
+ var _a2;
2010
+ return content.Key !== normalizedPath && !((_a2 = content.Key) == null ? void 0 : _a2.endsWith("/"));
2011
+ }).map((content) => __spreadProps(__spreadValues({}, content), {
2012
+ Name: content.Key.replace(normalizedPath, "") || content.Key,
2013
+ Location: `${this.link}${content.Key.replace(/^\//, "")}`,
2014
+ LastModified: new Date(content.LastModified)
2015
+ }));
2016
+ }
2017
+ continuationToken = result.NextContinuationToken;
2018
+ if (!result.IsTruncated || !continuationToken) {
2019
+ break;
2020
+ }
2021
+ currentPage++;
2022
+ }
2023
+ return {
2024
+ directories: allDirectories,
2025
+ files: allFiles,
2026
+ totalFetched: allFiles.length + allDirectories.length
2027
+ };
2028
+ });
2029
+ }
1673
2030
  };
1674
2031
 
1675
2032
  // src/aws/sns.ts
@@ -1707,6 +2064,7 @@ export {
1707
2064
  AWSConfigSharingUtil,
1708
2065
  IAMUtil,
1709
2066
  LambdaUtil,
1710
- S3BucketUtil,
2067
+ S3LocalstackUtil,
2068
+ S3Util,
1711
2069
  SNSUtil
1712
2070
  };