express-storage 1.0.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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
package/dist/utils/file.utils.js
CHANGED
|
@@ -1,35 +1,97 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
+
import crypto from 'crypto';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Creates a unique filename that won't collide with existing files.
|
|
6
|
+
*
|
|
7
|
+
* Format: {timestamp}_{random}_{sanitized_name}.{extension}
|
|
8
|
+
* Example: 1769104576000_a1b2c3d4e5_my_image.jpeg
|
|
9
|
+
*
|
|
10
|
+
* The random part uses crypto.randomBytes() for extra collision resistance
|
|
11
|
+
* in high-throughput scenarios.
|
|
5
12
|
*/
|
|
6
13
|
export function generateUniqueFileName(originalName) {
|
|
7
|
-
const timestamp =
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
const timestamp = Date.now();
|
|
15
|
+
const randomSuffix = crypto.randomBytes(6).toString('hex').substring(0, 10);
|
|
16
|
+
// Handle dotfiles like .gitignore or .env (they have no extension)
|
|
17
|
+
let extension;
|
|
18
|
+
let baseName;
|
|
19
|
+
if (originalName.startsWith('.') && !originalName.slice(1).includes('.')) {
|
|
20
|
+
extension = '';
|
|
21
|
+
baseName = sanitizeFileName(originalName);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
extension = path.extname(originalName).toLowerCase();
|
|
25
|
+
const sanitizedName = sanitizeFileName(originalName);
|
|
26
|
+
baseName = path.basename(sanitizedName, path.extname(sanitizedName));
|
|
27
|
+
}
|
|
28
|
+
if (!baseName || baseName.trim() === '') {
|
|
29
|
+
baseName = 'file';
|
|
30
|
+
}
|
|
31
|
+
return `${timestamp}_${randomSuffix}_${baseName}${extension}`;
|
|
12
32
|
}
|
|
13
33
|
/**
|
|
14
|
-
*
|
|
34
|
+
* Makes a filename safe for storage by removing problematic characters.
|
|
35
|
+
*
|
|
36
|
+
* Replaces anything that isn't alphanumeric, a dot, or a hyphen with underscores.
|
|
37
|
+
* This ensures compatibility with all filesystems and cloud storage providers.
|
|
38
|
+
*
|
|
39
|
+
* Note: Unicode characters like Chinese or emojis become underscores.
|
|
40
|
+
* If you need to preserve these, consider using your own sanitization function.
|
|
15
41
|
*/
|
|
16
42
|
export function sanitizeFileName(fileName) {
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
.replace(/
|
|
20
|
-
.replace(
|
|
43
|
+
const sanitized = fileName
|
|
44
|
+
.normalize('NFC')
|
|
45
|
+
.replace(/[^a-zA-Z0-9.-]/g, '_')
|
|
46
|
+
.replace(/_{2,}/g, '_')
|
|
47
|
+
.replace(/^_+|_+$/g, '');
|
|
48
|
+
return sanitized || 'file';
|
|
21
49
|
}
|
|
22
50
|
/**
|
|
23
|
-
*
|
|
51
|
+
* Checks if a filename is safe to use.
|
|
52
|
+
*
|
|
53
|
+
* Rejects:
|
|
54
|
+
* - Empty filenames
|
|
55
|
+
* - Filenames over 255 characters
|
|
56
|
+
* - Path traversal attempts (../, /, \)
|
|
57
|
+
* - Null bytes
|
|
58
|
+
*
|
|
59
|
+
* Returns an error message if invalid, null if OK.
|
|
60
|
+
*/
|
|
61
|
+
export function validateFileName(fileName) {
|
|
62
|
+
if (!fileName || typeof fileName !== 'string') {
|
|
63
|
+
return 'Filename is required';
|
|
64
|
+
}
|
|
65
|
+
const trimmed = fileName.trim();
|
|
66
|
+
if (trimmed.length === 0) {
|
|
67
|
+
return 'Filename cannot be empty';
|
|
68
|
+
}
|
|
69
|
+
if (trimmed.length > 255) {
|
|
70
|
+
return 'Filename is too long (max 255 characters)';
|
|
71
|
+
}
|
|
72
|
+
if (trimmed.includes('..') || trimmed.includes('/') || trimmed.includes('\\')) {
|
|
73
|
+
return 'Filename cannot contain path separators or traversal sequences';
|
|
74
|
+
}
|
|
75
|
+
if (trimmed.includes('\0')) {
|
|
76
|
+
return 'Filename cannot contain null bytes';
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Creates a date-based folder path: YYYY/MM
|
|
82
|
+
*
|
|
83
|
+
* Uses UTC to keep things consistent across timezones.
|
|
84
|
+
* Example: For January 2026 -> 'uploads/2026/01'
|
|
24
85
|
*/
|
|
25
86
|
export function createMonthBasedPath(basePath) {
|
|
26
87
|
const now = new Date();
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
return path.join(basePath,
|
|
88
|
+
const year = now.getUTCFullYear();
|
|
89
|
+
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
|
90
|
+
return path.join(basePath, year.toString(), month);
|
|
30
91
|
}
|
|
31
92
|
/**
|
|
32
|
-
*
|
|
93
|
+
* Creates a directory if it doesn't exist.
|
|
94
|
+
* Also creates any parent directories needed (recursive).
|
|
33
95
|
*/
|
|
34
96
|
export function ensureDirectoryExists(dirPath) {
|
|
35
97
|
if (!fs.existsSync(dirPath)) {
|
|
@@ -37,48 +99,72 @@ export function ensureDirectoryExists(dirPath) {
|
|
|
37
99
|
}
|
|
38
100
|
}
|
|
39
101
|
/**
|
|
40
|
-
*
|
|
102
|
+
* Converts bytes to a human-readable string.
|
|
103
|
+
*
|
|
104
|
+
* Examples:
|
|
105
|
+
* - 1024 -> "1 KB"
|
|
106
|
+
* - 1048576 -> "1 MB"
|
|
107
|
+
* - 0 -> "0 Bytes"
|
|
41
108
|
*/
|
|
42
109
|
export function formatFileSize(bytes) {
|
|
43
110
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
44
|
-
if (bytes
|
|
111
|
+
if (typeof bytes !== 'number' || Number.isNaN(bytes)) {
|
|
112
|
+
return 'Invalid size';
|
|
113
|
+
}
|
|
114
|
+
if (!Number.isFinite(bytes)) {
|
|
115
|
+
return bytes > 0 ? 'Infinite' : 'Invalid size';
|
|
116
|
+
}
|
|
117
|
+
if (bytes < 0) {
|
|
118
|
+
return 'Invalid size (negative)';
|
|
119
|
+
}
|
|
120
|
+
if (bytes === 0) {
|
|
45
121
|
return '0 Bytes';
|
|
46
|
-
|
|
47
|
-
|
|
122
|
+
}
|
|
123
|
+
let i = 0;
|
|
124
|
+
let size = bytes;
|
|
125
|
+
while (size >= 1024 && i < sizes.length - 1) {
|
|
126
|
+
size /= 1024;
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
return Math.round(size * 100) / 100 + ' ' + sizes[i];
|
|
48
130
|
}
|
|
49
131
|
/**
|
|
50
|
-
*
|
|
132
|
+
* Checks if a file size is within the allowed limit.
|
|
51
133
|
*/
|
|
52
134
|
export function validateFileSize(fileSize, maxSize) {
|
|
53
135
|
return fileSize <= maxSize;
|
|
54
136
|
}
|
|
55
137
|
/**
|
|
56
|
-
*
|
|
138
|
+
* Checks if a MIME type is in the allowed list.
|
|
57
139
|
*/
|
|
58
140
|
export function validateFileType(mimeType, allowedTypes) {
|
|
59
141
|
return allowedTypes.includes(mimeType);
|
|
60
142
|
}
|
|
61
143
|
/**
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get file extension from filename
|
|
144
|
+
* Extracts the file extension (lowercase, includes the dot).
|
|
145
|
+
*
|
|
146
|
+
* Examples:
|
|
147
|
+
* - 'photo.jpg' -> '.jpg'
|
|
148
|
+
* - '.gitignore' -> '' (dotfiles have no extension)
|
|
149
|
+
* - 'archive.tar.gz' -> '.gz' (only the last extension)
|
|
70
150
|
*/
|
|
71
151
|
export function getFileExtension(fileName) {
|
|
152
|
+
if (!fileName)
|
|
153
|
+
return '';
|
|
154
|
+
// Dotfiles like .gitignore don't have extensions
|
|
155
|
+
if (fileName.startsWith('.') && !fileName.slice(1).includes('.')) {
|
|
156
|
+
return '';
|
|
157
|
+
}
|
|
72
158
|
return path.extname(fileName).toLowerCase();
|
|
73
159
|
}
|
|
74
160
|
/**
|
|
75
|
-
*
|
|
161
|
+
* Checks if a MIME type indicates an image.
|
|
76
162
|
*/
|
|
77
163
|
export function isImageFile(mimeType) {
|
|
78
164
|
return mimeType.startsWith('image/');
|
|
79
165
|
}
|
|
80
166
|
/**
|
|
81
|
-
*
|
|
167
|
+
* Checks if a MIME type indicates a document (PDF, Word, Excel, etc.).
|
|
82
168
|
*/
|
|
83
169
|
export function isDocumentFile(mimeType) {
|
|
84
170
|
const documentTypes = [
|
|
@@ -92,4 +178,101 @@ export function isDocumentFile(mimeType) {
|
|
|
92
178
|
];
|
|
93
179
|
return documentTypes.includes(mimeType);
|
|
94
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Retries an async operation with exponential backoff.
|
|
183
|
+
*
|
|
184
|
+
* Great for cloud operations that might fail due to network blips
|
|
185
|
+
* or rate limiting.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // Retry up to 3 times with increasing delays
|
|
189
|
+
* const result = await withRetry(() => storage.uploadFile(file));
|
|
190
|
+
*
|
|
191
|
+
* // More aggressive retry strategy
|
|
192
|
+
* const result = await withRetry(() => fetchData(), {
|
|
193
|
+
* maxAttempts: 5,
|
|
194
|
+
* baseDelay: 500
|
|
195
|
+
* });
|
|
196
|
+
*/
|
|
197
|
+
export async function withRetry(operation, options = {}) {
|
|
198
|
+
const { maxAttempts = 3, baseDelay = 1000, maxDelay = 10000, exponentialBackoff = true, } = options;
|
|
199
|
+
let lastError;
|
|
200
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
201
|
+
try {
|
|
202
|
+
return await operation();
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
206
|
+
if (attempt < maxAttempts) {
|
|
207
|
+
const delay = exponentialBackoff
|
|
208
|
+
? Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay)
|
|
209
|
+
: baseDelay;
|
|
210
|
+
await sleep(delay);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
throw lastError || new Error(`Operation failed after ${maxAttempts} attempts`);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Pauses execution for the specified number of milliseconds.
|
|
218
|
+
*/
|
|
219
|
+
export function sleep(ms) {
|
|
220
|
+
return new Promise(resolve => globalThis.setTimeout(resolve, ms));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Processes an array with a concurrency limit.
|
|
224
|
+
*
|
|
225
|
+
* Prevents overwhelming APIs or running out of resources by limiting
|
|
226
|
+
* how many operations run at once.
|
|
227
|
+
*
|
|
228
|
+
* Implementation uses pre-assigned chunk-based processing to avoid any
|
|
229
|
+
* potential race conditions with shared index counters. Each worker gets
|
|
230
|
+
* its own set of indices to process.
|
|
231
|
+
*
|
|
232
|
+
* Note: The input array is snapshotted at the start to prevent issues
|
|
233
|
+
* if the caller modifies it during processing.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* // Upload 100 files, but only 10 at a time
|
|
237
|
+
* const results = await withConcurrencyLimit(
|
|
238
|
+
* files,
|
|
239
|
+
* (file) => uploadFile(file),
|
|
240
|
+
* { maxConcurrent: 10 }
|
|
241
|
+
* );
|
|
242
|
+
*/
|
|
243
|
+
export async function withConcurrencyLimit(items, operation, options = {}) {
|
|
244
|
+
const { maxConcurrent = 10 } = options;
|
|
245
|
+
if (items.length === 0) {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
// Snapshot the array to prevent issues if caller modifies it during processing
|
|
249
|
+
const itemsCopy = [...items];
|
|
250
|
+
const itemCount = itemsCopy.length;
|
|
251
|
+
// For small batches, just process everything at once
|
|
252
|
+
if (itemCount <= maxConcurrent) {
|
|
253
|
+
return Promise.all(itemsCopy.map((item, index) => operation(item, index)));
|
|
254
|
+
}
|
|
255
|
+
const results = new Array(itemCount);
|
|
256
|
+
const workerCount = Math.min(maxConcurrent, itemCount);
|
|
257
|
+
// Pre-assign indices to each worker to avoid any race conditions
|
|
258
|
+
// Each worker gets a dedicated set of indices: worker 0 gets [0, workerCount, 2*workerCount, ...],
|
|
259
|
+
// worker 1 gets [1, workerCount+1, 2*workerCount+1, ...], etc.
|
|
260
|
+
const createWorker = (workerId) => {
|
|
261
|
+
return (async () => {
|
|
262
|
+
for (let index = workerId; index < itemCount; index += workerCount) {
|
|
263
|
+
const item = itemsCopy[index];
|
|
264
|
+
if (item !== undefined) {
|
|
265
|
+
results[index] = await operation(item, index);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
})();
|
|
269
|
+
};
|
|
270
|
+
// Start all workers with their pre-assigned index ranges
|
|
271
|
+
const workers = [];
|
|
272
|
+
for (let i = 0; i < workerCount; i++) {
|
|
273
|
+
workers.push(createWorker(i));
|
|
274
|
+
}
|
|
275
|
+
await Promise.all(workers);
|
|
276
|
+
return results;
|
|
277
|
+
}
|
|
95
278
|
//# sourceMappingURL=file.utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.utils.js","sourceRoot":"","sources":["../../src/utils/file.utils.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"file.utils.js","sourceRoot":"","sources":["../../src/utils/file.utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-storage",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "Secure, unified file upload and cloud storage management for Express.js. One API for AWS S3, Google Cloud Storage, Azure Blob Storage, and local disk. Built-in security with path traversal prevention, file validation, presigned URLs, and automatic filename sanitization. Switch storage providers with zero code changes — just update your environment variables.",
|
|
5
5
|
"homepage": "https://github.com/th3hero/express-storage#readme",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/th3hero/express-storage/issues"
|
|
@@ -30,26 +30,48 @@
|
|
|
30
30
|
"express",
|
|
31
31
|
"file-upload",
|
|
32
32
|
"storage",
|
|
33
|
+
"upload",
|
|
34
|
+
"file-storage",
|
|
35
|
+
"cloud-storage",
|
|
33
36
|
"s3",
|
|
37
|
+
"aws-s3",
|
|
38
|
+
"amazon-s3",
|
|
34
39
|
"gcs",
|
|
35
|
-
"
|
|
40
|
+
"google-cloud-storage",
|
|
41
|
+
"azure",
|
|
42
|
+
"azure-blob",
|
|
43
|
+
"azure-blob-storage",
|
|
44
|
+
"blob-storage",
|
|
36
45
|
"presigned-url",
|
|
37
|
-
"
|
|
46
|
+
"presigned-upload",
|
|
47
|
+
"direct-upload",
|
|
48
|
+
"secure-upload",
|
|
49
|
+
"file-validation",
|
|
38
50
|
"multer",
|
|
39
|
-
"
|
|
51
|
+
"multer-storage",
|
|
52
|
+
"typescript",
|
|
53
|
+
"nodejs",
|
|
54
|
+
"expressjs",
|
|
55
|
+
"file-manager",
|
|
56
|
+
"upload-manager",
|
|
57
|
+
"storage-driver",
|
|
58
|
+
"multi-cloud",
|
|
59
|
+
"hybrid-cloud",
|
|
60
|
+
"file-security",
|
|
61
|
+
"upload-security"
|
|
40
62
|
],
|
|
41
63
|
"scripts": {
|
|
42
64
|
"build": "tsc",
|
|
43
65
|
"dev": "tsc --watch",
|
|
44
66
|
"clean": "rm -rf dist",
|
|
45
67
|
"prepublishOnly": "npm run clean && npm run build",
|
|
46
|
-
"test": "jest",
|
|
47
|
-
"test:watch": "jest --watch",
|
|
48
|
-
"test:coverage": "jest --coverage",
|
|
49
68
|
"lint": "eslint src --ext .ts",
|
|
50
69
|
"lint:fix": "eslint src --ext .ts --fix",
|
|
51
70
|
"format": "prettier --write src/**/*.ts",
|
|
52
|
-
"type-check": "tsc --noEmit"
|
|
71
|
+
"type-check": "tsc --noEmit",
|
|
72
|
+
"test": "vitest run",
|
|
73
|
+
"test:watch": "vitest",
|
|
74
|
+
"test:coverage": "vitest run --coverage"
|
|
53
75
|
},
|
|
54
76
|
"peerDependencies": {
|
|
55
77
|
"express": "^4.21.2"
|
|
@@ -57,26 +79,28 @@
|
|
|
57
79
|
"engines": {
|
|
58
80
|
"node": ">=16.0.0"
|
|
59
81
|
},
|
|
60
|
-
"devDependencies": {
|
|
61
|
-
"@types/express": "^5.0.3",
|
|
62
|
-
"@types/jest": "^30.0.0",
|
|
63
|
-
"@types/multer": "^2.0.0",
|
|
64
|
-
"@types/node": "^24.1.0",
|
|
65
|
-
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
66
|
-
"@typescript-eslint/parser": "^8.38.0",
|
|
67
|
-
"eslint": "^9.32.0",
|
|
68
|
-
"jest": "^30.0.5",
|
|
69
|
-
"prettier": "^3.6.2",
|
|
70
|
-
"ts-jest": "^29.4.0",
|
|
71
|
-
"typescript": "^5.8.3"
|
|
72
|
-
},
|
|
73
82
|
"dependencies": {
|
|
74
|
-
"@aws-sdk/client-s3": "^3.
|
|
75
|
-
"@aws-sdk/
|
|
76
|
-
"@
|
|
77
|
-
"
|
|
83
|
+
"@aws-sdk/client-s3": "^3.975.0",
|
|
84
|
+
"@aws-sdk/lib-storage": "^3.975.0",
|
|
85
|
+
"@aws-sdk/s3-request-presigner": "^3.975.0",
|
|
86
|
+
"@azure/identity": "^4.13.0",
|
|
87
|
+
"@azure/storage-blob": "^12.30.0",
|
|
88
|
+
"@google-cloud/storage": "^7.18.0",
|
|
89
|
+
"dotenv": "^17.2.3",
|
|
78
90
|
"multer": "^2.0.2",
|
|
79
|
-
"oci-sdk": "^2.114.0",
|
|
80
91
|
"tslib": "^2.8.1"
|
|
92
|
+
},
|
|
93
|
+
"devDependencies": {
|
|
94
|
+
"@types/express": "^5.0.6",
|
|
95
|
+
"@types/multer": "^2.0.0",
|
|
96
|
+
"@types/node": "^25.0.10",
|
|
97
|
+
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
|
98
|
+
"@typescript-eslint/parser": "^8.53.1",
|
|
99
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
100
|
+
"eslint": "^9.39.2",
|
|
101
|
+
"memfs": "^4.56.10",
|
|
102
|
+
"prettier": "^3.8.1",
|
|
103
|
+
"typescript": "^5.9.3",
|
|
104
|
+
"vitest": "^4.0.18"
|
|
81
105
|
}
|
|
82
106
|
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { BaseStorageDriver } from './base.driver.js';
|
|
2
|
-
import { FileUploadResult, PresignedUrlResult } from '../types/storage.types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Oracle Cloud Infrastructure storage driver (placeholder implementation)
|
|
5
|
-
*/
|
|
6
|
-
export declare class OCIStorageDriver extends BaseStorageDriver {
|
|
7
|
-
private bucketName;
|
|
8
|
-
private region;
|
|
9
|
-
constructor(config: any);
|
|
10
|
-
/**
|
|
11
|
-
* Upload file to OCI (placeholder)
|
|
12
|
-
*/
|
|
13
|
-
upload(file: Express.Multer.File): Promise<FileUploadResult>;
|
|
14
|
-
/**
|
|
15
|
-
* Generate presigned upload URL (placeholder)
|
|
16
|
-
*/
|
|
17
|
-
generateUploadUrl(_fileName: string): Promise<PresignedUrlResult>;
|
|
18
|
-
/**
|
|
19
|
-
* Generate presigned view URL (placeholder)
|
|
20
|
-
*/
|
|
21
|
-
generateViewUrl(_fileName: string): Promise<PresignedUrlResult>;
|
|
22
|
-
/**
|
|
23
|
-
* Delete file from OCI (placeholder)
|
|
24
|
-
*/
|
|
25
|
-
delete(_fileName: string): Promise<boolean>;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Oracle Cloud Infrastructure presigned driver
|
|
29
|
-
*/
|
|
30
|
-
export declare class OCIPresignedStorageDriver extends OCIStorageDriver {
|
|
31
|
-
constructor(config: any);
|
|
32
|
-
/**
|
|
33
|
-
* Override upload to return presigned URL instead of direct upload
|
|
34
|
-
*/
|
|
35
|
-
upload(file: Express.Multer.File): Promise<FileUploadResult>;
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=oci.driver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oci.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/oci.driver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEjF;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,iBAAiB;IACrD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,GAAG;IAOvB;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsBlE;;OAEG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAIvE;;OAEG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAIrE;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAIlD;AAED;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,gBAAgB;gBACjD,MAAM,EAAE,GAAG;IAIvB;;OAEG;IACY,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAyB5E"}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
// OCI SDK imports - simplified for now
|
|
2
|
-
// Note: OCI SDK structure may vary, this is a placeholder implementation
|
|
3
|
-
import { BaseStorageDriver } from './base.driver.js';
|
|
4
|
-
/**
|
|
5
|
-
* Oracle Cloud Infrastructure storage driver (placeholder implementation)
|
|
6
|
-
*/
|
|
7
|
-
export class OCIStorageDriver extends BaseStorageDriver {
|
|
8
|
-
constructor(config) {
|
|
9
|
-
super(config);
|
|
10
|
-
this.bucketName = config.bucketName;
|
|
11
|
-
this.region = config.ociRegion;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Upload file to OCI (placeholder)
|
|
15
|
-
*/
|
|
16
|
-
async upload(file) {
|
|
17
|
-
try {
|
|
18
|
-
// Validate file
|
|
19
|
-
const validationErrors = this.validateFile(file);
|
|
20
|
-
if (validationErrors.length > 0) {
|
|
21
|
-
return this.createErrorResult(validationErrors.join(', '));
|
|
22
|
-
}
|
|
23
|
-
// Generate unique filename
|
|
24
|
-
const fileName = this.generateFileName(file.originalname);
|
|
25
|
-
// Placeholder implementation
|
|
26
|
-
const fileUrl = `https://objectstorage.${this.region}.oraclecloud.com/b/${this.bucketName}/o/${fileName}`;
|
|
27
|
-
return this.createSuccessResult(fileName, fileUrl);
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
return this.createErrorResult(error instanceof Error ? error.message : 'Failed to upload file to OCI');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Generate presigned upload URL (placeholder)
|
|
35
|
-
*/
|
|
36
|
-
async generateUploadUrl(_fileName) {
|
|
37
|
-
return this.createPresignedErrorResult('OCI presigned URLs not implemented yet');
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Generate presigned view URL (placeholder)
|
|
41
|
-
*/
|
|
42
|
-
async generateViewUrl(_fileName) {
|
|
43
|
-
return this.createPresignedErrorResult('OCI presigned URLs not implemented yet');
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Delete file from OCI (placeholder)
|
|
47
|
-
*/
|
|
48
|
-
async delete(_fileName) {
|
|
49
|
-
// Placeholder implementation
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Oracle Cloud Infrastructure presigned driver
|
|
55
|
-
*/
|
|
56
|
-
export class OCIPresignedStorageDriver extends OCIStorageDriver {
|
|
57
|
-
constructor(config) {
|
|
58
|
-
super(config);
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Override upload to return presigned URL instead of direct upload
|
|
62
|
-
*/
|
|
63
|
-
async upload(file) {
|
|
64
|
-
try {
|
|
65
|
-
// Validate file
|
|
66
|
-
const validationErrors = this.validateFile(file);
|
|
67
|
-
if (validationErrors.length > 0) {
|
|
68
|
-
return this.createErrorResult(validationErrors.join(', '));
|
|
69
|
-
}
|
|
70
|
-
// Generate unique filename
|
|
71
|
-
const fileName = this.generateFileName(file.originalname);
|
|
72
|
-
// Generate presigned upload URL
|
|
73
|
-
const presignedResult = await this.generateUploadUrl(fileName);
|
|
74
|
-
if (!presignedResult.success) {
|
|
75
|
-
return this.createErrorResult(presignedResult.error || 'Failed to generate presigned URL');
|
|
76
|
-
}
|
|
77
|
-
return this.createSuccessResult(fileName, presignedResult.uploadUrl);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
return this.createErrorResult(error instanceof Error ? error.message : 'Failed to generate presigned URL');
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
//# sourceMappingURL=oci.driver.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oci.driver.js","sourceRoot":"","sources":["../../src/drivers/oci.driver.ts"],"names":[],"mappings":"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"}
|