express-storage 1.0.0 → 1.1.3
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 +519 -348
- package/dist/drivers/azure.driver.d.ts +88 -0
- package/dist/drivers/azure.driver.d.ts.map +1 -0
- package/dist/drivers/azure.driver.js +367 -0
- package/dist/drivers/azure.driver.js.map +1 -0
- package/dist/drivers/base.driver.d.ts +125 -24
- package/dist/drivers/base.driver.d.ts.map +1 -1
- package/dist/drivers/base.driver.js +248 -62
- package/dist/drivers/base.driver.js.map +1 -1
- package/dist/drivers/gcs.driver.d.ts +60 -13
- package/dist/drivers/gcs.driver.d.ts.map +1 -1
- package/dist/drivers/gcs.driver.js +242 -41
- package/dist/drivers/gcs.driver.js.map +1 -1
- package/dist/drivers/local.driver.d.ts +89 -12
- package/dist/drivers/local.driver.d.ts.map +1 -1
- package/dist/drivers/local.driver.js +533 -45
- package/dist/drivers/local.driver.js.map +1 -1
- package/dist/drivers/s3.driver.d.ts +64 -13
- package/dist/drivers/s3.driver.d.ts.map +1 -1
- package/dist/drivers/s3.driver.js +269 -41
- package/dist/drivers/s3.driver.js.map +1 -1
- package/dist/factory/driver.factory.d.ts +35 -29
- package/dist/factory/driver.factory.d.ts.map +1 -1
- package/dist/factory/driver.factory.js +119 -59
- package/dist/factory/driver.factory.js.map +1 -1
- package/dist/index.d.ts +23 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -46
- package/dist/index.js.map +1 -1
- package/dist/storage-manager.d.ts +205 -52
- package/dist/storage-manager.d.ts.map +1 -1
- package/dist/storage-manager.js +644 -73
- package/dist/storage-manager.js.map +1 -1
- package/dist/types/storage.types.d.ts +243 -18
- package/dist/types/storage.types.d.ts.map +1 -1
- package/dist/utils/config.utils.d.ts +28 -4
- package/dist/utils/config.utils.d.ts.map +1 -1
- package/dist/utils/config.utils.js +121 -47
- package/dist/utils/config.utils.js.map +1 -1
- package/dist/utils/file.utils.d.ts +111 -14
- package/dist/utils/file.utils.d.ts.map +1 -1
- package/dist/utils/file.utils.js +215 -32
- package/dist/utils/file.utils.js.map +1 -1
- package/package.json +51 -27
- package/dist/drivers/oci.driver.d.ts +0 -37
- package/dist/drivers/oci.driver.d.ts.map +0 -1
- package/dist/drivers/oci.driver.js +0 -84
- package/dist/drivers/oci.driver.js.map +0 -1
|
@@ -1,69 +1,170 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
|
+
import { IStorageDriver, FileUploadResult, PresignedUrlResult, StorageConfig, BlobValidationOptions, BlobValidationResult, ListFilesResult, UploadOptions, DeleteResult } from '../types/storage.types.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* BaseStorageDriver - The foundation that all storage drivers build upon.
|
|
5
|
+
*
|
|
6
|
+
* This abstract class provides common functionality that every driver needs:
|
|
7
|
+
* filename generation, file validation, content reading, and result formatting.
|
|
8
|
+
*
|
|
9
|
+
* If you're building a custom driver, extend this class and implement the
|
|
10
|
+
* abstract methods. You'll get all the helper methods for free.
|
|
4
11
|
*/
|
|
5
12
|
export declare abstract class BaseStorageDriver implements IStorageDriver {
|
|
6
13
|
protected config: StorageConfig;
|
|
7
14
|
constructor(config: StorageConfig);
|
|
8
15
|
/**
|
|
9
|
-
*
|
|
16
|
+
* Builds the full storage path by combining the bucket path with the filename.
|
|
17
|
+
* For example: 'uploads' + 'photo.jpg' = 'uploads/photo.jpg'
|
|
10
18
|
*/
|
|
11
|
-
|
|
19
|
+
protected buildFilePath(fileName: string): string;
|
|
12
20
|
/**
|
|
13
|
-
*
|
|
21
|
+
* Returns the configured bucket path, cleaned up and ready to use.
|
|
14
22
|
*/
|
|
15
|
-
|
|
23
|
+
protected getBucketPath(): string;
|
|
16
24
|
/**
|
|
17
|
-
*
|
|
25
|
+
* Uploads a single file. Each driver implements this differently.
|
|
18
26
|
*/
|
|
19
|
-
abstract
|
|
27
|
+
abstract upload(file: Express.Multer.File, options?: UploadOptions): Promise<FileUploadResult>;
|
|
20
28
|
/**
|
|
21
|
-
*
|
|
29
|
+
* Uploads multiple files with smart concurrency control.
|
|
30
|
+
* Processes up to 10 files at a time to balance speed and system resources.
|
|
22
31
|
*/
|
|
23
|
-
|
|
32
|
+
uploadMultiple(files: Express.Multer.File[], options?: UploadOptions): Promise<FileUploadResult[]>;
|
|
24
33
|
/**
|
|
25
|
-
*
|
|
34
|
+
* Creates a presigned URL for uploading.
|
|
35
|
+
* Each cloud provider has its own way of doing this.
|
|
26
36
|
*/
|
|
27
|
-
|
|
37
|
+
abstract generateUploadUrl(fileName: string, contentType?: string, fileSize?: number): Promise<PresignedUrlResult>;
|
|
28
38
|
/**
|
|
29
|
-
*
|
|
39
|
+
* Creates a presigned URL for viewing/downloading.
|
|
30
40
|
*/
|
|
31
|
-
|
|
41
|
+
abstract generateViewUrl(fileName: string): Promise<PresignedUrlResult>;
|
|
32
42
|
/**
|
|
33
|
-
*
|
|
43
|
+
* Deletes a file from storage.
|
|
34
44
|
*/
|
|
35
45
|
abstract delete(fileName: string): Promise<boolean>;
|
|
36
46
|
/**
|
|
37
|
-
*
|
|
47
|
+
* Lists files with optional filtering and pagination.
|
|
48
|
+
*/
|
|
49
|
+
abstract listFiles(prefix?: string, maxResults?: number, continuationToken?: string): Promise<ListFilesResult>;
|
|
50
|
+
/**
|
|
51
|
+
* Deletes multiple files with smart concurrency control.
|
|
52
|
+
* Returns detailed results so you know exactly what happened with each file.
|
|
38
53
|
*/
|
|
39
|
-
deleteMultiple(fileNames: string[]): Promise<
|
|
54
|
+
deleteMultiple(fileNames: string[]): Promise<DeleteResult[]>;
|
|
40
55
|
/**
|
|
41
|
-
*
|
|
56
|
+
* Creates a unique filename that won't collide with existing files.
|
|
57
|
+
* Format: {timestamp}_{random}_{original_name}.{ext}
|
|
42
58
|
*/
|
|
43
59
|
protected generateFileName(originalName: string): string;
|
|
44
60
|
/**
|
|
45
|
-
*
|
|
61
|
+
* Builds a success response for upload operations.
|
|
46
62
|
*/
|
|
47
63
|
protected createSuccessResult(fileName: string, fileUrl?: string): FileUploadResult;
|
|
48
64
|
/**
|
|
49
|
-
*
|
|
65
|
+
* Builds an error response for upload operations.
|
|
50
66
|
*/
|
|
51
67
|
protected createErrorResult(error: string): FileUploadResult;
|
|
52
68
|
/**
|
|
53
|
-
*
|
|
69
|
+
* Builds a success response for presigned URL operations.
|
|
54
70
|
*/
|
|
55
71
|
protected createPresignedSuccessResult(uploadUrl?: string, viewUrl?: string): PresignedUrlResult;
|
|
56
72
|
/**
|
|
57
|
-
*
|
|
73
|
+
* Builds an error response for presigned URL operations.
|
|
58
74
|
*/
|
|
59
75
|
protected createPresignedErrorResult(error: string): PresignedUrlResult;
|
|
60
76
|
/**
|
|
61
|
-
*
|
|
77
|
+
* Validates a file before upload.
|
|
78
|
+
*
|
|
79
|
+
* Checks for common issues:
|
|
80
|
+
* - Missing file
|
|
81
|
+
* - No original name
|
|
82
|
+
* - No MIME type
|
|
83
|
+
* - Empty content
|
|
84
|
+
*
|
|
85
|
+
* Works with both Multer memory storage (file.buffer) and disk storage (file.path).
|
|
62
86
|
*/
|
|
63
87
|
protected validateFile(file: Express.Multer.File): string[];
|
|
64
88
|
/**
|
|
65
|
-
*
|
|
89
|
+
* Reads the file content, whether it's in memory or on disk.
|
|
90
|
+
*
|
|
91
|
+
* Note: For disk storage, this reads the file but doesn't delete it.
|
|
92
|
+
* Call cleanupTempFile() afterward if you need to remove the temp file.
|
|
93
|
+
*
|
|
94
|
+
* @warning **MEMORY IMPLICATIONS**: This method loads the ENTIRE file into memory.
|
|
95
|
+
* For large files, this can cause memory exhaustion and application crashes.
|
|
96
|
+
*
|
|
97
|
+
* **ALWAYS call `shouldUseStreaming(file)` first** and use `getFileStream()`
|
|
98
|
+
* for files larger than 100MB. Example:
|
|
99
|
+
*
|
|
100
|
+
* ```typescript
|
|
101
|
+
* if (this.shouldUseStreaming(file)) {
|
|
102
|
+
* return this.uploadWithStream(file);
|
|
103
|
+
* }
|
|
104
|
+
* const content = this.getFileContent(file); // Safe for smaller files
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* Memory usage: A 1GB file will allocate ~1GB of heap memory.
|
|
108
|
+
* Node.js default heap limit is ~1.5GB, so large files WILL crash your app.
|
|
109
|
+
*
|
|
110
|
+
* @param file - The Multer file object
|
|
111
|
+
* @returns Buffer containing the entire file contents
|
|
112
|
+
* @throws Error if file has neither buffer nor path
|
|
113
|
+
*/
|
|
114
|
+
protected getFileContent(file: Express.Multer.File): Buffer;
|
|
115
|
+
/**
|
|
116
|
+
* Returns a readable stream for the file content.
|
|
117
|
+
*
|
|
118
|
+
* Use this instead of getFileContent() for large files to avoid
|
|
119
|
+
* loading the entire file into memory. Particularly useful for
|
|
120
|
+
* files larger than 100MB.
|
|
121
|
+
*
|
|
122
|
+
* @param file - The Multer file object
|
|
123
|
+
* @returns A readable stream of the file content
|
|
124
|
+
*/
|
|
125
|
+
protected getFileStream(file: Express.Multer.File): Readable;
|
|
126
|
+
/**
|
|
127
|
+
* Determines if a file should use streaming based on its size.
|
|
128
|
+
*
|
|
129
|
+
* Files larger than 100MB benefit from streaming to reduce memory usage.
|
|
130
|
+
*
|
|
131
|
+
* @param file - The Multer file object
|
|
132
|
+
* @returns true if the file should use streaming
|
|
133
|
+
*/
|
|
134
|
+
protected shouldUseStreaming(file: Express.Multer.File): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Gets the file size, reading from disk if necessary.
|
|
137
|
+
*
|
|
138
|
+
* @param file - The Multer file object
|
|
139
|
+
* @returns The file size in bytes
|
|
140
|
+
*/
|
|
141
|
+
protected getFileSize(file: Express.Multer.File): number;
|
|
142
|
+
/**
|
|
143
|
+
* Removes a temporary file created by Multer disk storage.
|
|
144
|
+
*
|
|
145
|
+
* Call this after a successful upload if you're using disk storage
|
|
146
|
+
* and want to clean up. Memory storage doesn't need this — the
|
|
147
|
+
* garbage collector handles cleanup automatically.
|
|
148
|
+
*
|
|
149
|
+
* @returns true if the file was deleted, false otherwise
|
|
150
|
+
*/
|
|
151
|
+
cleanupTempFile(file: Express.Multer.File): boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Returns how long presigned URLs should be valid (in seconds).
|
|
154
|
+
*
|
|
155
|
+
* Clamps the value to stay within cloud provider limits:
|
|
156
|
+
* - Minimum: 1 second
|
|
157
|
+
* - Maximum: 7 days (604800 seconds)
|
|
158
|
+
* - Default: 10 minutes (600 seconds)
|
|
66
159
|
*/
|
|
67
160
|
protected getPresignedUrlExpiry(): number;
|
|
161
|
+
/**
|
|
162
|
+
* Confirms that an upload completed successfully.
|
|
163
|
+
*
|
|
164
|
+
* The default implementation just checks if the file exists.
|
|
165
|
+
* Azure overrides this to validate file properties since Azure
|
|
166
|
+
* doesn't enforce constraints at the presigned URL level.
|
|
167
|
+
*/
|
|
168
|
+
validateAndConfirmUpload(reference: string, _options?: BlobValidationOptions): Promise<BlobValidationResult>;
|
|
68
169
|
}
|
|
69
170
|
//# sourceMappingURL=base.driver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/base.driver.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"base.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/base.driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,92 +1,94 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { Readable } from 'stream';
|
|
3
|
+
import { generateUniqueFileName, withConcurrencyLimit } from '../utils/file.utils.js';
|
|
4
|
+
/** Threshold for using streaming uploads (100MB) */
|
|
5
|
+
const STREAM_THRESHOLD = 100 * 1024 * 1024;
|
|
2
6
|
/**
|
|
3
|
-
*
|
|
7
|
+
* BaseStorageDriver - The foundation that all storage drivers build upon.
|
|
8
|
+
*
|
|
9
|
+
* This abstract class provides common functionality that every driver needs:
|
|
10
|
+
* filename generation, file validation, content reading, and result formatting.
|
|
11
|
+
*
|
|
12
|
+
* If you're building a custom driver, extend this class and implement the
|
|
13
|
+
* abstract methods. You'll get all the helper methods for free.
|
|
4
14
|
*/
|
|
5
15
|
export class BaseStorageDriver {
|
|
6
16
|
constructor(config) {
|
|
7
17
|
this.config = config;
|
|
8
18
|
}
|
|
9
19
|
/**
|
|
10
|
-
*
|
|
20
|
+
* Builds the full storage path by combining the bucket path with the filename.
|
|
21
|
+
* For example: 'uploads' + 'photo.jpg' = 'uploads/photo.jpg'
|
|
11
22
|
*/
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const result = await this.upload(file);
|
|
17
|
-
results.push(result);
|
|
18
|
-
}
|
|
19
|
-
catch (error) {
|
|
20
|
-
results.push({
|
|
21
|
-
success: false,
|
|
22
|
-
error: error instanceof Error ? error.message : 'Upload failed',
|
|
23
|
-
});
|
|
24
|
-
}
|
|
23
|
+
buildFilePath(fileName) {
|
|
24
|
+
const bucketPath = this.config.bucketPath?.trim();
|
|
25
|
+
if (!bucketPath || bucketPath === '' || bucketPath === '/') {
|
|
26
|
+
return fileName;
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
const normalizedPath = bucketPath.replace(/^\/+|\/+$/g, '');
|
|
29
|
+
return `${normalizedPath}/${fileName}`;
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
|
-
*
|
|
32
|
+
* Returns the configured bucket path, cleaned up and ready to use.
|
|
30
33
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
for (const fileName of fileNames) {
|
|
34
|
-
try {
|
|
35
|
-
const result = await this.generateUploadUrl(fileName);
|
|
36
|
-
results.push(result);
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
results.push({
|
|
40
|
-
success: false,
|
|
41
|
-
error: error instanceof Error ? error.message : 'Failed to generate upload URL',
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return results;
|
|
34
|
+
getBucketPath() {
|
|
35
|
+
return this.config.bucketPath?.trim()?.replace(/^\/+|\/+$/g, '') || '';
|
|
46
36
|
}
|
|
47
37
|
/**
|
|
48
|
-
*
|
|
38
|
+
* Uploads multiple files with smart concurrency control.
|
|
39
|
+
* Processes up to 10 files at a time to balance speed and system resources.
|
|
49
40
|
*/
|
|
50
|
-
async
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
async uploadMultiple(files, options) {
|
|
42
|
+
if (!files || files.length === 0) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return withConcurrencyLimit(files, async (file) => {
|
|
53
46
|
try {
|
|
54
|
-
|
|
55
|
-
results.push(result);
|
|
47
|
+
return await this.upload(file, options);
|
|
56
48
|
}
|
|
57
49
|
catch (error) {
|
|
58
|
-
|
|
50
|
+
return {
|
|
59
51
|
success: false,
|
|
60
|
-
error: error instanceof Error ? error.message : 'Failed to
|
|
61
|
-
}
|
|
52
|
+
error: error instanceof Error ? error.message : 'Failed to upload file',
|
|
53
|
+
};
|
|
62
54
|
}
|
|
63
|
-
}
|
|
64
|
-
return results;
|
|
55
|
+
}, { maxConcurrent: 10 });
|
|
65
56
|
}
|
|
66
57
|
/**
|
|
67
|
-
*
|
|
58
|
+
* Deletes multiple files with smart concurrency control.
|
|
59
|
+
* Returns detailed results so you know exactly what happened with each file.
|
|
68
60
|
*/
|
|
69
61
|
async deleteMultiple(fileNames) {
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
if (!fileNames || fileNames.length === 0) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
return withConcurrencyLimit(fileNames, async (fileName) => {
|
|
72
66
|
try {
|
|
73
|
-
const
|
|
74
|
-
|
|
67
|
+
const success = await this.delete(fileName);
|
|
68
|
+
const result = { success, fileName };
|
|
69
|
+
if (!success) {
|
|
70
|
+
result.error = 'File not found or already deleted';
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
75
73
|
}
|
|
76
74
|
catch (error) {
|
|
77
|
-
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
fileName,
|
|
78
|
+
error: error instanceof Error ? error.message : 'Failed to delete file',
|
|
79
|
+
};
|
|
78
80
|
}
|
|
79
|
-
}
|
|
80
|
-
return results;
|
|
81
|
+
}, { maxConcurrent: 10 });
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
83
|
-
*
|
|
84
|
+
* Creates a unique filename that won't collide with existing files.
|
|
85
|
+
* Format: {timestamp}_{random}_{original_name}.{ext}
|
|
84
86
|
*/
|
|
85
87
|
generateFileName(originalName) {
|
|
86
88
|
return generateUniqueFileName(originalName);
|
|
87
89
|
}
|
|
88
90
|
/**
|
|
89
|
-
*
|
|
91
|
+
* Builds a success response for upload operations.
|
|
90
92
|
*/
|
|
91
93
|
createSuccessResult(fileName, fileUrl) {
|
|
92
94
|
const result = {
|
|
@@ -99,7 +101,7 @@ export class BaseStorageDriver {
|
|
|
99
101
|
return result;
|
|
100
102
|
}
|
|
101
103
|
/**
|
|
102
|
-
*
|
|
104
|
+
* Builds an error response for upload operations.
|
|
103
105
|
*/
|
|
104
106
|
createErrorResult(error) {
|
|
105
107
|
return {
|
|
@@ -108,7 +110,7 @@ export class BaseStorageDriver {
|
|
|
108
110
|
};
|
|
109
111
|
}
|
|
110
112
|
/**
|
|
111
|
-
*
|
|
113
|
+
* Builds a success response for presigned URL operations.
|
|
112
114
|
*/
|
|
113
115
|
createPresignedSuccessResult(uploadUrl, viewUrl) {
|
|
114
116
|
const result = {
|
|
@@ -123,7 +125,7 @@ export class BaseStorageDriver {
|
|
|
123
125
|
return result;
|
|
124
126
|
}
|
|
125
127
|
/**
|
|
126
|
-
*
|
|
128
|
+
* Builds an error response for presigned URL operations.
|
|
127
129
|
*/
|
|
128
130
|
createPresignedErrorResult(error) {
|
|
129
131
|
return {
|
|
@@ -132,7 +134,15 @@ export class BaseStorageDriver {
|
|
|
132
134
|
};
|
|
133
135
|
}
|
|
134
136
|
/**
|
|
135
|
-
*
|
|
137
|
+
* Validates a file before upload.
|
|
138
|
+
*
|
|
139
|
+
* Checks for common issues:
|
|
140
|
+
* - Missing file
|
|
141
|
+
* - No original name
|
|
142
|
+
* - No MIME type
|
|
143
|
+
* - Empty content
|
|
144
|
+
*
|
|
145
|
+
* Works with both Multer memory storage (file.buffer) and disk storage (file.path).
|
|
136
146
|
*/
|
|
137
147
|
validateFile(file) {
|
|
138
148
|
const errors = [];
|
|
@@ -146,16 +156,192 @@ export class BaseStorageDriver {
|
|
|
146
156
|
if (!file.mimetype) {
|
|
147
157
|
errors.push('File must have a MIME type');
|
|
148
158
|
}
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
// Check for file content (could be in memory or on disk)
|
|
160
|
+
const hasBuffer = (file.buffer?.length ?? 0) > 0;
|
|
161
|
+
const hasPath = typeof file.path === 'string' && file.path.length > 0;
|
|
162
|
+
const hasEmptyBuffer = file.buffer !== null && file.buffer !== undefined && file.buffer.length === 0;
|
|
163
|
+
if (hasEmptyBuffer && !hasPath) {
|
|
164
|
+
errors.push('File is empty (0 bytes)');
|
|
165
|
+
}
|
|
166
|
+
else if (!hasBuffer && !hasPath) {
|
|
167
|
+
errors.push('File must have either buffer (memory storage) or path (disk storage)');
|
|
168
|
+
}
|
|
169
|
+
// For disk storage, verify the file exists and isn't empty
|
|
170
|
+
if (hasPath && !hasBuffer) {
|
|
171
|
+
try {
|
|
172
|
+
const stats = fs.statSync(file.path);
|
|
173
|
+
if (stats.size === 0) {
|
|
174
|
+
errors.push('File is empty (0 bytes)');
|
|
175
|
+
}
|
|
176
|
+
if (!file.size || file.size === 0) {
|
|
177
|
+
file.size = stats.size;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
errors.push('Cannot read file from disk storage path');
|
|
182
|
+
}
|
|
151
183
|
}
|
|
152
184
|
return errors;
|
|
153
185
|
}
|
|
154
186
|
/**
|
|
155
|
-
*
|
|
187
|
+
* Reads the file content, whether it's in memory or on disk.
|
|
188
|
+
*
|
|
189
|
+
* Note: For disk storage, this reads the file but doesn't delete it.
|
|
190
|
+
* Call cleanupTempFile() afterward if you need to remove the temp file.
|
|
191
|
+
*
|
|
192
|
+
* @warning **MEMORY IMPLICATIONS**: This method loads the ENTIRE file into memory.
|
|
193
|
+
* For large files, this can cause memory exhaustion and application crashes.
|
|
194
|
+
*
|
|
195
|
+
* **ALWAYS call `shouldUseStreaming(file)` first** and use `getFileStream()`
|
|
196
|
+
* for files larger than 100MB. Example:
|
|
197
|
+
*
|
|
198
|
+
* ```typescript
|
|
199
|
+
* if (this.shouldUseStreaming(file)) {
|
|
200
|
+
* return this.uploadWithStream(file);
|
|
201
|
+
* }
|
|
202
|
+
* const content = this.getFileContent(file); // Safe for smaller files
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* Memory usage: A 1GB file will allocate ~1GB of heap memory.
|
|
206
|
+
* Node.js default heap limit is ~1.5GB, so large files WILL crash your app.
|
|
207
|
+
*
|
|
208
|
+
* @param file - The Multer file object
|
|
209
|
+
* @returns Buffer containing the entire file contents
|
|
210
|
+
* @throws Error if file has neither buffer nor path
|
|
211
|
+
*/
|
|
212
|
+
getFileContent(file) {
|
|
213
|
+
if ((file.buffer?.length ?? 0) > 0) {
|
|
214
|
+
return file.buffer;
|
|
215
|
+
}
|
|
216
|
+
if (file.path) {
|
|
217
|
+
return fs.readFileSync(file.path);
|
|
218
|
+
}
|
|
219
|
+
throw new Error('File has neither buffer nor path - cannot read content');
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Returns a readable stream for the file content.
|
|
223
|
+
*
|
|
224
|
+
* Use this instead of getFileContent() for large files to avoid
|
|
225
|
+
* loading the entire file into memory. Particularly useful for
|
|
226
|
+
* files larger than 100MB.
|
|
227
|
+
*
|
|
228
|
+
* @param file - The Multer file object
|
|
229
|
+
* @returns A readable stream of the file content
|
|
230
|
+
*/
|
|
231
|
+
getFileStream(file) {
|
|
232
|
+
if ((file.buffer?.length ?? 0) > 0) {
|
|
233
|
+
return Readable.from(file.buffer);
|
|
234
|
+
}
|
|
235
|
+
if (file.path) {
|
|
236
|
+
return fs.createReadStream(file.path);
|
|
237
|
+
}
|
|
238
|
+
throw new Error('File has neither buffer nor path - cannot create stream');
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Determines if a file should use streaming based on its size.
|
|
242
|
+
*
|
|
243
|
+
* Files larger than 100MB benefit from streaming to reduce memory usage.
|
|
244
|
+
*
|
|
245
|
+
* @param file - The Multer file object
|
|
246
|
+
* @returns true if the file should use streaming
|
|
247
|
+
*/
|
|
248
|
+
shouldUseStreaming(file) {
|
|
249
|
+
const size = file.size || 0;
|
|
250
|
+
return size > STREAM_THRESHOLD;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Gets the file size, reading from disk if necessary.
|
|
254
|
+
*
|
|
255
|
+
* @param file - The Multer file object
|
|
256
|
+
* @returns The file size in bytes
|
|
257
|
+
*/
|
|
258
|
+
getFileSize(file) {
|
|
259
|
+
if (file.size && file.size > 0) {
|
|
260
|
+
return file.size;
|
|
261
|
+
}
|
|
262
|
+
if (file.buffer?.length) {
|
|
263
|
+
return file.buffer.length;
|
|
264
|
+
}
|
|
265
|
+
if (file.path) {
|
|
266
|
+
try {
|
|
267
|
+
const stats = fs.statSync(file.path);
|
|
268
|
+
return stats.size;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Removes a temporary file created by Multer disk storage.
|
|
278
|
+
*
|
|
279
|
+
* Call this after a successful upload if you're using disk storage
|
|
280
|
+
* and want to clean up. Memory storage doesn't need this — the
|
|
281
|
+
* garbage collector handles cleanup automatically.
|
|
282
|
+
*
|
|
283
|
+
* @returns true if the file was deleted, false otherwise
|
|
284
|
+
*/
|
|
285
|
+
cleanupTempFile(file) {
|
|
286
|
+
if (file.path && fs.existsSync(file.path)) {
|
|
287
|
+
try {
|
|
288
|
+
fs.unlinkSync(file.path);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Returns how long presigned URLs should be valid (in seconds).
|
|
299
|
+
*
|
|
300
|
+
* Clamps the value to stay within cloud provider limits:
|
|
301
|
+
* - Minimum: 1 second
|
|
302
|
+
* - Maximum: 7 days (604800 seconds)
|
|
303
|
+
* - Default: 10 minutes (600 seconds)
|
|
156
304
|
*/
|
|
157
305
|
getPresignedUrlExpiry() {
|
|
158
|
-
|
|
306
|
+
const MAX_EXPIRY = 604800;
|
|
307
|
+
const MIN_EXPIRY = 1;
|
|
308
|
+
const DEFAULT_EXPIRY = 600;
|
|
309
|
+
const expiry = this.config.presignedUrlExpiry;
|
|
310
|
+
if (expiry === undefined || expiry === null || Number.isNaN(expiry)) {
|
|
311
|
+
return DEFAULT_EXPIRY;
|
|
312
|
+
}
|
|
313
|
+
if (expiry < MIN_EXPIRY) {
|
|
314
|
+
return MIN_EXPIRY;
|
|
315
|
+
}
|
|
316
|
+
if (expiry > MAX_EXPIRY) {
|
|
317
|
+
return MAX_EXPIRY;
|
|
318
|
+
}
|
|
319
|
+
return expiry;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Confirms that an upload completed successfully.
|
|
323
|
+
*
|
|
324
|
+
* The default implementation just checks if the file exists.
|
|
325
|
+
* Azure overrides this to validate file properties since Azure
|
|
326
|
+
* doesn't enforce constraints at the presigned URL level.
|
|
327
|
+
*/
|
|
328
|
+
async validateAndConfirmUpload(reference, _options) {
|
|
329
|
+
const viewResult = await this.generateViewUrl(reference);
|
|
330
|
+
if (viewResult.success) {
|
|
331
|
+
const result = {
|
|
332
|
+
success: true,
|
|
333
|
+
reference,
|
|
334
|
+
expiresIn: this.getPresignedUrlExpiry(),
|
|
335
|
+
};
|
|
336
|
+
if (viewResult.viewUrl) {
|
|
337
|
+
result.viewUrl = viewResult.viewUrl;
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
success: false,
|
|
343
|
+
error: viewResult.error || 'File not found',
|
|
344
|
+
};
|
|
159
345
|
}
|
|
160
346
|
}
|
|
161
347
|
//# sourceMappingURL=base.driver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.driver.js","sourceRoot":"","sources":["../../src/drivers/base.driver.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"base.driver.js","sourceRoot":"","sources":["../../src/drivers/base.driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEtF,oDAAoD;AACpD,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C;;;;;;;;GAQG;AACH,MAAM,OAAgB,iBAAiB;IAGrC,YAAY,MAAqB;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;;OAGG;IACO,aAAa,CAAC,QAAgB;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;QAClD,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,EAAE,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;YAC3D,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAC5D,OAAO,GAAG,cAAc,IAAI,QAAQ,EAAE,CAAC;IACzC,CAAC;IAED;;OAEG;IACO,aAAa;QACrB,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IACzE,CAAC;IAOD;;;OAGG;IACH,KAAK,CAAC,cAAc,CAAC,KAA4B,EAAE,OAAuB;QACxE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,oBAAoB,CACzB,KAAK,EACL,KAAK,EAAE,IAAI,EAA6B,EAAE;YACxC,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC1C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB;iBACxE,CAAC;YACJ,CAAC;QACH,CAAC,EACD,EAAE,aAAa,EAAE,EAAE,EAAE,CACtB,CAAC;IACJ,CAAC;IAuBD;;;OAGG;IACH,KAAK,CAAC,cAAc,CAAC,SAAmB;QACtC,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,oBAAoB,CACzB,SAAS,EACT,KAAK,EAAE,QAAQ,EAAyB,EAAE;YACxC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC5C,MAAM,MAAM,GAAiB,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;gBACnD,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,CAAC,KAAK,GAAG,mCAAmC,CAAC;gBACrD,CAAC;gBACD,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,QAAQ;oBACR,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB;iBACxE,CAAC;YACJ,CAAC;QACH,CAAC,EACD,EAAE,aAAa,EAAE,EAAE,EAAE,CACtB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACO,gBAAgB,CAAC,YAAoB;QAC7C,OAAO,sBAAsB,CAAC,YAAY,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACO,mBAAmB,CAAC,QAAgB,EAAE,OAAgB;QAC9D,MAAM,MAAM,GAAqB;YAC/B,OAAO,EAAE,IAAI;YACb,QAAQ;SACT,CAAC;QACF,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;QAC3B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACO,iBAAiB,CAAC,KAAa;QACvC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK;SACN,CAAC;IACJ,CAAC;IAED;;OAEG;IACO,4BAA4B,CAAC,SAAkB,EAAE,OAAgB;QACzE,MAAM,MAAM,GAAuB;YACjC,OAAO,EAAE,IAAI;SACd,CAAC;QACF,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC;QAC/B,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;QAC3B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACO,0BAA0B,CAAC,KAAa;QAChD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK;SACN,CAAC;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACO,YAAY,CAAC,IAAyB;QAC9C,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAChC,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QACjD,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC5C,CAAC;QAED,yDAAyD;QACzD,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QACtE,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;QAErG,IAAI,cAAc,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACzC,CAAC;aAAM,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;QACtF,CAAC;QAED,2DAA2D;QAC3D,IAAI,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrC,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBACrB,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;gBACzC,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBAClC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;gBACzB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACO,cAAc,CAAC,IAAyB;QAChD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IAED;;;;;;;;;OASG;IACO,aAAa,CAAC,IAAyB;QAC/C,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,OAAO,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED;;;;;;;OAOG;IACO,kBAAkB,CAAC,IAAyB;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;QAC5B,OAAO,IAAI,GAAG,gBAAgB,CAAC;IACjC,CAAC;IAED;;;;;OAKG;IACO,WAAW,CAAC,IAAyB;QAC7C,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QAC5B,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrC,OAAO,KAAK,CAAC,IAAI,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,CAAC;YACX,CAAC;QACH,CAAC;QAED,OAAO,CAAC,CAAC;IACX,CAAC;IAED;;;;;;;;OAQG;IACI,eAAe,CAAC,IAAyB;QAC9C,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,IAAI,CAAC;gBACH,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACzB,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;;OAOG;IACO,qBAAqB;QAC7B,MAAM,UAAU,GAAG,MAAM,CAAC;QAC1B,MAAM,UAAU,GAAG,CAAC,CAAC;QACrB,MAAM,cAAc,GAAG,GAAG,CAAC;QAE3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC;QAE9C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACpE,OAAO,cAAc,CAAC;QACxB,CAAC;QAED,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;YACxB,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;YACxB,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,wBAAwB,CAAC,SAAiB,EAAE,QAAgC;QAChF,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QAEzD,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,MAAM,MAAM,GAAyB;gBACnC,OAAO,EAAE,IAAI;gBACb,SAAS;gBACT,SAAS,EAAE,IAAI,CAAC,qBAAqB,EAAE;aACxC,CAAC;YACF,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,MAAM,CAAC,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;YACtC,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,UAAU,CAAC,KAAK,IAAI,gBAAgB;SAC5C,CAAC;IACJ,CAAC;CACF"}
|