@hdriel/aws-utils 1.0.0

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 ADDED
@@ -0,0 +1,1648 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
13
+ var __pow = Math.pow;
14
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
15
+ var __spreadValues = (a, b) => {
16
+ for (var prop in b || (b = {}))
17
+ if (__hasOwnProp.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ if (__getOwnPropSymbols)
20
+ for (var prop of __getOwnPropSymbols(b)) {
21
+ if (__propIsEnum.call(b, prop))
22
+ __defNormalProp(a, prop, b[prop]);
23
+ }
24
+ return a;
25
+ };
26
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
27
+ var __export = (target, all) => {
28
+ for (var name in all)
29
+ __defProp(target, name, { get: all[name], enumerable: true });
30
+ };
31
+ var __copyProps = (to, from, except, desc) => {
32
+ if (from && typeof from === "object" || typeof from === "function") {
33
+ for (let key of __getOwnPropNames(from))
34
+ if (!__hasOwnProp.call(to, key) && key !== except)
35
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
36
+ }
37
+ return to;
38
+ };
39
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
40
+ // If the importer is in node compatibility mode or this is not an ESM
41
+ // file that has been converted to a CommonJS file using a Babel-
42
+ // compatible transform (i.e. "__esModule" has not been set), then set
43
+ // "default" to the CommonJS "module.exports" for node compatibility.
44
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
45
+ mod
46
+ ));
47
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
48
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
49
+ var __async = (__this, __arguments, generator) => {
50
+ return new Promise((resolve, reject) => {
51
+ var fulfilled = (value) => {
52
+ try {
53
+ step(generator.next(value));
54
+ } catch (e) {
55
+ reject(e);
56
+ }
57
+ };
58
+ var rejected = (value) => {
59
+ try {
60
+ step(generator.throw(value));
61
+ } catch (e) {
62
+ reject(e);
63
+ }
64
+ };
65
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
66
+ step((generator = generator.apply(__this, __arguments)).next());
67
+ });
68
+ };
69
+ var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it);
70
+
71
+ // src/index.ts
72
+ var index_exports = {};
73
+ __export(index_exports, {
74
+ ACLs: () => ACLs,
75
+ AWSConfigSharingUtil: () => AWSConfigSharingUtil,
76
+ IAMUtil: () => IAMUtil,
77
+ LambdaUtil: () => LambdaUtil,
78
+ S3BucketUtil: () => S3BucketUtil,
79
+ SNSUtil: () => SNSUtil
80
+ });
81
+ module.exports = __toCommonJS(index_exports);
82
+
83
+ // src/aws/iam.ts
84
+ var import_client_iam = require("@aws-sdk/client-iam");
85
+
86
+ // src/aws/configuration.ts
87
+ var _AWSConfigSharingUtil = class _AWSConfigSharingUtil {
88
+ constructor() {
89
+ }
90
+ static setConfig({
91
+ accessKeyId,
92
+ secretAccessKey,
93
+ endpoint,
94
+ region
95
+ }) {
96
+ _AWSConfigSharingUtil.accessKeyId = accessKeyId;
97
+ _AWSConfigSharingUtil.secretAccessKey = secretAccessKey;
98
+ _AWSConfigSharingUtil.endpoint = endpoint;
99
+ _AWSConfigSharingUtil.region = region;
100
+ }
101
+ static getConfig() {
102
+ return {
103
+ accessKeyId: _AWSConfigSharingUtil.accessKeyId,
104
+ secretAccessKey: _AWSConfigSharingUtil.secretAccessKey,
105
+ region: _AWSConfigSharingUtil.region,
106
+ endpoint: _AWSConfigSharingUtil.endpoint
107
+ };
108
+ }
109
+ };
110
+ __publicField(_AWSConfigSharingUtil, "accessKeyId");
111
+ __publicField(_AWSConfigSharingUtil, "secretAccessKey");
112
+ __publicField(_AWSConfigSharingUtil, "endpoint");
113
+ __publicField(_AWSConfigSharingUtil, "region");
114
+ var AWSConfigSharingUtil = _AWSConfigSharingUtil;
115
+
116
+ // src/aws/iam.ts
117
+ var IAMUtil = class {
118
+ constructor({
119
+ accessKeyId = AWSConfigSharingUtil.accessKeyId,
120
+ secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
121
+ endpoint = AWSConfigSharingUtil.endpoint,
122
+ region = AWSConfigSharingUtil.region,
123
+ debug = false
124
+ } = {}) {
125
+ __publicField(this, "iam");
126
+ const credentials = { accessKeyId, secretAccessKey };
127
+ const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
128
+ if (debug) {
129
+ console.log("IAMUtil client options", options);
130
+ }
131
+ this.iam = new import_client_iam.IAMClient(options);
132
+ }
133
+ get client() {
134
+ return this.iam;
135
+ }
136
+ getUserList() {
137
+ return __async(this, null, function* () {
138
+ const command = new import_client_iam.ListUsersCommand({});
139
+ return this.iam.send(command);
140
+ });
141
+ }
142
+ listUsers(maxItems) {
143
+ return __async(this, null, function* () {
144
+ try {
145
+ const command = new import_client_iam.ListUsersCommand({ MaxItems: maxItems });
146
+ const response = yield this.iam.send(command);
147
+ return response.Users;
148
+ } catch (error) {
149
+ console.error("Error listing IAM users:", error);
150
+ return null;
151
+ }
152
+ });
153
+ }
154
+ };
155
+
156
+ // src/aws/lambda.ts
157
+ var import_client_lambda = require("@aws-sdk/client-lambda");
158
+
159
+ // src/utils/logger.ts
160
+ var import_stack_trace_logger = require("stack-trace-logger");
161
+ var includeCloudWatchOptions = process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY && process.env.AWS_REGION && process.env.AWS_LOG_GROUP_NAME && process.env.AWS_LOG_STREAM_NAME;
162
+ var _a;
163
+ var logger = new import_stack_trace_logger.Logger(__spreadValues({
164
+ serviceName: process.env.SERVICE_NAME || "SERVER",
165
+ loggingModeLevel: process.env.LOGGING_MODE,
166
+ lineTraceLevels: (_a = process.env.LOGGING_STACK_TRACE_LEVELS) == null ? void 0 : _a.split(","),
167
+ stackTraceLines: { error: 3, warn: 3, info: 1 },
168
+ tags: ["reqId?", "url?"],
169
+ runLocally: true
170
+ }, includeCloudWatchOptions && {
171
+ transportCloudWatchOptions: {
172
+ awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
173
+ awsSecretKey: process.env.AWS_SECRET_ACCESS_KEY,
174
+ awsRegion: process.env.AWS_REGION,
175
+ logGroupName: process.env.AWS_LOG_GROUP_NAME,
176
+ logStreamName: process.env.AWS_LOG_STREAM_NAME,
177
+ retentionInDays: +process.env.AWS_LOG_RETENTION_IN_DAY
178
+ }
179
+ }));
180
+
181
+ // src/aws/lambda.ts
182
+ var LambdaUtil = class {
183
+ constructor({
184
+ accessKeyId = AWSConfigSharingUtil.accessKeyId,
185
+ secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
186
+ endpoint = AWSConfigSharingUtil.endpoint,
187
+ region = AWSConfigSharingUtil.region,
188
+ serviceFunctionName,
189
+ debug = false
190
+ }) {
191
+ __publicField(this, "lambda");
192
+ __publicField(this, "serviceFunctionName");
193
+ const credentials = { accessKeyId, secretAccessKey };
194
+ const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
195
+ if (debug) {
196
+ console.log("LambdaUtil client options", options);
197
+ }
198
+ this.serviceFunctionName = serviceFunctionName;
199
+ this.lambda = new import_client_lambda.Lambda(__spreadValues(__spreadValues(__spreadValues({}, credentials && { credentials }), endpoint && { endpoint }), region && { region }));
200
+ }
201
+ directInvoke(_0) {
202
+ return __async(this, arguments, function* ({
203
+ payload = {},
204
+ invocationType = import_client_lambda.InvocationType.Event
205
+ }) {
206
+ var _a2;
207
+ const Payload = JSON.stringify(payload);
208
+ try {
209
+ const command = new import_client_lambda.InvokeCommand({
210
+ FunctionName: this.serviceFunctionName,
211
+ Payload,
212
+ InvocationType: invocationType
213
+ });
214
+ const data = yield this.lambda.send(command);
215
+ if (invocationType === import_client_lambda.InvocationType.RequestResponse) {
216
+ logger.info(null, "directInvoke lambda function response", { FunctionName, data });
217
+ }
218
+ const status = (_a2 = data.StatusCode) != null ? _a2 : 200;
219
+ const result = data.Payload;
220
+ return status >= 200 && status < 300 ? result : Promise.reject(result);
221
+ } catch (err) {
222
+ logger.error(null, "failed to directInvoke lambda function", {
223
+ err,
224
+ Payload,
225
+ FunctionName
226
+ });
227
+ throw err;
228
+ }
229
+ });
230
+ }
231
+ runLambdaInDryRunMode(payload) {
232
+ return __async(this, null, function* () {
233
+ return this.directInvoke({
234
+ payload,
235
+ invocationType: import_client_lambda.InvocationType.DryRun
236
+ });
237
+ });
238
+ }
239
+ triggerLambdaEvent(payload) {
240
+ return __async(this, null, function* () {
241
+ return this.directInvoke({
242
+ payload,
243
+ invocationType: import_client_lambda.InvocationType.Event
244
+ });
245
+ });
246
+ }
247
+ runAndGetLambdaResponse(payload) {
248
+ return __async(this, null, function* () {
249
+ return this.directInvoke({
250
+ payload,
251
+ invocationType: import_client_lambda.InvocationType.RequestResponse
252
+ });
253
+ });
254
+ }
255
+ };
256
+
257
+ // src/aws/s3-bucket.ts
258
+ var import_ms = __toESM(require("ms"), 1);
259
+ var import_pathe = __toESM(require("pathe"), 1);
260
+ var import_http = __toESM(require("http"), 1);
261
+ var import_https = __toESM(require("https"), 1);
262
+ var import_stream = require("stream");
263
+ 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");
268
+ var import_archiver = __toESM(require("archiver"), 1);
269
+ var import_node_stream = require("stream");
270
+ var import_client_s3 = require("@aws-sdk/client-s3");
271
+
272
+ // src/utils/consts.ts
273
+ var ACLs = /* @__PURE__ */ ((ACLs2) => {
274
+ ACLs2["private"] = "private";
275
+ ACLs2["publicRead"] = "public-read";
276
+ ACLs2["publicReadWrite"] = "public-read-write";
277
+ return ACLs2;
278
+ })(ACLs || {});
279
+
280
+ // src/utils/concurrency.ts
281
+ var import_p_limit = __toESM(require("p-limit"), 1);
282
+ var s3Limiter = (0, import_p_limit.default)(4);
283
+
284
+ // src/aws/s3-bucket.ts
285
+ var import_multer = __toESM(require("multer"), 1);
286
+ var import_multer_s3 = __toESM(require("multer-s3"), 1);
287
+ var import_bytes = __toESM(require("bytes"), 1);
288
+ var pump = (0, import_util.promisify)(import_stream.pipeline);
289
+ var parseRangeHeader = (range, contentLength, chunkSize) => {
290
+ if (!range || !range.startsWith("bytes=")) return null;
291
+ const rangeParts = range.replace("bytes=", "").split("-");
292
+ const start = parseInt(rangeParts[0], 10);
293
+ let end = parseInt(rangeParts[1], 10);
294
+ end = end || start + chunkSize - 1;
295
+ if (isNaN(start) || start < 0 || start >= contentLength) return null;
296
+ if (isNaN(end) || end < start || end >= contentLength) {
297
+ return [start, contentLength - 1];
298
+ }
299
+ return [start, Math.min(end, end)];
300
+ };
301
+ var S3BucketUtil = class _S3BucketUtil {
302
+ constructor({
303
+ logger: logger2,
304
+ bucket,
305
+ reqId,
306
+ accessKeyId = AWSConfigSharingUtil.accessKeyId,
307
+ secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
308
+ endpoint = AWSConfigSharingUtil.endpoint,
309
+ region = AWSConfigSharingUtil.region,
310
+ s3ForcePathStyle = true,
311
+ maxUploadFileSizeRestriction = "10GB"
312
+ }) {
313
+ __publicField(this, "s3Client");
314
+ __publicField(this, "bucket");
315
+ __publicField(this, "endpoint");
316
+ __publicField(this, "region");
317
+ __publicField(this, "logger");
318
+ __publicField(this, "reqId");
319
+ __publicField(this, "maxUploadFileSizeRestriction");
320
+ const credentials = { accessKeyId, secretAccessKey };
321
+ const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
322
+ this.endpoint = endpoint;
323
+ this.region = region;
324
+ this.bucket = bucket;
325
+ this.logger = logger2;
326
+ this.reqId = reqId != null ? reqId : null;
327
+ this.maxUploadFileSizeRestriction = maxUploadFileSizeRestriction;
328
+ const s3ClientParams = __spreadProps(__spreadValues(__spreadValues({}, options), s3ForcePathStyle && { forcePathStyle: s3ForcePathStyle }), {
329
+ requestHandler: new import_node_http_handler.NodeHttpHandler({
330
+ httpAgent: new import_http.default.Agent({ keepAlive: true, maxSockets: 300 }),
331
+ httpsAgent: new import_https.default.Agent({ keepAlive: true, maxSockets: 300 }),
332
+ connectionTimeout: 3e3,
333
+ socketTimeout: 3e4
334
+ })
335
+ });
336
+ this.s3Client = new import_client_s3.S3Client(s3ClientParams);
337
+ }
338
+ get link() {
339
+ return this.endpoint === "http://localhost:4566" ? `${this.endpoint}/${this.bucket}/` : `https://s3.${this.region}.amazonaws.com/${this.bucket}/`;
340
+ }
341
+ execute(command, options) {
342
+ return __async(this, null, function* () {
343
+ return this.s3Client.send(command, options);
344
+ });
345
+ }
346
+ // ##### BUCKET BLOCK ##########################
347
+ getBucketList() {
348
+ return __async(this, arguments, function* (options = {}, includePublicAccess = false) {
349
+ const command = new import_client_s3.ListBucketsCommand(options);
350
+ const response = yield this.execute(command);
351
+ const responseData = (response == null ? void 0 : response.Buckets) || null;
352
+ if (!responseData) return null;
353
+ if (includePublicAccess) {
354
+ yield Promise.allSettled(
355
+ responseData.map((data) => __async(this, null, function* () {
356
+ const result = yield this.execute(
357
+ new import_client_s3.GetPublicAccessBlockCommand({ Bucket: data.Name })
358
+ );
359
+ data.PublicAccessBlockConfiguration = result.PublicAccessBlockConfiguration;
360
+ }))
361
+ );
362
+ }
363
+ return responseData;
364
+ });
365
+ }
366
+ isBucketExists() {
367
+ return __async(this, null, function* () {
368
+ var _a2, _b;
369
+ const bucketName = this.bucket;
370
+ try {
371
+ yield this.execute(new import_client_s3.HeadBucketCommand({ Bucket: bucketName }));
372
+ return true;
373
+ } catch (err) {
374
+ if (err.name !== "NotFound" && ((_a2 = err.$metadata) == null ? void 0 : _a2.httpStatusCode) !== 404) {
375
+ (_b = this.logger) == null ? void 0 : _b.error(this.reqId, "Error checking bucket:", err);
376
+ throw err;
377
+ } else {
378
+ return false;
379
+ }
380
+ }
381
+ });
382
+ }
383
+ initAsPublicBucket() {
384
+ return __async(this, null, function* () {
385
+ var _a2, _b;
386
+ const bucketName = this.bucket;
387
+ const isExists = yield this.isBucketExists();
388
+ if (isExists) {
389
+ (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
390
+ return;
391
+ }
392
+ const data = yield this.execute(new import_client_s3.CreateBucketCommand({ Bucket: bucketName }));
393
+ CREATE_PUBLICK_ACCESS_BLOCK: {
394
+ const command = new import_client_s3.PutPublicAccessBlockCommand({
395
+ Bucket: bucketName,
396
+ PublicAccessBlockConfiguration: {
397
+ BlockPublicAcls: false,
398
+ IgnorePublicAcls: false,
399
+ BlockPublicPolicy: false,
400
+ RestrictPublicBuckets: false
401
+ }
402
+ });
403
+ yield this.execute(command);
404
+ }
405
+ UPDATE_PUBLICK_ACCESS_POLICY: {
406
+ const policy = {
407
+ Version: "2012-10-17",
408
+ Statement: [
409
+ {
410
+ Sid: "PublicReadGetObject",
411
+ Effect: "Allow",
412
+ Principal: "*",
413
+ Action: "s3:GetObject",
414
+ Resource: `arn:aws:s3:::${bucketName}/*`
415
+ }
416
+ ]
417
+ };
418
+ const command = new import_client_s3.PutBucketPolicyCommand({ Bucket: bucketName, Policy: JSON.stringify(policy) });
419
+ yield this.execute(command);
420
+ }
421
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, `Public bucket created successfully.`, { bucketName });
422
+ return data;
423
+ });
424
+ }
425
+ initAsPrivateBucket(includeConstraintLocation) {
426
+ return __async(this, null, function* () {
427
+ var _a2, _b;
428
+ const bucketName = this.bucket;
429
+ const isExists = yield this.isBucketExists();
430
+ if (isExists) {
431
+ (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
432
+ return;
433
+ }
434
+ const createParams = __spreadValues({
435
+ Bucket: bucketName
436
+ }, includeConstraintLocation && {
437
+ CreateBucketConfiguration: { LocationConstraint: this.region }
438
+ });
439
+ const data = yield this.execute(new import_client_s3.CreateBucketCommand(createParams));
440
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, `Private bucket created successfully.`, { bucketName });
441
+ return data;
442
+ });
443
+ }
444
+ initBucket() {
445
+ return __async(this, arguments, function* (acl = "private" /* private */, includeConstraintLocation = false) {
446
+ var _a2;
447
+ const bucketName = this.bucket;
448
+ const isExists = yield this.isBucketExists();
449
+ if (isExists) {
450
+ (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, `Bucket already exists.`, { bucketName });
451
+ return;
452
+ }
453
+ const data = acl === "private" /* private */ ? yield this.initAsPrivateBucket(includeConstraintLocation) : yield this.initAsPublicBucket();
454
+ return data;
455
+ });
456
+ }
457
+ emptyBucket() {
458
+ return __async(this, null, function* () {
459
+ let ContinuationToken = void 0;
460
+ do {
461
+ const listResp = yield this.execute(
462
+ new import_client_s3.ListObjectsV2Command({
463
+ Bucket: this.bucket,
464
+ ContinuationToken
465
+ })
466
+ );
467
+ if (listResp.Contents && listResp.Contents.length > 0) {
468
+ yield this.execute(
469
+ new import_client_s3.DeleteObjectsCommand({
470
+ Bucket: this.bucket,
471
+ Delete: {
472
+ Objects: listResp.Contents.map((obj) => ({ Key: obj.Key }))
473
+ }
474
+ })
475
+ );
476
+ }
477
+ ContinuationToken = listResp.NextContinuationToken;
478
+ } while (ContinuationToken);
479
+ });
480
+ }
481
+ bucketInfo(options) {
482
+ return __async(this, null, function* () {
483
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
484
+ const bucketName = this.bucket;
485
+ const info = {
486
+ name: bucketName,
487
+ region: this.region,
488
+ endpoint: this.endpoint,
489
+ exists: false
490
+ };
491
+ try {
492
+ try {
493
+ const headBucketResponse = yield this.execute(
494
+ new import_client_s3.HeadBucketCommand(__spreadValues({ Bucket: bucketName }, options))
495
+ );
496
+ (_a2 = this.logger) == null ? void 0 : _a2.debug("bucketInfo", "HeadBucketCommandOutput", headBucketResponse);
497
+ info.exists = true;
498
+ info.bucketRegion = headBucketResponse.BucketRegion;
499
+ info.accessPointAlias = headBucketResponse.AccessPointAlias;
500
+ } catch (err) {
501
+ if (err.name === "NotFound" || ((_b = err.$metadata) == null ? void 0 : _b.httpStatusCode) === 404) {
502
+ return info;
503
+ }
504
+ throw err;
505
+ }
506
+ try {
507
+ const buckets = yield this.getBucketList({ Prefix: this.bucket, BucketRegion: this.region });
508
+ (_c = this.logger) == null ? void 0 : _c.debug("bucketInfo", "getBucketList", { buckets });
509
+ const bucket = buckets == null ? void 0 : buckets.find((b) => b.Name === bucketName);
510
+ if (bucket == null ? void 0 : bucket.CreationDate) {
511
+ info.creationDate = bucket.CreationDate;
512
+ }
513
+ } catch (error) {
514
+ (_d = this.logger) == null ? void 0 : _d.warn(this.reqId, "Failed to get bucket creation date", { bucketName, error });
515
+ }
516
+ try {
517
+ const aclResponse = yield this.execute(
518
+ new import_client_s3.GetBucketAclCommand({ Bucket: bucketName })
519
+ );
520
+ (_e = this.logger) == null ? void 0 : _e.debug("bucketInfo", "GetBucketAclCommandOutput", aclResponse);
521
+ info.acl = (_f = aclResponse.Grants) == null ? void 0 : _f.map((grant) => {
522
+ var _a3;
523
+ return {
524
+ grantee: (_a3 = grant.Grantee) == null ? void 0 : _a3.Type,
525
+ permission: grant.Permission
526
+ };
527
+ });
528
+ } catch (error) {
529
+ (_g = this.logger) == null ? void 0 : _g.warn(this.reqId, "Failed to get bucket ACL", { bucketName, error });
530
+ }
531
+ try {
532
+ const publicAccessResponse = yield this.execute(
533
+ new import_client_s3.GetPublicAccessBlockCommand({ Bucket: bucketName })
534
+ );
535
+ (_h = this.logger) == null ? void 0 : _h.debug("bucketInfo", "GetPublicAccessBlockCommandOutput", publicAccessResponse);
536
+ info.publicAccessBlock = publicAccessResponse.PublicAccessBlockConfiguration;
537
+ } catch (error) {
538
+ if (error.name !== "NoSuchPublicAccessBlockConfiguration") {
539
+ (_i = this.logger) == null ? void 0 : _i.warn(this.reqId, "Failed to get public access block", { bucketName, error });
540
+ }
541
+ }
542
+ try {
543
+ const policyResponse = yield this.execute(
544
+ new import_client_s3.GetBucketPolicyCommand({ Bucket: bucketName })
545
+ );
546
+ (_j = this.logger) == null ? void 0 : _j.debug("bucketInfo", "GetBucketPolicyCommandOutput", policyResponse);
547
+ if (policyResponse.Policy) {
548
+ info.policy = JSON.parse(policyResponse.Policy);
549
+ }
550
+ } catch (error) {
551
+ if (error.name !== "NoSuchBucketPolicy") {
552
+ (_k = this.logger) == null ? void 0 : _k.warn(this.reqId, "Failed to get bucket policy", { bucketName, error });
553
+ }
554
+ }
555
+ try {
556
+ const versioningResponse = yield this.execute(
557
+ new import_client_s3.GetBucketVersioningCommand({ Bucket: bucketName })
558
+ );
559
+ (_l = this.logger) == null ? void 0 : _l.debug("bucketInfo", "GetBucketVersioningCommandOutput", versioningResponse);
560
+ info.versioning = versioningResponse.Status || "Disabled";
561
+ } catch (error) {
562
+ (_m = this.logger) == null ? void 0 : _m.warn(this.reqId, "Failed to get bucket versioning", { bucketName, error });
563
+ }
564
+ try {
565
+ const encryptionResponse = yield this.execute(
566
+ new import_client_s3.GetBucketEncryptionCommand({ Bucket: bucketName })
567
+ );
568
+ (_n = this.logger) == null ? void 0 : _n.debug("bucketInfo", "GetBucketEncryptionCommandOutput", encryptionResponse);
569
+ info.encryption = {
570
+ enabled: true,
571
+ type: (_r = (_q = (_p = (_o = encryptionResponse.ServerSideEncryptionConfiguration) == null ? void 0 : _o.Rules) == null ? void 0 : _p[0]) == null ? void 0 : _q.ApplyServerSideEncryptionByDefault) == null ? void 0 : _r.SSEAlgorithm
572
+ };
573
+ } catch (error) {
574
+ if (error.name === "ServerSideEncryptionConfigurationNotFoundError") {
575
+ info.encryption = { enabled: false };
576
+ } else {
577
+ (_s = this.logger) == null ? void 0 : _s.warn(this.reqId, "Failed to get bucket encryption", { bucketName, error });
578
+ info.encryption = { enabled: false };
579
+ }
580
+ }
581
+ (_t = this.logger) == null ? void 0 : _t.debug("bucketInfo", "bucket info response", info);
582
+ return info;
583
+ } catch (error) {
584
+ (_u = this.logger) == null ? void 0 : _u.error(this.reqId, "Failed to get bucket info", { bucketName, error });
585
+ throw error;
586
+ }
587
+ });
588
+ }
589
+ destroyBucket(forceDeleteAllFilesBeforeDestroyBucket = false) {
590
+ return __async(this, null, function* () {
591
+ var _a2;
592
+ const bucketName = this.bucket;
593
+ const isExists = yield this.isBucketExists();
594
+ if (!isExists) {
595
+ (_a2 = this.logger) == null ? void 0 : _a2.debug(this.reqId, `Bucket not exists.`, { bucketName });
596
+ return;
597
+ }
598
+ if (forceDeleteAllFilesBeforeDestroyBucket) {
599
+ yield this.emptyBucket();
600
+ }
601
+ const createParams = { Bucket: bucketName };
602
+ const data = yield this.execute(new import_client_s3.DeleteBucketCommand(createParams));
603
+ return data;
604
+ });
605
+ }
606
+ // ##### DIRECTORY BLOCK ##########################
607
+ createDirectory(directoryPath) {
608
+ return __async(this, null, function* () {
609
+ let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
610
+ if (!normalizedPath) throw new Error("No directory path provided");
611
+ if (normalizedPath === "/") normalizedPath = "";
612
+ const command = new import_client_s3.PutObjectCommand({ Bucket: this.bucket, Key: `${normalizedPath}/` });
613
+ const result = yield this.execute(command);
614
+ return result;
615
+ });
616
+ }
617
+ deleteDirectory(directoryPath) {
618
+ return __async(this, null, function* () {
619
+ var _a2, _b, _c, _d, _e, _f, _g;
620
+ let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
621
+ if (!normalizedPath) {
622
+ throw new Error("No directory path provided");
623
+ }
624
+ if (normalizedPath === "/") normalizedPath = "";
625
+ let totalDeletedCount = 0;
626
+ let ContinuationToken = void 0;
627
+ do {
628
+ const listResp = yield this.execute(
629
+ new import_client_s3.ListObjectsV2Command({
630
+ Bucket: this.bucket,
631
+ Prefix: normalizedPath,
632
+ ContinuationToken
633
+ })
634
+ );
635
+ if (listResp.Contents && listResp.Contents.length > 0) {
636
+ const deleteResult = yield this.execute(
637
+ new import_client_s3.DeleteObjectsCommand({
638
+ Bucket: this.bucket,
639
+ Delete: {
640
+ Objects: listResp.Contents.map((obj) => ({ Key: obj.Key })),
641
+ Quiet: false
642
+ }
643
+ })
644
+ );
645
+ totalDeletedCount += (_b = (_a2 = deleteResult.Deleted) == null ? void 0 : _a2.length) != null ? _b : 0;
646
+ if (deleteResult.Errors && deleteResult.Errors.length > 0) {
647
+ (_c = this.logger) == null ? void 0 : _c.warn(this.reqId, `Some objects failed to delete`, {
648
+ directoryPath: normalizedPath,
649
+ errors: deleteResult.Errors
650
+ });
651
+ }
652
+ }
653
+ ContinuationToken = listResp.NextContinuationToken;
654
+ } while (ContinuationToken);
655
+ if (totalDeletedCount === 0) {
656
+ const directoryExists = yield this.fileExists(normalizedPath);
657
+ if (!directoryExists) {
658
+ (_d = this.logger) == null ? void 0 : _d.debug(this.reqId, `Directory not found`, { directoryPath: normalizedPath });
659
+ return null;
660
+ }
661
+ }
662
+ try {
663
+ yield this.execute(
664
+ new import_client_s3.DeleteObjectCommand({
665
+ Bucket: this.bucket,
666
+ Key: normalizedPath
667
+ })
668
+ );
669
+ totalDeletedCount++;
670
+ } catch (error) {
671
+ if (error.name !== "NotFound" && ((_e = error.$metadata) == null ? void 0 : _e.httpStatusCode) !== 404) {
672
+ (_f = this.logger) == null ? void 0 : _f.warn(this.reqId, `Failed to delete directory marker`, {
673
+ directoryPath: normalizedPath,
674
+ error
675
+ });
676
+ }
677
+ }
678
+ (_g = this.logger) == null ? void 0 : _g.info(this.reqId, `Directory deleted successfully`, {
679
+ directoryPath: normalizedPath,
680
+ deletedCount: totalDeletedCount
681
+ });
682
+ return {
683
+ Deleted: [{ Key: normalizedPath }],
684
+ $metadata: {}
685
+ };
686
+ });
687
+ }
688
+ directoryList(directoryPath) {
689
+ return __async(this, null, function* () {
690
+ let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
691
+ if (directoryPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
692
+ else normalizedPath = "";
693
+ const result = yield this.execute(
694
+ new import_client_s3.ListObjectsV2Command({
695
+ Bucket: this.bucket,
696
+ Prefix: normalizedPath,
697
+ Delimiter: "/"
698
+ })
699
+ );
700
+ const directories = (result.CommonPrefixes || []).map((prefix) => prefix.Prefix).map((prefix) => {
701
+ const relativePath = prefix.replace(normalizedPath, "");
702
+ const dir = relativePath.replace(/\/$/, "");
703
+ return dir;
704
+ }).filter((dir) => dir);
705
+ const files = (result.Contents || []).filter((content) => {
706
+ var _a2;
707
+ return content.Key !== normalizedPath && !((_a2 = content.Key) == null ? void 0 : _a2.endsWith("/"));
708
+ }).map((content) => __spreadProps(__spreadValues({}, content), {
709
+ Name: content.Key.replace(normalizedPath, "") || content.Key,
710
+ LastModified: new Date(content.LastModified)
711
+ }));
712
+ return { directories, files };
713
+ });
714
+ }
715
+ /**
716
+ * Get all files recursively (example for search/indexing)
717
+ * @param directoryPath
718
+ */
719
+ directoryListRecursive(directoryPath) {
720
+ return __async(this, null, function* () {
721
+ let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
722
+ if (directoryPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
723
+ else normalizedPath = "";
724
+ const allDirectories = [];
725
+ const allFiles = [];
726
+ let ContinuationToken = void 0;
727
+ do {
728
+ const command = new import_client_s3.ListObjectsV2Command({
729
+ Bucket: this.bucket,
730
+ Prefix: normalizedPath,
731
+ ContinuationToken
732
+ });
733
+ const result = yield this.execute(command);
734
+ if (result.Contents) {
735
+ for (const content of result.Contents) {
736
+ const fullPath = content.Key;
737
+ const relativePath = fullPath.replace(normalizedPath, "");
738
+ const filename = fullPath.split("/").pop();
739
+ if (fullPath.endsWith("/")) {
740
+ allDirectories.push(relativePath.slice(0, -1));
741
+ } else {
742
+ allFiles.push(__spreadProps(__spreadValues({}, content), {
743
+ Name: filename,
744
+ Path: fullPath,
745
+ LastModified: new Date(content.LastModified)
746
+ }));
747
+ }
748
+ }
749
+ }
750
+ ContinuationToken = result.NextContinuationToken;
751
+ } while (ContinuationToken);
752
+ return { directories: allDirectories, files: allFiles };
753
+ });
754
+ }
755
+ /**
756
+ * Get tree files recursively (example for build file explorer UI)
757
+ * @param directoryPath - the directory start from
758
+ * @example
759
+ * const tree = await s3Util.getDirectoryTree('uploads');
760
+ * // {
761
+ * // name: 'uploads',
762
+ * // path: 'uploads/',
763
+ * // type: 'directory',
764
+ * // children: [
765
+ * // {
766
+ * // name: 'logo.png',
767
+ * // path: 'uploads/logo.png',
768
+ * // type: 'file',
769
+ * // size: 12345,
770
+ * // lastModified: Date
771
+ * // },
772
+ * // {
773
+ * // name: 'images',
774
+ * // path: 'uploads/images/',
775
+ * // type: 'directory',
776
+ * // children: [
777
+ * // { name: 'photo1.jpg', type: 'file', ... },
778
+ * // { name: 'photo2.jpg', type: 'file', ... }
779
+ * // ]
780
+ * // }
781
+ * // ]
782
+ * // }
783
+ */
784
+ directoryTree(directoryPath) {
785
+ return __async(this, null, function* () {
786
+ let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
787
+ const lastDirectory = directoryPath == null ? void 0 : directoryPath.split("/").pop();
788
+ const { directories, files } = yield this.directoryList(normalizedPath);
789
+ if (directoryPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
790
+ else normalizedPath = "";
791
+ const treeNode = {
792
+ path: "/" + normalizedPath,
793
+ name: lastDirectory || this.bucket,
794
+ type: "directory",
795
+ children: []
796
+ };
797
+ for (const file of files) {
798
+ treeNode.children.push({
799
+ path: "/" + file.Key,
800
+ name: file.Name,
801
+ type: "file",
802
+ size: file.Size,
803
+ lastModified: file.LastModified
804
+ });
805
+ }
806
+ for (const dir of directories) {
807
+ const subPath = treeNode.path + dir;
808
+ const subTree = yield this.directoryTree(subPath);
809
+ treeNode.children.push(subTree);
810
+ }
811
+ return treeNode;
812
+ });
813
+ }
814
+ // ##### FILES BLOCK ##########################
815
+ fileInfo(filePath) {
816
+ return __async(this, null, function* () {
817
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
818
+ if (!normalizedKey || normalizedKey === "/") {
819
+ throw new Error("No file key provided");
820
+ }
821
+ const command = new import_client_s3.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
822
+ return yield this.execute(command);
823
+ });
824
+ }
825
+ fileListInfo(directoryPath, fileNamePrefix) {
826
+ return __async(this, null, function* () {
827
+ var _a2, _b;
828
+ let normalizedPath = decodeURIComponent((directoryPath == null ? void 0 : directoryPath.replace(/^\//, "").replace(/\/$/, "")) || "");
829
+ if (directoryPath !== "/" && directoryPath !== "" && directoryPath !== void 0) normalizedPath += "/";
830
+ else normalizedPath = "";
831
+ const prefix = normalizedPath + (fileNamePrefix || "");
832
+ const command = new import_client_s3.ListObjectsCommand({
833
+ Bucket: this.bucket,
834
+ Prefix: prefix,
835
+ Delimiter: "/"
836
+ });
837
+ const result = yield this.execute(command);
838
+ const files = ((_a2 = result.Contents) != null ? _a2 : []).filter((v) => v).map(
839
+ (content) => {
840
+ var _a3, _b2;
841
+ return __spreadProps(__spreadValues({}, content), {
842
+ Name: (_b2 = (_a3 = content.Key) == null ? void 0 : _a3.replace(prefix, "")) != null ? _b2 : content.Key,
843
+ LastModified: content.LastModified ? new Date(content.LastModified) : null
844
+ });
845
+ }
846
+ ).filter((content) => content.Name);
847
+ (_b = this.logger) == null ? void 0 : _b.debug(null, "file list info", { prefix, files });
848
+ return files;
849
+ });
850
+ }
851
+ taggingFile(filePath, tagVersion = "1.0.0") {
852
+ return __async(this, null, function* () {
853
+ try {
854
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
855
+ if (!normalizedKey || normalizedKey === "/") {
856
+ throw new Error("No file key provided");
857
+ }
858
+ const command = new import_client_s3.PutObjectTaggingCommand({
859
+ Bucket: this.bucket,
860
+ Key: normalizedKey,
861
+ Tagging: { TagSet: [{ Key: "version", Value: tagVersion }] }
862
+ });
863
+ yield this.execute(command);
864
+ return true;
865
+ } catch (e) {
866
+ return false;
867
+ }
868
+ });
869
+ }
870
+ fileVersion(filePath) {
871
+ return __async(this, null, function* () {
872
+ var _a2, _b;
873
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
874
+ if (!normalizedKey || normalizedKey === "/") {
875
+ throw new Error("No file key provided");
876
+ }
877
+ const command = new import_client_s3.GetObjectTaggingCommand({ Bucket: this.bucket, Key: normalizedKey });
878
+ const result = yield this.execute(command);
879
+ const tag = (_a2 = result.TagSet) == null ? void 0 : _a2.find((tag2) => tag2.Key === "version");
880
+ return (_b = tag == null ? void 0 : tag.Value) != null ? _b : "";
881
+ });
882
+ }
883
+ fileUrl(filePath, expiresIn = "15m") {
884
+ return __async(this, null, function* () {
885
+ var _a2;
886
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
887
+ if (!normalizedKey || normalizedKey === "/") {
888
+ throw new Error("No file key provided");
889
+ }
890
+ const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : (0, import_ms.default)(expiresIn) / 1e3;
891
+ const command = new import_client_s3.GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
892
+ const url = yield (0, import_s3_request_presigner.getSignedUrl)(this.s3Client, command, {
893
+ expiresIn: expiresInSeconds
894
+ // is using 3600 it's will expire in 1 hour (default is 900 seconds = 15 minutes)
895
+ });
896
+ (_a2 = this.logger) == null ? void 0 : _a2.info(null, "generate signed file url", { url, filePath: normalizedKey, expiresIn });
897
+ return url;
898
+ });
899
+ }
900
+ sizeOf(filePath, unit = "bytes") {
901
+ return __async(this, null, function* () {
902
+ var _a2, _b, _c;
903
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
904
+ if (!normalizedKey || normalizedKey === "/") {
905
+ throw new Error("No file key provided");
906
+ }
907
+ try {
908
+ const command = new import_client_s3.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
909
+ const headObject = yield this.execute(command);
910
+ const bytes2 = (_a2 = headObject.ContentLength) != null ? _a2 : 0;
911
+ switch (unit) {
912
+ case "KB":
913
+ return bytes2 / 1024;
914
+ case "MB":
915
+ return bytes2 / (1024 * 1024);
916
+ case "GB":
917
+ return bytes2 / (1024 * 1024 * 1024);
918
+ default:
919
+ return bytes2;
920
+ }
921
+ } catch (error) {
922
+ if (error.name === "NotFound" || ((_b = error.$metadata) == null ? void 0 : _b.httpStatusCode) === 404) {
923
+ (_c = this.logger) == null ? void 0 : _c.warn(this.reqId, "File not found", { filePath: normalizedKey });
924
+ return 0;
925
+ }
926
+ throw error;
927
+ }
928
+ });
929
+ }
930
+ fileExists(filePath) {
931
+ return __async(this, null, function* () {
932
+ var _a2;
933
+ try {
934
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
935
+ if (!normalizedKey || normalizedKey === "/") {
936
+ throw new Error("No file key provided");
937
+ }
938
+ const command = new import_client_s3.HeadObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
939
+ yield this.execute(command);
940
+ return true;
941
+ } catch (error) {
942
+ if (error.name === "NotFound" || ((_a2 = error.$metadata) == null ? void 0 : _a2.httpStatusCode) === 404) {
943
+ return false;
944
+ }
945
+ throw error;
946
+ }
947
+ });
948
+ }
949
+ fileContent(filePath, format = "buffer") {
950
+ return __async(this, null, function* () {
951
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
952
+ if (!normalizedKey || normalizedKey === "/") {
953
+ throw new Error("No file key provided");
954
+ }
955
+ const command = new import_client_s3.GetObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
956
+ const result = yield this.execute(command);
957
+ if (!result.Body) {
958
+ throw new Error("File body is empty");
959
+ }
960
+ const stream = result.Body;
961
+ const chunks = [];
962
+ try {
963
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
964
+ const chunk = temp.value;
965
+ chunks.push(chunk);
966
+ }
967
+ } catch (temp) {
968
+ error = [temp];
969
+ } finally {
970
+ try {
971
+ more && (temp = iter.return) && (yield temp.call(iter));
972
+ } finally {
973
+ if (error)
974
+ throw error[0];
975
+ }
976
+ }
977
+ const buffer = import_buffer.Buffer.concat(chunks);
978
+ if (format === "base64" || format === "utf8") {
979
+ return buffer.toString(format);
980
+ }
981
+ return buffer;
982
+ });
983
+ }
984
+ uploadFile(_0, _1) {
985
+ return __async(this, arguments, function* (filePath, fileData, acl = "private" /* private */, version = "1.0.0") {
986
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
987
+ if (!normalizedKey || normalizedKey === "/") {
988
+ throw new Error("No file key provided");
989
+ }
990
+ const upload = new import_lib_storage.Upload({
991
+ client: this.s3Client,
992
+ params: {
993
+ Bucket: this.bucket,
994
+ ACL: acl,
995
+ Key: normalizedKey,
996
+ Body: fileData,
997
+ Tagging: `version=${version}`
998
+ }
999
+ });
1000
+ const result = yield upload.done();
1001
+ return {
1002
+ Bucket: this.bucket,
1003
+ Key: normalizedKey,
1004
+ Location: `https://${this.bucket}.s3.amazonaws.com/${normalizedKey}`,
1005
+ test: `${this.link}/${normalizedKey}`,
1006
+ ETag: result.ETag
1007
+ };
1008
+ });
1009
+ }
1010
+ deleteFile(filePath) {
1011
+ return __async(this, null, function* () {
1012
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
1013
+ if (!normalizedKey || normalizedKey === "/") {
1014
+ throw new Error("No file key provided");
1015
+ }
1016
+ const command = new import_client_s3.DeleteObjectCommand({ Bucket: this.bucket, Key: normalizedKey });
1017
+ return yield this.execute(command);
1018
+ });
1019
+ }
1020
+ // ##### STREAMING BLOCK ##########################
1021
+ streamObjectFile(_0) {
1022
+ return __async(this, arguments, function* (filePath, {
1023
+ Range,
1024
+ checkFileExists = true,
1025
+ abortSignal
1026
+ } = {}) {
1027
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
1028
+ if (!normalizedKey || normalizedKey === "/") {
1029
+ throw new Error("No file key provided");
1030
+ }
1031
+ if (checkFileExists) {
1032
+ const isExists = yield this.fileExists(normalizedKey);
1033
+ if (!isExists) return null;
1034
+ }
1035
+ const command = new import_client_s3.GetObjectCommand(__spreadValues({
1036
+ Bucket: this.bucket,
1037
+ Key: normalizedKey
1038
+ }, Range ? { Range } : {}));
1039
+ const response = yield this.execute(command, { abortSignal });
1040
+ if (!response.Body || !(response.Body instanceof import_node_stream.Readable)) {
1041
+ throw new Error("Invalid response body: not a Readable stream");
1042
+ }
1043
+ return response.Body;
1044
+ });
1045
+ }
1046
+ streamVideoFile(_0) {
1047
+ return __async(this, arguments, function* ({
1048
+ filePath,
1049
+ Range,
1050
+ abortSignal
1051
+ }) {
1052
+ var _a2;
1053
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
1054
+ if (!normalizedKey || normalizedKey === "/") {
1055
+ throw new Error("No file key provided");
1056
+ }
1057
+ try {
1058
+ const cmd = new import_client_s3.GetObjectCommand(__spreadValues({
1059
+ Bucket: this.bucket,
1060
+ Key: normalizedKey
1061
+ }, Range ? { Range } : {}));
1062
+ const data = yield s3Limiter(() => this.execute(cmd, { abortSignal }));
1063
+ const body = data.Body;
1064
+ if (!body) return null;
1065
+ return {
1066
+ body,
1067
+ meta: {
1068
+ contentType: data.ContentType,
1069
+ contentLength: data.ContentLength,
1070
+ contentRange: data.ContentRange,
1071
+ acceptRanges: data.AcceptRanges,
1072
+ etag: data.ETag,
1073
+ lastModified: data.LastModified
1074
+ }
1075
+ };
1076
+ } catch (error) {
1077
+ (_a2 = this.logger) == null ? void 0 : _a2.warn(this.reqId, "getS3VideoStream error", {
1078
+ Bucket: this.bucket,
1079
+ filePath: normalizedKey,
1080
+ Range,
1081
+ error
1082
+ });
1083
+ return null;
1084
+ }
1085
+ });
1086
+ }
1087
+ getStreamZipFileCtr(_0) {
1088
+ return __async(this, arguments, function* ({
1089
+ filePath,
1090
+ filename: _filename,
1091
+ compressionLevel = 5
1092
+ }) {
1093
+ return (req, res, next) => __async(this, null, function* () {
1094
+ var _a2, _b, _c, _d, _e;
1095
+ const filePaths = [].concat(filePath).map((filePath2) => {
1096
+ return decodeURIComponent((filePath2 == null ? void 0 : filePath2.replace(/^\//, "").replace(/\/$/, "")) || "");
1097
+ }).filter((v) => v && v !== "/");
1098
+ if (!filePaths.length) {
1099
+ throw new Error("No file keys provided");
1100
+ }
1101
+ let filename = _filename || (/* @__PURE__ */ new Date()).toISOString();
1102
+ filename = filename.endsWith(".zip") ? filename : `${filename}.zip`;
1103
+ const abort = new AbortController();
1104
+ const onClose = () => {
1105
+ abort.abort();
1106
+ };
1107
+ req.once("close", onClose);
1108
+ try {
1109
+ (_a2 = this.logger) == null ? void 0 : _a2.info(this.reqId, "Starting parallel file download...", { fileCount: filePaths.length });
1110
+ const downloadPromises = filePaths.map((filePath2) => __async(this, null, function* () {
1111
+ var _a3, _b2, _c2;
1112
+ try {
1113
+ if (abort.signal.aborted) return null;
1114
+ const stream = yield this.streamObjectFile(filePath2, { abortSignal: abort.signal });
1115
+ if (!stream) {
1116
+ (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "File not found", { filePath: filePath2 });
1117
+ return null;
1118
+ }
1119
+ const chunks = [];
1120
+ try {
1121
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
1122
+ const chunk = temp.value;
1123
+ if (abort.signal.aborted) {
1124
+ stream.destroy();
1125
+ return null;
1126
+ }
1127
+ chunks.push(import_buffer.Buffer.from(chunk));
1128
+ }
1129
+ } catch (temp) {
1130
+ error = [temp];
1131
+ } finally {
1132
+ try {
1133
+ more && (temp = iter.return) && (yield temp.call(iter));
1134
+ } finally {
1135
+ if (error)
1136
+ throw error[0];
1137
+ }
1138
+ }
1139
+ const buffer = import_buffer.Buffer.concat(chunks);
1140
+ const fileName = filePath2.split("/").pop() || filePath2;
1141
+ (_b2 = this.logger) == null ? void 0 : _b2.debug(this.reqId, "File downloaded", {
1142
+ filePath: filePath2,
1143
+ sizeMB: (buffer.length / (1024 * 1024)).toFixed(2)
1144
+ });
1145
+ return { buffer, name: fileName, path: filePath2 };
1146
+ } catch (error2) {
1147
+ (_c2 = this.logger) == null ? void 0 : _c2.warn(this.reqId, "Failed to download file", { filePath: filePath2, error: error2 });
1148
+ return null;
1149
+ }
1150
+ }));
1151
+ const fileBuffers = (yield Promise.all(downloadPromises)).filter(Boolean);
1152
+ if (abort.signal.aborted || fileBuffers.length === 0) {
1153
+ req.off("close", onClose);
1154
+ if (fileBuffers.length === 0) {
1155
+ next(new Error("No files available to zip"));
1156
+ }
1157
+ return;
1158
+ }
1159
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "All files downloaded, measuring zip size...", {
1160
+ fileCount: fileBuffers.length,
1161
+ totalSizeMB: (fileBuffers.reduce((sum, f) => sum + f.buffer.length, 0) / (1024 * 1024)).toFixed(2)
1162
+ });
1163
+ const measureArchive = (0, import_archiver.default)("zip", { zlib: { level: compressionLevel } });
1164
+ let actualZipSize = 0;
1165
+ measureArchive.on("data", (chunk) => {
1166
+ actualZipSize += chunk.length;
1167
+ });
1168
+ for (const file of fileBuffers) {
1169
+ if (abort.signal.aborted) break;
1170
+ measureArchive.append(file.buffer, { name: file.name });
1171
+ }
1172
+ yield measureArchive.finalize();
1173
+ if (abort.signal.aborted) {
1174
+ req.off("close", onClose);
1175
+ return;
1176
+ }
1177
+ (_c = this.logger) == null ? void 0 : _c.info(this.reqId, "Zip size calculated", {
1178
+ actualZipSize,
1179
+ sizeMB: (actualZipSize / (1024 * 1024)).toFixed(2)
1180
+ });
1181
+ const actualArchive = (0, import_archiver.default)("zip", { zlib: { level: compressionLevel } });
1182
+ res.setHeader("Content-Type", "application/zip");
1183
+ res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
1184
+ res.setHeader("Content-Length", String(actualZipSize));
1185
+ res.setHeader("Access-Control-Expose-Headers", "Content-Type, Content-Disposition, Content-Length");
1186
+ actualArchive.on("error", (err) => {
1187
+ var _a3;
1188
+ (_a3 = this.logger) == null ? void 0 : _a3.error(this.reqId, "Archive error", { error: err });
1189
+ abort.abort();
1190
+ if (!res.headersSent) {
1191
+ next(err);
1192
+ }
1193
+ });
1194
+ actualArchive.pipe(res);
1195
+ for (const file of fileBuffers) {
1196
+ if (abort.signal.aborted) break;
1197
+ actualArchive.append(file.buffer, { name: file.name });
1198
+ }
1199
+ yield actualArchive.finalize();
1200
+ (_d = this.logger) == null ? void 0 : _d.info(this.reqId, "Zip download completed", {
1201
+ fileCount: fileBuffers.length,
1202
+ totalSize: actualZipSize
1203
+ });
1204
+ req.off("close", onClose);
1205
+ } catch (error) {
1206
+ abort.abort();
1207
+ 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";
1208
+ if (isBenignError) {
1209
+ return;
1210
+ }
1211
+ if (!res.headersSent) {
1212
+ (_e = this.logger) == null ? void 0 : _e.error(this.reqId, "Failed to create zip archive", { error });
1213
+ next(error);
1214
+ } else if (!res.writableEnded) {
1215
+ try {
1216
+ res.end();
1217
+ } catch (e) {
1218
+ }
1219
+ }
1220
+ } finally {
1221
+ req.off("close", onClose);
1222
+ }
1223
+ });
1224
+ });
1225
+ }
1226
+ getStreamFileCtrl(_0) {
1227
+ return __async(this, arguments, function* ({ filePath, filename }) {
1228
+ return (req, res, next) => __async(this, null, function* () {
1229
+ var _a2, _b;
1230
+ const abort = new AbortController();
1231
+ let stream = null;
1232
+ const onClose = () => {
1233
+ var _a3;
1234
+ abort.abort();
1235
+ (_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
1236
+ };
1237
+ req.once("close", onClose);
1238
+ const normalizedKey = decodeURIComponent((filePath == null ? void 0 : filePath.replace(/^\//, "").replace(/\/$/, "")) || "");
1239
+ if (!normalizedKey || normalizedKey === "/") {
1240
+ throw new Error("No file key provided");
1241
+ }
1242
+ try {
1243
+ const isExists = yield this.fileExists(normalizedKey);
1244
+ if (!isExists) {
1245
+ req.off("close", onClose);
1246
+ next(Error(`File not found: "${normalizedKey}"`));
1247
+ return;
1248
+ }
1249
+ stream = yield this.streamObjectFile(normalizedKey, {
1250
+ abortSignal: abort.signal,
1251
+ checkFileExists: false
1252
+ });
1253
+ if (!stream) {
1254
+ req.off("close", onClose);
1255
+ next(Error(`Failed to get file stream: "${normalizedKey}"`));
1256
+ return;
1257
+ }
1258
+ const fileInfo = yield this.fileInfo(normalizedKey);
1259
+ const fileName = filename || normalizedKey.split("/").pop() || "download";
1260
+ res.setHeader("Content-Type", fileInfo.ContentType || "application/octet-stream");
1261
+ res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
1262
+ if (fileInfo.ContentLength) {
1263
+ res.setHeader("Content-Length", String(fileInfo.ContentLength));
1264
+ }
1265
+ stream.on("error", (err) => {
1266
+ var _a3, _b2;
1267
+ (_a3 = this.logger) == null ? void 0 : _a3.warn(this.reqId, "Stream error", { filePath: normalizedKey, error: err });
1268
+ abort.abort();
1269
+ (_b2 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _b2.call(stream);
1270
+ });
1271
+ res.once("close", () => {
1272
+ var _a3;
1273
+ (_a3 = stream == null ? void 0 : stream.destroy) == null ? void 0 : _a3.call(stream);
1274
+ req.off("close", onClose);
1275
+ });
1276
+ yield pump(stream, res);
1277
+ req.off("close", onClose);
1278
+ } catch (error) {
1279
+ abort.abort();
1280
+ if (stream) {
1281
+ (_a2 = stream.destroy) == null ? void 0 : _a2.call(stream);
1282
+ }
1283
+ 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";
1284
+ if (isBenignStreamError) {
1285
+ return;
1286
+ }
1287
+ (_b = this.logger) == null ? void 0 : _b.error(this.reqId, "Failed to stream file", { filePath: normalizedKey, error });
1288
+ if (!res.headersSent) {
1289
+ next(error);
1290
+ } else if (!res.writableEnded) {
1291
+ try {
1292
+ res.end();
1293
+ } catch (e) {
1294
+ }
1295
+ }
1296
+ } finally {
1297
+ req.off("close", onClose);
1298
+ }
1299
+ });
1300
+ });
1301
+ }
1302
+ getStreamVideoFileCtrl(_0) {
1303
+ return __async(this, arguments, function* ({
1304
+ fileKey,
1305
+ allowedWhitelist,
1306
+ contentType = "video/mp4",
1307
+ streamTimeoutMS = 3e4,
1308
+ bufferMB = 5
1309
+ }) {
1310
+ return (req, res, next) => __async(this, null, function* () {
1311
+ var _a2, _b, _c, _d, _e, _f;
1312
+ const normalizedKey = decodeURIComponent((fileKey == null ? void 0 : fileKey.replace(/^\//, "").replace(/\/$/, "")) || "");
1313
+ if (!normalizedKey || normalizedKey === "/") {
1314
+ throw new Error("No file key provided");
1315
+ }
1316
+ const isExists = yield this.fileExists(normalizedKey);
1317
+ const fileSize = yield this.sizeOf(normalizedKey);
1318
+ let Range;
1319
+ if (!isExists) {
1320
+ next(Error(`File does not exist: "${normalizedKey}"`));
1321
+ return;
1322
+ }
1323
+ try {
1324
+ if (req.method === "HEAD") {
1325
+ res.setHeader("Content-Type", contentType);
1326
+ res.setHeader("Accept-Ranges", "bytes");
1327
+ if (fileSize) res.setHeader("Content-Length", String(fileSize));
1328
+ return res.status(200).end();
1329
+ }
1330
+ const bufferSize = bufferMB;
1331
+ const CHUNK_SIZE = __pow(10, 6) * bufferSize;
1332
+ const rangeValues = parseRangeHeader(req.headers.range, fileSize, CHUNK_SIZE);
1333
+ let [start, end] = rangeValues || [];
1334
+ if (!rangeValues || start < 0 || start >= fileSize || end < 0 || end >= fileSize || start > end) {
1335
+ res.status(416).send("Requested Range Not Satisfiable");
1336
+ return;
1337
+ }
1338
+ res.statusCode = 206;
1339
+ const chunkLength = end - start + 1;
1340
+ res.setHeader("Content-Length", chunkLength);
1341
+ res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`);
1342
+ res.setHeader("Accept-Ranges", "bytes");
1343
+ res.setHeader("Content-Type", "video/mp4");
1344
+ Range = `bytes=${start}-${end}`;
1345
+ } catch (error) {
1346
+ next(error);
1347
+ return;
1348
+ }
1349
+ const abort = new AbortController();
1350
+ const onClose = () => abort.abort();
1351
+ req.once("close", onClose);
1352
+ try {
1353
+ const result = yield this.streamVideoFile({
1354
+ filePath: normalizedKey,
1355
+ Range,
1356
+ abortSignal: abort.signal
1357
+ });
1358
+ const { body, meta } = result;
1359
+ const origin = Array.isArray(allowedWhitelist) ? allowedWhitelist.includes((_a2 = req.headers.origin) != null ? _a2 : "") ? req.headers.origin : void 0 : allowedWhitelist;
1360
+ if (origin) {
1361
+ res.setHeader("Access-Control-Allow-Origin", origin);
1362
+ res.setHeader("Vary", "Origin");
1363
+ }
1364
+ const finalContentType = contentType.startsWith("video/") ? contentType : `video/${contentType}`;
1365
+ res.setHeader("Content-Type", (_b = meta.contentType) != null ? _b : finalContentType);
1366
+ res.setHeader("Accept-Ranges", (_c = meta.acceptRanges) != null ? _c : "bytes");
1367
+ if (Range && meta.contentRange) {
1368
+ res.status(206);
1369
+ res.setHeader("Content-Range", meta.contentRange);
1370
+ if (typeof meta.contentLength === "number") {
1371
+ res.setHeader("Content-Length", String(meta.contentLength));
1372
+ }
1373
+ } else if (fileSize) {
1374
+ res.setHeader("Content-Length", String(fileSize));
1375
+ }
1376
+ if (meta.etag) res.setHeader("ETag", meta.etag);
1377
+ if (meta.lastModified) res.setHeader("Last-Modified", meta.lastModified.toUTCString());
1378
+ const timeout = setTimeout(() => {
1379
+ abort.abort();
1380
+ if (!res.headersSent) res.status(504);
1381
+ res.end();
1382
+ }, streamTimeoutMS);
1383
+ res.once("close", () => {
1384
+ var _a3;
1385
+ clearTimeout(timeout);
1386
+ (_a3 = body.destroy) == null ? void 0 : _a3.call(body);
1387
+ req.off("close", onClose);
1388
+ });
1389
+ yield pump(body, res);
1390
+ clearTimeout(timeout);
1391
+ } catch (error) {
1392
+ 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";
1393
+ if (isBenignStreamError) {
1394
+ return;
1395
+ }
1396
+ if (!res.headersSent) {
1397
+ (_f = this.logger) == null ? void 0 : _f.warn(req.id, "caught exception in stream controller", {
1398
+ error: (_d = error == null ? void 0 : error.message) != null ? _d : String(error),
1399
+ key: fileKey,
1400
+ url: req.originalUrl,
1401
+ userId: (_e = req.user) == null ? void 0 : _e._id
1402
+ });
1403
+ next(error);
1404
+ return;
1405
+ }
1406
+ if (!res.writableEnded) {
1407
+ try {
1408
+ res.end();
1409
+ } catch (e) {
1410
+ }
1411
+ }
1412
+ return;
1413
+ }
1414
+ });
1415
+ });
1416
+ }
1417
+ static fileFilter(types, fileExt) {
1418
+ const fileTypesChecker = (fileExt == null ? void 0 : fileExt.length) ? new RegExp(`\\.(${fileExt.join("|")})$`, "i") : void 0;
1419
+ return function(_req, file, cb) {
1420
+ const fileExtension = import_pathe.default.extname(file.originalname).substring(1);
1421
+ const extname = fileTypesChecker ? fileTypesChecker.test(`.${fileExtension}`) : true;
1422
+ const mimeType = (types == null ? void 0 : types.length) ? types.some((type) => file.mimetype.startsWith(`${type}/`)) : true;
1423
+ if (mimeType && extname) {
1424
+ return cb(null, true);
1425
+ }
1426
+ const errorMsg = !extname ? `Upload File Ext Error: Allowed extensions: [${fileExt == null ? void 0 : fileExt.join(", ")}]. Got: ${fileExtension}` : `Upload File Type Error: Allowed types: [${types == null ? void 0 : types.join(", ")}]. Got: ${file.mimetype}`;
1427
+ return cb(new Error(errorMsg));
1428
+ };
1429
+ }
1430
+ getFileSize(maxFileSize) {
1431
+ var _a2;
1432
+ const fileSizeUnitValue = maxFileSize != null ? maxFileSize : this.maxUploadFileSizeRestriction;
1433
+ const fileSize = typeof fileSizeUnitValue === "number" ? fileSizeUnitValue : (0, import_bytes.default)(fileSizeUnitValue);
1434
+ if (!fileSize) {
1435
+ (_a2 = this.logger) == null ? void 0 : _a2.warn(this.reqId, "Failed to convert fileSize restriction, proceeding without limit", {
1436
+ maxFileSize,
1437
+ maxUploadFileSizeRestriction: this.maxUploadFileSizeRestriction
1438
+ });
1439
+ return void 0;
1440
+ }
1441
+ return fileSize;
1442
+ }
1443
+ getUploadFileMW(directory, {
1444
+ acl = "private" /* private */,
1445
+ maxFileSize,
1446
+ filename: _filename,
1447
+ fileType = [],
1448
+ fileExt = [],
1449
+ metadata: customMetadata
1450
+ } = {}) {
1451
+ let normalizedPath = decodeURIComponent((directory == null ? void 0 : directory.replace(/^\//, "").replace(/\/$/, "")) || "");
1452
+ if (directory !== "/" && directory !== "" && directory !== void 0) normalizedPath += "/";
1453
+ else normalizedPath = "";
1454
+ const fileSize = this.getFileSize(maxFileSize);
1455
+ const fileTypes = [].concat(fileType);
1456
+ const fileExts = [].concat(fileExt);
1457
+ const fileFilter = (fileTypes == null ? void 0 : fileTypes.length) || (fileExts == null ? void 0 : fileExts.length) ? _S3BucketUtil.fileFilter(fileTypes, fileExts) : void 0;
1458
+ return (0, import_multer.default)({
1459
+ fileFilter,
1460
+ limits: __spreadValues({}, fileSize && { fileSize }),
1461
+ storage: (0, import_multer_s3.default)({
1462
+ acl,
1463
+ s3: this.s3Client,
1464
+ bucket: this.bucket,
1465
+ contentType: import_multer_s3.default.AUTO_CONTENT_TYPE,
1466
+ metadata: (req, file, cb) => __async(this, null, function* () {
1467
+ const baseMetadata = __spreadProps(__spreadValues({}, file), { directory: normalizedPath });
1468
+ if (customMetadata) {
1469
+ const additionalMetadata = typeof customMetadata === "function" ? yield customMetadata(req, file) : customMetadata;
1470
+ Object.assign(baseMetadata, additionalMetadata);
1471
+ }
1472
+ cb(null, baseMetadata);
1473
+ }),
1474
+ key: (req, file, cb) => __async(this, null, function* () {
1475
+ let filename;
1476
+ if (typeof _filename === "function") {
1477
+ filename = yield _filename(req, file);
1478
+ } else if (_filename) {
1479
+ filename = _filename;
1480
+ } else {
1481
+ filename = file.originalname;
1482
+ }
1483
+ filename = decodeURIComponent(filename);
1484
+ const key = `${normalizedPath}${filename}`;
1485
+ cb(null, key);
1486
+ })
1487
+ })
1488
+ });
1489
+ }
1490
+ /**
1491
+ * Middleware for uploading a single file
1492
+ * Adds the uploaded file info to req.s3File
1493
+ */
1494
+ uploadSingleFile(fieldName, directory, options = {}) {
1495
+ const upload = this.getUploadFileMW(directory, options);
1496
+ return (req, res, next) => {
1497
+ const mw = upload.single(fieldName);
1498
+ mw(req, res, (err) => {
1499
+ var _a2, _b;
1500
+ if (err) {
1501
+ (_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Single file upload error", { fieldName, error: err.message });
1502
+ return next(err);
1503
+ }
1504
+ if (req.file) {
1505
+ req.s3File = req.file;
1506
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Single file uploaded successfully", {
1507
+ fieldName,
1508
+ key: req.s3File.key,
1509
+ location: req.s3File.location,
1510
+ size: req.s3File.size
1511
+ });
1512
+ }
1513
+ next();
1514
+ });
1515
+ };
1516
+ }
1517
+ /**
1518
+ * Middleware for uploading multiple files with the same field name
1519
+ * Adds the uploaded files info to req.s3Files
1520
+ */
1521
+ uploadMultipleFiles(fieldName, directory, options = {}) {
1522
+ const upload = this.getUploadFileMW(directory, options);
1523
+ return (req, res, next) => {
1524
+ const mw = upload.array(fieldName, options.maxFilesCount || void 0);
1525
+ mw(req, res, (err) => {
1526
+ var _a2, _b;
1527
+ if (err) {
1528
+ (_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Multiple files upload error", { fieldName, error: err.message });
1529
+ return next(err);
1530
+ }
1531
+ if (Array.isArray(req.files)) {
1532
+ req.s3Files = req.files;
1533
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Multiple files uploaded successfully", {
1534
+ fieldName,
1535
+ count: req.s3Files.length,
1536
+ keys: req.s3Files.map((f) => f.key)
1537
+ });
1538
+ }
1539
+ next();
1540
+ });
1541
+ };
1542
+ }
1543
+ /**
1544
+ * Middleware for uploading multiple files with different field names
1545
+ * Adds the uploaded files info to req.s3FilesByField
1546
+ */
1547
+ uploadFieldsFiles(fields) {
1548
+ const fieldConfigs = fields.map((field) => {
1549
+ const upload = this.getUploadFileMW(field.directory, field.options || {});
1550
+ return {
1551
+ name: field.name,
1552
+ maxCount: field.maxCount || 1,
1553
+ upload,
1554
+ directory: field.directory
1555
+ };
1556
+ });
1557
+ return (req, res, next) => __async(this, null, function* () {
1558
+ const multerFields = fieldConfigs.map((f) => ({ name: f.name, maxCount: f.maxCount }));
1559
+ const upload = this.getUploadFileMW(fieldConfigs[0].directory);
1560
+ const mw = upload.fields(multerFields);
1561
+ mw(req, res, (err) => {
1562
+ var _a2, _b;
1563
+ if (err) {
1564
+ (_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Fields upload error", { error: err.message });
1565
+ return next(err);
1566
+ }
1567
+ if (req.files && typeof req.files === "object" && !Array.isArray(req.files)) {
1568
+ req.s3FilesByField = req.files;
1569
+ const uploadSummary = Object.entries(req.s3FilesByField).map(([field, files]) => ({
1570
+ field,
1571
+ count: files.length,
1572
+ keys: files.map((f) => f.key)
1573
+ }));
1574
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Fields uploaded successfully", { uploadSummary });
1575
+ }
1576
+ next();
1577
+ });
1578
+ });
1579
+ }
1580
+ /**
1581
+ * Middleware for uploading any files (mixed field names)
1582
+ * Adds the uploaded files info to req.s3AllFiles
1583
+ */
1584
+ uploadAnyFiles(directory, maxCount, options = {}) {
1585
+ const upload = this.getUploadFileMW(directory, options);
1586
+ return (req, res, next) => {
1587
+ const anyUpload = maxCount ? upload.any() : upload.any();
1588
+ anyUpload(req, res, (err) => {
1589
+ var _a2, _b;
1590
+ if (err) {
1591
+ (_a2 = this.logger) == null ? void 0 : _a2.error(this.reqId, "Any files upload error", { error: err.message });
1592
+ return next(err);
1593
+ }
1594
+ if (req.files && Array.isArray(req.files)) {
1595
+ req.s3AllFiles = req.files;
1596
+ if (maxCount && req.s3AllFiles.length > maxCount) {
1597
+ return next(new Error(`Too many files uploaded. Maximum is ${maxCount}`));
1598
+ }
1599
+ (_b = this.logger) == null ? void 0 : _b.info(this.reqId, "Any files uploaded successfully", {
1600
+ count: req.s3AllFiles.length,
1601
+ keys: req.s3AllFiles.map((f) => f.key)
1602
+ });
1603
+ }
1604
+ next();
1605
+ });
1606
+ };
1607
+ }
1608
+ };
1609
+
1610
+ // src/aws/sns.ts
1611
+ var import_client_sns = require("@aws-sdk/client-sns");
1612
+ var SNSUtil = class {
1613
+ constructor({
1614
+ accessKeyId = AWSConfigSharingUtil.accessKeyId,
1615
+ secretAccessKey = AWSConfigSharingUtil.secretAccessKey,
1616
+ endpoint = AWSConfigSharingUtil.endpoint,
1617
+ region = AWSConfigSharingUtil.region,
1618
+ topicArn,
1619
+ debug = false
1620
+ }) {
1621
+ __publicField(this, "sns");
1622
+ __publicField(this, "topicArn");
1623
+ const credentials = { accessKeyId, secretAccessKey };
1624
+ const options = __spreadValues(__spreadValues(__spreadValues({}, accessKeyId && secretAccessKey && { credentials }), endpoint && { endpoint }), region && { region });
1625
+ if (debug) {
1626
+ console.log("LambdaUtil client options", options);
1627
+ }
1628
+ this.topicArn = topicArn;
1629
+ this.sns = new import_client_sns.SNS(__spreadValues(__spreadValues(__spreadValues({}, credentials && { credentials }), endpoint && { endpoint }), region && { region }));
1630
+ }
1631
+ publishTopicMessage(message) {
1632
+ return __async(this, null, function* () {
1633
+ this.sns.publish({
1634
+ Message: typeof message === "string" ? message : JSON.stringify(message),
1635
+ TopicArn: this.topicArn
1636
+ });
1637
+ });
1638
+ }
1639
+ };
1640
+ // Annotate the CommonJS export names for ESM import in node:
1641
+ 0 && (module.exports = {
1642
+ ACLs,
1643
+ AWSConfigSharingUtil,
1644
+ IAMUtil,
1645
+ LambdaUtil,
1646
+ S3BucketUtil,
1647
+ SNSUtil
1648
+ });