node-server-engine 1.8.0 → 1.9.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/README.md +712 -67
- package/dist/entities/AWSS3/AWSS3.d.ts +157 -0
- package/dist/entities/AWSS3/AWSS3.js +426 -0
- package/dist/entities/AWSS3/AWSS3.js.map +1 -0
- package/dist/entities/AWSS3/AWSS3.types.d.ts +78 -0
- package/dist/entities/AWSS3/AWSS3.types.js +2 -0
- package/dist/entities/AWSS3/AWSS3.types.js.map +1 -0
- package/dist/entities/AWSS3/index.d.ts +2 -0
- package/dist/entities/{Storage → AWSS3}/index.js +8 -8
- package/dist/entities/AWSS3/index.js.map +1 -0
- package/dist/entities/ElasticSearch/ElasticSearch.d.ts +5 -0
- package/dist/entities/ElasticSearch/ElasticSearch.js +137 -38
- package/dist/entities/ElasticSearch/ElasticSearch.js.map +1 -1
- package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.d.ts +141 -0
- package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.js +351 -0
- package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.js.map +1 -0
- package/dist/entities/{Storage/Storage.types.d.ts → GoogleCloudStorage/GoogleCloudStorage.types.d.ts} +14 -0
- package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.types.js +2 -0
- package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.types.js.map +1 -0
- package/dist/entities/GoogleCloudStorage/index.d.ts +2 -0
- package/dist/entities/GoogleCloudStorage/index.js +28 -0
- package/dist/entities/GoogleCloudStorage/index.js.map +1 -0
- package/dist/entities/Localizator/Localizator.js +2 -2
- package/dist/entities/Localizator/Localizator.js.map +1 -1
- package/dist/entities/index.d.ts +2 -1
- package/dist/entities/index.js +15 -4
- package/dist/entities/index.js.map +1 -1
- package/package.json +11 -10
- package/dist/entities/Storage/Storage.d.ts +0 -38
- package/dist/entities/Storage/Storage.js +0 -214
- package/dist/entities/Storage/Storage.js.map +0 -1
- package/dist/entities/Storage/Storage.types.js +0 -2
- package/dist/entities/Storage/Storage.types.js.map +0 -1
- package/dist/entities/Storage/index.d.ts +0 -2
- package/dist/entities/Storage/index.js.map +0 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
3
|
+
import { S3UploadDestinationOptions, S3UploadedFile, S3UploaderOptions, S3GetResponse, S3DownloadResponse, AWSS3Config, S3FileMetadata } from './AWSS3.types';
|
|
4
|
+
/**
|
|
5
|
+
* Generic AWS S3 wrapper for easy file operations
|
|
6
|
+
* Provides methods for uploading, downloading, streaming, and deleting files
|
|
7
|
+
* Compatible with AWS S3 and S3-compatible services (MinIO, LocalStack, etc.)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Initialize with custom configuration
|
|
12
|
+
* AWSS3.init({
|
|
13
|
+
* region: 'us-east-1',
|
|
14
|
+
* accessKeyId: 'YOUR_ACCESS_KEY',
|
|
15
|
+
* secretAccessKey: 'YOUR_SECRET_KEY'
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Upload a file
|
|
19
|
+
* const result = await AWSS3.upload(
|
|
20
|
+
* fileStream,
|
|
21
|
+
* 'my-bucket',
|
|
22
|
+
* { directory: 'uploads', mime: 'image/png' }
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* // Download a file
|
|
26
|
+
* const { data, metadata } = await AWSS3.get('my-bucket', 'uploads/file.png');
|
|
27
|
+
*
|
|
28
|
+
* // Delete a file
|
|
29
|
+
* await AWSS3.delete('my-bucket', 'uploads/file.png');
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare const AWSS3: {
|
|
33
|
+
s3Client: S3Client | null;
|
|
34
|
+
/**
|
|
35
|
+
* Initialize AWS S3 with configuration
|
|
36
|
+
* Call this method before using any other methods
|
|
37
|
+
* @param config - AWS S3 configuration options
|
|
38
|
+
*/
|
|
39
|
+
init(config?: AWSS3Config): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get the S3 client instance (auto-initializes if needed)
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
getS3Client(): S3Client;
|
|
45
|
+
/**
|
|
46
|
+
* Upload a file to AWS S3
|
|
47
|
+
* @param readStream - Readable stream of the file content
|
|
48
|
+
* @param bucket - Bucket name where the file should be stored
|
|
49
|
+
* @param destinationOptions - Options for file destination (directory, fileName, etc.)
|
|
50
|
+
* @param uploaderOptions - Additional options like maxSize
|
|
51
|
+
* @param metadata - Optional metadata to attach to the file
|
|
52
|
+
* @returns Promise with file upload result
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const fileStream = fs.createReadStream('photo.jpg');
|
|
57
|
+
* const result = await AWSS3.upload(
|
|
58
|
+
* fileStream,
|
|
59
|
+
* 'my-bucket',
|
|
60
|
+
* { directory: 'photos', mime: 'image/jpeg' },
|
|
61
|
+
* { maxSize: '5MB' },
|
|
62
|
+
* { userId: '123', uploadedBy: 'user@example.com' }
|
|
63
|
+
* );
|
|
64
|
+
* console.log(result.Key); // photos/uuid.jpeg
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
upload(readStream: Readable, bucket: string, destinationOptions?: S3UploadDestinationOptions, uploaderOptions?: S3UploaderOptions, metadata?: Record<string, string>): Promise<S3UploadedFile>;
|
|
68
|
+
/**
|
|
69
|
+
* Download a file from AWS S3 and return its content as Buffer
|
|
70
|
+
* @param bucket - Bucket name
|
|
71
|
+
* @param key - File key (path) in the bucket
|
|
72
|
+
* @returns Promise with file data and metadata
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const { data, metadata } = await AWSS3.get('my-bucket', 'photos/image.jpg');
|
|
77
|
+
* console.log(metadata.ContentLength); // File size in bytes
|
|
78
|
+
* console.log(data); // Buffer with file content
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
get(bucket: string, key: string): Promise<S3GetResponse>;
|
|
82
|
+
/**
|
|
83
|
+
* Get a readable stream for a file from AWS S3
|
|
84
|
+
* Use this for large files or when you need to stream the content
|
|
85
|
+
* @param bucket - Bucket name
|
|
86
|
+
* @param key - File key (path) in the bucket
|
|
87
|
+
* @returns Promise with readable stream and metadata
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const { stream, metadata } = await AWSS3.download('my-bucket', 'videos/video.mp4');
|
|
92
|
+
* stream.pipe(res); // Stream to HTTP response
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
download(bucket: string, key: string): Promise<S3DownloadResponse>;
|
|
96
|
+
/**
|
|
97
|
+
* Get a readable stream for a file (without fetching all metadata first)
|
|
98
|
+
* Use this when you don't need metadata and want faster streaming
|
|
99
|
+
* @param bucket - Bucket name
|
|
100
|
+
* @param key - File key (path) in the bucket
|
|
101
|
+
* @returns Promise with readable stream
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const stream = await AWSS3.getFileStream('my-bucket', 'audio/song.mp3');
|
|
106
|
+
* stream.pipe(res); // Direct streaming
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
getFileStream(bucket: string, key: string): Promise<Readable>;
|
|
110
|
+
/**
|
|
111
|
+
* Get file metadata without downloading the file
|
|
112
|
+
* @param bucket - Bucket name
|
|
113
|
+
* @param key - File key (path) in the bucket
|
|
114
|
+
* @returns Promise with file metadata
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const metadata = await AWSS3.getMetadata('my-bucket', 'photos/image.jpg');
|
|
119
|
+
* console.log(metadata.ContentLength); // File size
|
|
120
|
+
* console.log(metadata.ContentType); // MIME type
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
getMetadata(bucket: string, key: string): Promise<S3FileMetadata>;
|
|
124
|
+
/**
|
|
125
|
+
* Delete a file from AWS S3
|
|
126
|
+
* @param bucket - Bucket name
|
|
127
|
+
* @param key - File key (path) in the bucket
|
|
128
|
+
* @returns Promise that resolves when deletion is complete
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* await AWSS3.delete('my-bucket', 'temp/old-file.txt');
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
delete(bucket: string, key: string): Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Generate a random file path with optional directory and extension
|
|
138
|
+
* Useful for creating unique file names
|
|
139
|
+
* @param options - Options for file destination generation
|
|
140
|
+
* @returns Generated file path
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* // Generate UUID only
|
|
145
|
+
* const path1 = AWSS3.generateFileDestination();
|
|
146
|
+
* // 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
|
147
|
+
*
|
|
148
|
+
* // With directory and MIME type
|
|
149
|
+
* const path2 = AWSS3.generateFileDestination({
|
|
150
|
+
* directory: 'uploads/images',
|
|
151
|
+
* mime: 'image/jpeg'
|
|
152
|
+
* });
|
|
153
|
+
* // 'uploads/images/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpeg'
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
generateFileDestination(options?: S3UploadDestinationOptions): string;
|
|
157
|
+
};
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.AWSS3 = void 0;
|
|
8
|
+
var _crypto = require("crypto");
|
|
9
|
+
var _clientS = require("@aws-sdk/client-s3");
|
|
10
|
+
var _filesizeParser = _interopRequireDefault(require("filesize-parser"));
|
|
11
|
+
var _mimeTypes = _interopRequireDefault(require("mime-types"));
|
|
12
|
+
var _WebError = require("../WebError");
|
|
13
|
+
var _report = require("../../utils/report");
|
|
14
|
+
var _trimPathSlash = require("../../utils/trimPathSlash");
|
|
15
|
+
const namespace = 'engine:aws-s3';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generic AWS S3 wrapper for easy file operations
|
|
19
|
+
* Provides methods for uploading, downloading, streaming, and deleting files
|
|
20
|
+
* Compatible with AWS S3 and S3-compatible services (MinIO, LocalStack, etc.)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Initialize with custom configuration
|
|
25
|
+
* AWSS3.init({
|
|
26
|
+
* region: 'us-east-1',
|
|
27
|
+
* accessKeyId: 'YOUR_ACCESS_KEY',
|
|
28
|
+
* secretAccessKey: 'YOUR_SECRET_KEY'
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Upload a file
|
|
32
|
+
* const result = await AWSS3.upload(
|
|
33
|
+
* fileStream,
|
|
34
|
+
* 'my-bucket',
|
|
35
|
+
* { directory: 'uploads', mime: 'image/png' }
|
|
36
|
+
* );
|
|
37
|
+
*
|
|
38
|
+
* // Download a file
|
|
39
|
+
* const { data, metadata } = await AWSS3.get('my-bucket', 'uploads/file.png');
|
|
40
|
+
*
|
|
41
|
+
* // Delete a file
|
|
42
|
+
* await AWSS3.delete('my-bucket', 'uploads/file.png');
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
const AWSS3 = exports.AWSS3 = {
|
|
46
|
+
s3Client: null,
|
|
47
|
+
/**
|
|
48
|
+
* Initialize AWS S3 with configuration
|
|
49
|
+
* Call this method before using any other methods
|
|
50
|
+
* @param config - AWS S3 configuration options
|
|
51
|
+
*/
|
|
52
|
+
init(config) {
|
|
53
|
+
const s3Config = {};
|
|
54
|
+
if (config) {
|
|
55
|
+
if (config.region) s3Config.region = config.region;
|
|
56
|
+
if (config.endpoint) s3Config.endpoint = config.endpoint;
|
|
57
|
+
if (config.forcePathStyle !== undefined) s3Config.forcePathStyle = config.forcePathStyle;
|
|
58
|
+
if (config.accessKeyId && config.secretAccessKey) {
|
|
59
|
+
s3Config.credentials = {
|
|
60
|
+
accessKeyId: config.accessKeyId,
|
|
61
|
+
secretAccessKey: config.secretAccessKey,
|
|
62
|
+
...(config.sessionToken && {
|
|
63
|
+
sessionToken: config.sessionToken
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
// Use environment variables as fallback
|
|
69
|
+
if (process.env.AWS_REGION) s3Config.region = process.env.AWS_REGION;
|
|
70
|
+
if (process.env.AWS_S3_ENDPOINT) s3Config.endpoint = process.env.AWS_S3_ENDPOINT;
|
|
71
|
+
if (process.env.AWS_S3_FORCE_PATH_STYLE === 'true') s3Config.forcePathStyle = true;
|
|
72
|
+
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
|
|
73
|
+
s3Config.credentials = {
|
|
74
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
75
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
76
|
+
...(process.env.AWS_SESSION_TOKEN && {
|
|
77
|
+
sessionToken: process.env.AWS_SESSION_TOKEN
|
|
78
|
+
})
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this.s3Client = new _clientS.S3Client(s3Config);
|
|
83
|
+
(0, _report.reportDebug)({
|
|
84
|
+
namespace,
|
|
85
|
+
message: 'AWS S3 initialized',
|
|
86
|
+
data: {
|
|
87
|
+
region: s3Config.region,
|
|
88
|
+
endpoint: s3Config.endpoint
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* Get the S3 client instance (auto-initializes if needed)
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
getS3Client() {
|
|
97
|
+
if (!this.s3Client) {
|
|
98
|
+
this.init();
|
|
99
|
+
}
|
|
100
|
+
return this.s3Client;
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* Upload a file to AWS S3
|
|
104
|
+
* @param readStream - Readable stream of the file content
|
|
105
|
+
* @param bucket - Bucket name where the file should be stored
|
|
106
|
+
* @param destinationOptions - Options for file destination (directory, fileName, etc.)
|
|
107
|
+
* @param uploaderOptions - Additional options like maxSize
|
|
108
|
+
* @param metadata - Optional metadata to attach to the file
|
|
109
|
+
* @returns Promise with file upload result
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const fileStream = fs.createReadStream('photo.jpg');
|
|
114
|
+
* const result = await AWSS3.upload(
|
|
115
|
+
* fileStream,
|
|
116
|
+
* 'my-bucket',
|
|
117
|
+
* { directory: 'photos', mime: 'image/jpeg' },
|
|
118
|
+
* { maxSize: '5MB' },
|
|
119
|
+
* { userId: '123', uploadedBy: 'user@example.com' }
|
|
120
|
+
* );
|
|
121
|
+
* console.log(result.Key); // photos/uuid.jpeg
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
async upload(readStream, bucket, destinationOptions = {}, uploaderOptions = {}, metadata) {
|
|
125
|
+
const client = this.getS3Client();
|
|
126
|
+
const {
|
|
127
|
+
maxSize
|
|
128
|
+
} = uploaderOptions;
|
|
129
|
+
(0, _report.reportDebug)({
|
|
130
|
+
namespace,
|
|
131
|
+
message: 'Uploading file to S3',
|
|
132
|
+
data: {
|
|
133
|
+
bucket,
|
|
134
|
+
destinationOptions,
|
|
135
|
+
uploaderOptions
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Generate the file path
|
|
140
|
+
const {
|
|
141
|
+
fileName,
|
|
142
|
+
directory
|
|
143
|
+
} = destinationOptions;
|
|
144
|
+
let key;
|
|
145
|
+
if (fileName) {
|
|
146
|
+
const trimmedDirectory = (0, _trimPathSlash.trimPathSlash)(directory ?? '/');
|
|
147
|
+
key = trimmedDirectory ? `${trimmedDirectory}/${fileName}` : fileName;
|
|
148
|
+
} else {
|
|
149
|
+
key = this.generateFileDestination(destinationOptions);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Collect stream data with size validation
|
|
153
|
+
const chunks = [];
|
|
154
|
+
let receivedSize = 0;
|
|
155
|
+
const intMaxSize = maxSize && (0, _filesizeParser.default)(maxSize);
|
|
156
|
+
for await (const chunk of readStream) {
|
|
157
|
+
receivedSize += chunk.length;
|
|
158
|
+
if (intMaxSize && receivedSize > intMaxSize) {
|
|
159
|
+
throw new _WebError.WebError({
|
|
160
|
+
statusCode: 413,
|
|
161
|
+
errorCode: 'file-too-large',
|
|
162
|
+
message: 'File size exceeds the maximum allowed limit',
|
|
163
|
+
hint: {
|
|
164
|
+
maxSize
|
|
165
|
+
},
|
|
166
|
+
data: {
|
|
167
|
+
bucket,
|
|
168
|
+
destinationOptions,
|
|
169
|
+
receivedSize,
|
|
170
|
+
maxSize
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
chunks.push(chunk);
|
|
175
|
+
}
|
|
176
|
+
const body = Buffer.concat(chunks);
|
|
177
|
+
|
|
178
|
+
// Determine content type
|
|
179
|
+
const contentType = destinationOptions.mime || 'application/octet-stream';
|
|
180
|
+
const command = new _clientS.PutObjectCommand({
|
|
181
|
+
Bucket: bucket,
|
|
182
|
+
Key: key,
|
|
183
|
+
Body: body,
|
|
184
|
+
ContentType: contentType,
|
|
185
|
+
...(metadata && {
|
|
186
|
+
Metadata: metadata
|
|
187
|
+
})
|
|
188
|
+
});
|
|
189
|
+
const response = await client.send(command);
|
|
190
|
+
(0, _report.reportDebug)({
|
|
191
|
+
namespace,
|
|
192
|
+
message: 'File uploaded to S3',
|
|
193
|
+
data: {
|
|
194
|
+
bucket,
|
|
195
|
+
key,
|
|
196
|
+
etag: response.ETag
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
Bucket: bucket,
|
|
201
|
+
Key: key,
|
|
202
|
+
ETag: response.ETag,
|
|
203
|
+
Location: `https://${bucket}.s3.amazonaws.com/${key}`,
|
|
204
|
+
ServerSideEncryption: response.ServerSideEncryption,
|
|
205
|
+
VersionId: response.VersionId
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
/**
|
|
209
|
+
* Download a file from AWS S3 and return its content as Buffer
|
|
210
|
+
* @param bucket - Bucket name
|
|
211
|
+
* @param key - File key (path) in the bucket
|
|
212
|
+
* @returns Promise with file data and metadata
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```typescript
|
|
216
|
+
* const { data, metadata } = await AWSS3.get('my-bucket', 'photos/image.jpg');
|
|
217
|
+
* console.log(metadata.ContentLength); // File size in bytes
|
|
218
|
+
* console.log(data); // Buffer with file content
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
async get(bucket, key) {
|
|
222
|
+
const client = this.getS3Client();
|
|
223
|
+
const command = new _clientS.GetObjectCommand({
|
|
224
|
+
Bucket: bucket,
|
|
225
|
+
Key: key
|
|
226
|
+
});
|
|
227
|
+
const response = await client.send(command);
|
|
228
|
+
|
|
229
|
+
// Convert stream to buffer
|
|
230
|
+
const chunks = [];
|
|
231
|
+
for await (const chunk of response.Body) {
|
|
232
|
+
chunks.push(chunk);
|
|
233
|
+
}
|
|
234
|
+
const data = Buffer.concat(chunks);
|
|
235
|
+
const metadata = {
|
|
236
|
+
ContentType: response.ContentType,
|
|
237
|
+
ContentLength: response.ContentLength,
|
|
238
|
+
LastModified: response.LastModified,
|
|
239
|
+
ETag: response.ETag,
|
|
240
|
+
Metadata: response.Metadata,
|
|
241
|
+
ServerSideEncryption: response.ServerSideEncryption,
|
|
242
|
+
VersionId: response.VersionId
|
|
243
|
+
};
|
|
244
|
+
(0, _report.reportDebug)({
|
|
245
|
+
namespace,
|
|
246
|
+
message: 'Downloaded file from S3',
|
|
247
|
+
data: {
|
|
248
|
+
bucket,
|
|
249
|
+
key,
|
|
250
|
+
size: data.length
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
return {
|
|
254
|
+
data,
|
|
255
|
+
metadata
|
|
256
|
+
};
|
|
257
|
+
},
|
|
258
|
+
/**
|
|
259
|
+
* Get a readable stream for a file from AWS S3
|
|
260
|
+
* Use this for large files or when you need to stream the content
|
|
261
|
+
* @param bucket - Bucket name
|
|
262
|
+
* @param key - File key (path) in the bucket
|
|
263
|
+
* @returns Promise with readable stream and metadata
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* const { stream, metadata } = await AWSS3.download('my-bucket', 'videos/video.mp4');
|
|
268
|
+
* stream.pipe(res); // Stream to HTTP response
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
async download(bucket, key) {
|
|
272
|
+
const client = this.getS3Client();
|
|
273
|
+
const command = new _clientS.GetObjectCommand({
|
|
274
|
+
Bucket: bucket,
|
|
275
|
+
Key: key
|
|
276
|
+
});
|
|
277
|
+
const response = await client.send(command);
|
|
278
|
+
const metadata = {
|
|
279
|
+
ContentType: response.ContentType,
|
|
280
|
+
ContentLength: response.ContentLength,
|
|
281
|
+
LastModified: response.LastModified,
|
|
282
|
+
ETag: response.ETag,
|
|
283
|
+
Metadata: response.Metadata,
|
|
284
|
+
ServerSideEncryption: response.ServerSideEncryption,
|
|
285
|
+
VersionId: response.VersionId
|
|
286
|
+
};
|
|
287
|
+
(0, _report.reportDebug)({
|
|
288
|
+
namespace,
|
|
289
|
+
message: 'Fetched file stream from S3',
|
|
290
|
+
data: {
|
|
291
|
+
bucket,
|
|
292
|
+
key,
|
|
293
|
+
metadata
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
return {
|
|
297
|
+
stream: response.Body,
|
|
298
|
+
metadata
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
/**
|
|
302
|
+
* Get a readable stream for a file (without fetching all metadata first)
|
|
303
|
+
* Use this when you don't need metadata and want faster streaming
|
|
304
|
+
* @param bucket - Bucket name
|
|
305
|
+
* @param key - File key (path) in the bucket
|
|
306
|
+
* @returns Promise with readable stream
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* const stream = await AWSS3.getFileStream('my-bucket', 'audio/song.mp3');
|
|
311
|
+
* stream.pipe(res); // Direct streaming
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
async getFileStream(bucket, key) {
|
|
315
|
+
const client = this.getS3Client();
|
|
316
|
+
const command = new _clientS.GetObjectCommand({
|
|
317
|
+
Bucket: bucket,
|
|
318
|
+
Key: key
|
|
319
|
+
});
|
|
320
|
+
const response = await client.send(command);
|
|
321
|
+
(0, _report.reportDebug)({
|
|
322
|
+
namespace,
|
|
323
|
+
message: "Fetched file stream from S3's bucket",
|
|
324
|
+
data: {
|
|
325
|
+
bucket,
|
|
326
|
+
key
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
return response.Body;
|
|
330
|
+
},
|
|
331
|
+
/**
|
|
332
|
+
* Get file metadata without downloading the file
|
|
333
|
+
* @param bucket - Bucket name
|
|
334
|
+
* @param key - File key (path) in the bucket
|
|
335
|
+
* @returns Promise with file metadata
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* const metadata = await AWSS3.getMetadata('my-bucket', 'photos/image.jpg');
|
|
340
|
+
* console.log(metadata.ContentLength); // File size
|
|
341
|
+
* console.log(metadata.ContentType); // MIME type
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
async getMetadata(bucket, key) {
|
|
345
|
+
const client = this.getS3Client();
|
|
346
|
+
const command = new _clientS.HeadObjectCommand({
|
|
347
|
+
Bucket: bucket,
|
|
348
|
+
Key: key
|
|
349
|
+
});
|
|
350
|
+
const response = await client.send(command);
|
|
351
|
+
return {
|
|
352
|
+
ContentType: response.ContentType,
|
|
353
|
+
ContentLength: response.ContentLength,
|
|
354
|
+
LastModified: response.LastModified,
|
|
355
|
+
ETag: response.ETag,
|
|
356
|
+
Metadata: response.Metadata,
|
|
357
|
+
ServerSideEncryption: response.ServerSideEncryption,
|
|
358
|
+
VersionId: response.VersionId
|
|
359
|
+
};
|
|
360
|
+
},
|
|
361
|
+
/**
|
|
362
|
+
* Delete a file from AWS S3
|
|
363
|
+
* @param bucket - Bucket name
|
|
364
|
+
* @param key - File key (path) in the bucket
|
|
365
|
+
* @returns Promise that resolves when deletion is complete
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```typescript
|
|
369
|
+
* await AWSS3.delete('my-bucket', 'temp/old-file.txt');
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
async delete(bucket, key) {
|
|
373
|
+
const client = this.getS3Client();
|
|
374
|
+
(0, _report.reportDebug)({
|
|
375
|
+
namespace,
|
|
376
|
+
message: 'Deleting file from S3',
|
|
377
|
+
data: {
|
|
378
|
+
bucket,
|
|
379
|
+
key
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
const command = new _clientS.DeleteObjectCommand({
|
|
383
|
+
Bucket: bucket,
|
|
384
|
+
Key: key
|
|
385
|
+
});
|
|
386
|
+
await client.send(command);
|
|
387
|
+
},
|
|
388
|
+
/**
|
|
389
|
+
* Generate a random file path with optional directory and extension
|
|
390
|
+
* Useful for creating unique file names
|
|
391
|
+
* @param options - Options for file destination generation
|
|
392
|
+
* @returns Generated file path
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```typescript
|
|
396
|
+
* // Generate UUID only
|
|
397
|
+
* const path1 = AWSS3.generateFileDestination();
|
|
398
|
+
* // 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
|
399
|
+
*
|
|
400
|
+
* // With directory and MIME type
|
|
401
|
+
* const path2 = AWSS3.generateFileDestination({
|
|
402
|
+
* directory: 'uploads/images',
|
|
403
|
+
* mime: 'image/jpeg'
|
|
404
|
+
* });
|
|
405
|
+
* // 'uploads/images/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpeg'
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
generateFileDestination(options = {}) {
|
|
409
|
+
const {
|
|
410
|
+
directory = '/',
|
|
411
|
+
mime,
|
|
412
|
+
noExtension = false
|
|
413
|
+
} = options;
|
|
414
|
+
|
|
415
|
+
// Determine file extension from MIME type
|
|
416
|
+
const extension = mime && _mimeTypes.default.extension(mime) ? _mimeTypes.default.extension(mime) : undefined;
|
|
417
|
+
|
|
418
|
+
// Build the full name with or without extension
|
|
419
|
+
const fullName = extension && !noExtension ? `${(0, _crypto.randomUUID)()}.${extension}` : (0, _crypto.randomUUID)();
|
|
420
|
+
|
|
421
|
+
// Clean up directory path
|
|
422
|
+
const trimmedDirectory = (0, _trimPathSlash.trimPathSlash)(directory);
|
|
423
|
+
return `${trimmedDirectory ? `${trimmedDirectory}/` : ''}${fullName}`;
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
//# sourceMappingURL=AWSS3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AWSS3.js","names":["_crypto","require","_clientS","_filesizeParser","_interopRequireDefault","_mimeTypes","_WebError","_report","_trimPathSlash","namespace","AWSS3","exports","s3Client","init","config","s3Config","region","endpoint","forcePathStyle","undefined","accessKeyId","secretAccessKey","credentials","sessionToken","process","env","AWS_REGION","AWS_S3_ENDPOINT","AWS_S3_FORCE_PATH_STYLE","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SESSION_TOKEN","S3Client","reportDebug","message","data","getS3Client","upload","readStream","bucket","destinationOptions","uploaderOptions","metadata","client","maxSize","fileName","directory","key","trimmedDirectory","trimPathSlash","generateFileDestination","chunks","receivedSize","intMaxSize","fileSizeParser","chunk","length","WebError","statusCode","errorCode","hint","push","body","Buffer","concat","contentType","mime","command","PutObjectCommand","Bucket","Key","Body","ContentType","Metadata","response","send","etag","ETag","Location","ServerSideEncryption","VersionId","get","GetObjectCommand","ContentLength","LastModified","size","download","stream","getFileStream","getMetadata","HeadObjectCommand","delete","DeleteObjectCommand","options","noExtension","extension","mimeTypes","fullName","randomUUID"],"sources":["../../../src/entities/AWSS3/AWSS3.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { Readable } from 'stream';\nimport {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n DeleteObjectCommand,\n HeadObjectCommand,\n S3ClientConfig\n} from '@aws-sdk/client-s3';\nimport fileSizeParser from 'filesize-parser';\nimport mimeTypes from 'mime-types';\nimport {\n S3UploadDestinationOptions,\n S3UploadedFile,\n S3UploaderOptions,\n S3GetResponse,\n S3DownloadResponse,\n AWSS3Config,\n S3FileMetadata\n} from './AWSS3.types';\nimport { WebError } from 'entities/WebError';\nimport { reportDebug } from 'utils/report';\nimport { trimPathSlash } from 'utils/trimPathSlash';\n\nconst namespace = 'engine:aws-s3';\n\n/**\n * Generic AWS S3 wrapper for easy file operations\n * Provides methods for uploading, downloading, streaming, and deleting files\n * Compatible with AWS S3 and S3-compatible services (MinIO, LocalStack, etc.)\n * \n * @example\n * ```typescript\n * // Initialize with custom configuration\n * AWSS3.init({\n * region: 'us-east-1',\n * accessKeyId: 'YOUR_ACCESS_KEY',\n * secretAccessKey: 'YOUR_SECRET_KEY'\n * });\n * \n * // Upload a file\n * const result = await AWSS3.upload(\n * fileStream,\n * 'my-bucket',\n * { directory: 'uploads', mime: 'image/png' }\n * );\n * \n * // Download a file\n * const { data, metadata } = await AWSS3.get('my-bucket', 'uploads/file.png');\n * \n * // Delete a file\n * await AWSS3.delete('my-bucket', 'uploads/file.png');\n * ```\n */\nexport const AWSS3 = {\n s3Client: null as S3Client | null,\n\n /**\n * Initialize AWS S3 with configuration\n * Call this method before using any other methods\n * @param config - AWS S3 configuration options\n */\n init(config?: AWSS3Config): void {\n const s3Config: S3ClientConfig = {};\n\n if (config) {\n if (config.region) s3Config.region = config.region;\n if (config.endpoint) s3Config.endpoint = config.endpoint;\n if (config.forcePathStyle !== undefined) s3Config.forcePathStyle = config.forcePathStyle;\n \n if (config.accessKeyId && config.secretAccessKey) {\n s3Config.credentials = {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n ...(config.sessionToken && { sessionToken: config.sessionToken })\n };\n }\n } else {\n // Use environment variables as fallback\n if (process.env.AWS_REGION) s3Config.region = process.env.AWS_REGION;\n if (process.env.AWS_S3_ENDPOINT) s3Config.endpoint = process.env.AWS_S3_ENDPOINT;\n if (process.env.AWS_S3_FORCE_PATH_STYLE === 'true') s3Config.forcePathStyle = true;\n \n if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {\n s3Config.credentials = {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,\n ...(process.env.AWS_SESSION_TOKEN && { sessionToken: process.env.AWS_SESSION_TOKEN })\n };\n }\n }\n\n this.s3Client = new S3Client(s3Config);\n reportDebug({\n namespace,\n message: 'AWS S3 initialized',\n data: { region: s3Config.region, endpoint: s3Config.endpoint }\n });\n },\n\n /**\n * Get the S3 client instance (auto-initializes if needed)\n * @private\n */\n getS3Client(): S3Client {\n if (!this.s3Client) {\n this.init();\n }\n return this.s3Client!;\n },\n\n /**\n * Upload a file to AWS S3\n * @param readStream - Readable stream of the file content\n * @param bucket - Bucket name where the file should be stored\n * @param destinationOptions - Options for file destination (directory, fileName, etc.)\n * @param uploaderOptions - Additional options like maxSize\n * @param metadata - Optional metadata to attach to the file\n * @returns Promise with file upload result\n * \n * @example\n * ```typescript\n * const fileStream = fs.createReadStream('photo.jpg');\n * const result = await AWSS3.upload(\n * fileStream,\n * 'my-bucket',\n * { directory: 'photos', mime: 'image/jpeg' },\n * { maxSize: '5MB' },\n * { userId: '123', uploadedBy: 'user@example.com' }\n * );\n * console.log(result.Key); // photos/uuid.jpeg\n * ```\n */\n async upload(\n readStream: Readable,\n bucket: string,\n destinationOptions: S3UploadDestinationOptions = {},\n uploaderOptions: S3UploaderOptions = {},\n metadata?: Record<string, string>\n ): Promise<S3UploadedFile> {\n const client = this.getS3Client();\n const { maxSize } = uploaderOptions;\n\n reportDebug({\n namespace,\n message: 'Uploading file to S3',\n data: { bucket, destinationOptions, uploaderOptions }\n });\n\n // Generate the file path\n const { fileName, directory } = destinationOptions;\n\n let key: string;\n if (fileName) {\n const trimmedDirectory = trimPathSlash(directory ?? '/');\n key = trimmedDirectory ? `${trimmedDirectory}/${fileName}` : fileName;\n } else {\n key = this.generateFileDestination(destinationOptions);\n }\n\n // Collect stream data with size validation\n const chunks: Buffer[] = [];\n let receivedSize = 0;\n const intMaxSize = maxSize && fileSizeParser(maxSize);\n\n for await (const chunk of readStream) {\n receivedSize += chunk.length;\n \n if (intMaxSize && receivedSize > intMaxSize) {\n throw new WebError({\n statusCode: 413,\n errorCode: 'file-too-large',\n message: 'File size exceeds the maximum allowed limit',\n hint: { maxSize },\n data: {\n bucket,\n destinationOptions,\n receivedSize,\n maxSize\n }\n });\n }\n \n chunks.push(chunk);\n }\n\n const body = Buffer.concat(chunks);\n\n // Determine content type\n const contentType = destinationOptions.mime || 'application/octet-stream';\n\n const command = new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n ...(metadata && { Metadata: metadata })\n });\n\n const response = await client.send(command);\n\n reportDebug({\n namespace,\n message: 'File uploaded to S3',\n data: { bucket, key, etag: response.ETag }\n });\n\n return {\n Bucket: bucket,\n Key: key,\n ETag: response.ETag,\n Location: `https://${bucket}.s3.amazonaws.com/${key}`,\n ServerSideEncryption: response.ServerSideEncryption,\n VersionId: response.VersionId\n };\n },\n\n /**\n * Download a file from AWS S3 and return its content as Buffer\n * @param bucket - Bucket name\n * @param key - File key (path) in the bucket\n * @returns Promise with file data and metadata\n * \n * @example\n * ```typescript\n * const { data, metadata } = await AWSS3.get('my-bucket', 'photos/image.jpg');\n * console.log(metadata.ContentLength); // File size in bytes\n * console.log(data); // Buffer with file content\n * ```\n */\n async get(bucket: string, key: string): Promise<S3GetResponse> {\n const client = this.getS3Client();\n\n const command = new GetObjectCommand({\n Bucket: bucket,\n Key: key\n });\n\n const response = await client.send(command);\n\n // Convert stream to buffer\n const chunks: Buffer[] = [];\n for await (const chunk of response.Body as Readable) {\n chunks.push(chunk);\n }\n const data = Buffer.concat(chunks);\n\n const metadata: S3FileMetadata = {\n ContentType: response.ContentType,\n ContentLength: response.ContentLength,\n LastModified: response.LastModified,\n ETag: response.ETag,\n Metadata: response.Metadata,\n ServerSideEncryption: response.ServerSideEncryption,\n VersionId: response.VersionId\n };\n\n reportDebug({\n namespace,\n message: 'Downloaded file from S3',\n data: { bucket, key, size: data.length }\n });\n\n return { data, metadata };\n },\n\n /**\n * Get a readable stream for a file from AWS S3\n * Use this for large files or when you need to stream the content\n * @param bucket - Bucket name\n * @param key - File key (path) in the bucket\n * @returns Promise with readable stream and metadata\n * \n * @example\n * ```typescript\n * const { stream, metadata } = await AWSS3.download('my-bucket', 'videos/video.mp4');\n * stream.pipe(res); // Stream to HTTP response\n * ```\n */\n async download(bucket: string, key: string): Promise<S3DownloadResponse> {\n const client = this.getS3Client();\n\n const command = new GetObjectCommand({\n Bucket: bucket,\n Key: key\n });\n\n const response = await client.send(command);\n\n const metadata: S3FileMetadata = {\n ContentType: response.ContentType,\n ContentLength: response.ContentLength,\n LastModified: response.LastModified,\n ETag: response.ETag,\n Metadata: response.Metadata,\n ServerSideEncryption: response.ServerSideEncryption,\n VersionId: response.VersionId\n };\n\n reportDebug({\n namespace,\n message: 'Fetched file stream from S3',\n data: { bucket, key, metadata }\n });\n\n return {\n stream: response.Body as Readable,\n metadata\n };\n },\n\n /**\n * Get a readable stream for a file (without fetching all metadata first)\n * Use this when you don't need metadata and want faster streaming\n * @param bucket - Bucket name\n * @param key - File key (path) in the bucket\n * @returns Promise with readable stream\n * \n * @example\n * ```typescript\n * const stream = await AWSS3.getFileStream('my-bucket', 'audio/song.mp3');\n * stream.pipe(res); // Direct streaming\n * ```\n */\n async getFileStream(bucket: string, key: string): Promise<Readable> {\n const client = this.getS3Client();\n\n const command = new GetObjectCommand({\n Bucket: bucket,\n Key: key\n });\n\n const response = await client.send(command);\n\n reportDebug({\n namespace,\n message: \"Fetched file stream from S3's bucket\",\n data: { bucket, key }\n });\n\n return response.Body as Readable;\n },\n\n /**\n * Get file metadata without downloading the file\n * @param bucket - Bucket name\n * @param key - File key (path) in the bucket\n * @returns Promise with file metadata\n * \n * @example\n * ```typescript\n * const metadata = await AWSS3.getMetadata('my-bucket', 'photos/image.jpg');\n * console.log(metadata.ContentLength); // File size\n * console.log(metadata.ContentType); // MIME type\n * ```\n */\n async getMetadata(bucket: string, key: string): Promise<S3FileMetadata> {\n const client = this.getS3Client();\n\n const command = new HeadObjectCommand({\n Bucket: bucket,\n Key: key\n });\n\n const response = await client.send(command);\n\n return {\n ContentType: response.ContentType,\n ContentLength: response.ContentLength,\n LastModified: response.LastModified,\n ETag: response.ETag,\n Metadata: response.Metadata,\n ServerSideEncryption: response.ServerSideEncryption,\n VersionId: response.VersionId\n };\n },\n\n /**\n * Delete a file from AWS S3\n * @param bucket - Bucket name\n * @param key - File key (path) in the bucket\n * @returns Promise that resolves when deletion is complete\n * \n * @example\n * ```typescript\n * await AWSS3.delete('my-bucket', 'temp/old-file.txt');\n * ```\n */\n async delete(bucket: string, key: string): Promise<void> {\n const client = this.getS3Client();\n\n reportDebug({\n namespace,\n message: 'Deleting file from S3',\n data: { bucket, key }\n });\n\n const command = new DeleteObjectCommand({\n Bucket: bucket,\n Key: key\n });\n\n await client.send(command);\n },\n\n /**\n * Generate a random file path with optional directory and extension\n * Useful for creating unique file names\n * @param options - Options for file destination generation\n * @returns Generated file path\n * \n * @example\n * ```typescript\n * // Generate UUID only\n * const path1 = AWSS3.generateFileDestination();\n * // 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'\n * \n * // With directory and MIME type\n * const path2 = AWSS3.generateFileDestination({\n * directory: 'uploads/images',\n * mime: 'image/jpeg'\n * });\n * // 'uploads/images/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpeg'\n * ```\n */\n generateFileDestination(options: S3UploadDestinationOptions = {}): string {\n const { directory = '/', mime, noExtension = false } = options;\n\n // Determine file extension from MIME type\n const extension =\n mime && mimeTypes.extension(mime) ? mimeTypes.extension(mime) : undefined;\n \n // Build the full name with or without extension\n const fullName =\n extension && !noExtension ? `${randomUUID()}.${extension}` : randomUUID();\n \n // Clean up directory path\n const trimmedDirectory = trimPathSlash(directory);\n return `${trimmedDirectory ? `${trimmedDirectory}/` : ''}${fullName}`;\n }\n};\n"],"mappings":"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","ignoreList":[]}
|