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,39 +1,86 @@
|
|
|
1
1
|
import { BaseStorageDriver } from './base.driver.js';
|
|
2
|
-
import { FileUploadResult, PresignedUrlResult } from '../types/storage.types.js';
|
|
2
|
+
import { FileUploadResult, PresignedUrlResult, StorageConfig, BlobValidationOptions, BlobValidationResult, ListFilesResult, UploadOptions } from '../types/storage.types.js';
|
|
3
3
|
/**
|
|
4
|
-
* Google Cloud Storage
|
|
4
|
+
* GCSStorageDriver - Handles file operations with Google Cloud Storage.
|
|
5
|
+
*
|
|
6
|
+
* Supports two authentication methods:
|
|
7
|
+
* 1. Service account JSON file (GCS_CREDENTIALS path)
|
|
8
|
+
* 2. Application Default Credentials (when running on GCP)
|
|
9
|
+
*
|
|
10
|
+
* If you're running on GCP (Cloud Run, GKE, etc.), you usually don't need
|
|
11
|
+
* to provide credentials — the SDK picks them up automatically.
|
|
5
12
|
*/
|
|
6
13
|
export declare class GCSStorageDriver extends BaseStorageDriver {
|
|
7
14
|
private storage;
|
|
8
15
|
private bucket;
|
|
9
16
|
private bucketName;
|
|
10
17
|
private projectId;
|
|
11
|
-
constructor(config:
|
|
18
|
+
constructor(config: StorageConfig);
|
|
12
19
|
/**
|
|
13
|
-
*
|
|
20
|
+
* Uploads a file directly to GCS.
|
|
21
|
+
* Handles both memory and disk storage from Multer.
|
|
22
|
+
*
|
|
23
|
+
* For large files (>100MB), uses streaming upload to reduce
|
|
24
|
+
* memory usage and improve reliability.
|
|
14
25
|
*/
|
|
15
|
-
upload(file: Express.Multer.File): Promise<FileUploadResult>;
|
|
26
|
+
upload(file: Express.Multer.File, options?: UploadOptions): Promise<FileUploadResult>;
|
|
16
27
|
/**
|
|
17
|
-
*
|
|
28
|
+
* Uploads a large file using streaming.
|
|
29
|
+
*
|
|
30
|
+
* This method pipes the file stream directly to GCS, which is more
|
|
31
|
+
* memory-efficient for large files.
|
|
18
32
|
*/
|
|
19
|
-
|
|
33
|
+
private uploadWithStream;
|
|
20
34
|
/**
|
|
21
|
-
*
|
|
35
|
+
* Creates a presigned URL for uploading directly to GCS.
|
|
36
|
+
*
|
|
37
|
+
* The URL enforces:
|
|
38
|
+
* - Exact content type
|
|
39
|
+
* - Exact file size (via x-goog-content-length-range header)
|
|
40
|
+
*
|
|
41
|
+
* Clients that try to upload different content will get a 403.
|
|
42
|
+
*/
|
|
43
|
+
generateUploadUrl(fileName: string, contentType?: string, fileSize?: number): Promise<PresignedUrlResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a presigned URL for downloading/viewing a file.
|
|
22
46
|
*/
|
|
23
47
|
generateViewUrl(fileName: string): Promise<PresignedUrlResult>;
|
|
24
48
|
/**
|
|
25
|
-
*
|
|
49
|
+
* Deletes a file from GCS.
|
|
50
|
+
* Returns false if the file doesn't exist, throws on real errors.
|
|
26
51
|
*/
|
|
27
52
|
delete(fileName: string): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Confirms an upload and optionally validates the file.
|
|
55
|
+
*
|
|
56
|
+
* For GCS, validation is optional since constraints are enforced at URL level.
|
|
57
|
+
* But you can still use this to verify the file matches expectations.
|
|
58
|
+
*/
|
|
59
|
+
validateAndConfirmUpload(reference: string, options?: BlobValidationOptions): Promise<BlobValidationResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Lists files in the bucket with optional prefix filtering and pagination.
|
|
62
|
+
*/
|
|
63
|
+
listFiles(prefix?: string, maxResults?: number, continuationToken?: string): Promise<ListFilesResult>;
|
|
28
64
|
}
|
|
29
65
|
/**
|
|
30
|
-
*
|
|
66
|
+
* GCSPresignedStorageDriver - GCS driver that returns presigned URLs from upload().
|
|
67
|
+
*
|
|
68
|
+
* Use this when you want clients to upload directly to GCS without
|
|
69
|
+
* the file passing through your server.
|
|
31
70
|
*/
|
|
32
71
|
export declare class GCSPresignedStorageDriver extends GCSStorageDriver {
|
|
33
|
-
constructor(config:
|
|
72
|
+
constructor(config: StorageConfig);
|
|
34
73
|
/**
|
|
35
|
-
*
|
|
74
|
+
* Instead of uploading the file, returns a presigned URL for the client to use.
|
|
75
|
+
*
|
|
76
|
+
* The returned fileUrl is the presigned upload URL.
|
|
77
|
+
* After the client uploads, use validateAndConfirmUpload() to get a view URL.
|
|
78
|
+
*
|
|
79
|
+
* Note: The `options` parameter (metadata, cacheControl, etc.) is NOT applied
|
|
80
|
+
* when using presigned uploads. These options must be set by the client when
|
|
81
|
+
* making the actual upload request to GCS, or configured via bucket settings.
|
|
82
|
+
* For server-side uploads with full options support, use the regular 'gcs' driver.
|
|
36
83
|
*/
|
|
37
|
-
upload(file: Express.Multer.File): Promise<FileUploadResult>;
|
|
84
|
+
upload(file: Express.Multer.File, _options?: UploadOptions): Promise<FileUploadResult>;
|
|
38
85
|
}
|
|
39
86
|
//# sourceMappingURL=gcs.driver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gcs.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/gcs.driver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"gcs.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/gcs.driver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAY,MAAM,2BAA2B,CAAC;AAGvL;;;;;;;;;GASG;AACH,qBAAa,gBAAiB,SAAQ,iBAAiB;IACrD,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,aAAa;IAkBjC;;;;;;OAMG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkD3F;;;;;OAKG;YACW,gBAAgB;IAmB9B;;;;;;;;OAQG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAyC/G;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAwBpE;;;OAGG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBhD;;;;;OAKG;IACY,wBAAwB,CACrC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAsEhC;;OAEG;IACG,SAAS,CACb,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,GAAE,MAAa,EACzB,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,eAAe,CAAC;CAkD5B;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,gBAAgB;gBACjD,MAAM,EAAE,aAAa;IAIjC;;;;;;;;;;OAUG;IACY,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;CA2BtG"}
|
|
@@ -1,60 +1,124 @@
|
|
|
1
1
|
import { Storage } from '@google-cloud/storage';
|
|
2
2
|
import { BaseStorageDriver } from './base.driver.js';
|
|
3
3
|
/**
|
|
4
|
-
* Google Cloud Storage
|
|
4
|
+
* GCSStorageDriver - Handles file operations with Google Cloud Storage.
|
|
5
|
+
*
|
|
6
|
+
* Supports two authentication methods:
|
|
7
|
+
* 1. Service account JSON file (GCS_CREDENTIALS path)
|
|
8
|
+
* 2. Application Default Credentials (when running on GCP)
|
|
9
|
+
*
|
|
10
|
+
* If you're running on GCP (Cloud Run, GKE, etc.), you usually don't need
|
|
11
|
+
* to provide credentials — the SDK picks them up automatically.
|
|
5
12
|
*/
|
|
6
13
|
export class GCSStorageDriver extends BaseStorageDriver {
|
|
7
14
|
constructor(config) {
|
|
8
15
|
super(config);
|
|
9
16
|
this.bucketName = config.bucketName;
|
|
10
17
|
this.projectId = config.gcsProjectId;
|
|
11
|
-
|
|
12
|
-
this.storage = new Storage({
|
|
18
|
+
const storageOptions = {
|
|
13
19
|
projectId: this.projectId,
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
};
|
|
21
|
+
if (config.gcsCredentials) {
|
|
22
|
+
storageOptions.keyFilename = config.gcsCredentials;
|
|
23
|
+
}
|
|
24
|
+
this.storage = new Storage(storageOptions);
|
|
16
25
|
this.bucket = this.storage.bucket(this.bucketName);
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
19
|
-
*
|
|
28
|
+
* Uploads a file directly to GCS.
|
|
29
|
+
* Handles both memory and disk storage from Multer.
|
|
30
|
+
*
|
|
31
|
+
* For large files (>100MB), uses streaming upload to reduce
|
|
32
|
+
* memory usage and improve reliability.
|
|
20
33
|
*/
|
|
21
|
-
async upload(file) {
|
|
34
|
+
async upload(file, options) {
|
|
22
35
|
try {
|
|
23
|
-
// Validate file
|
|
24
36
|
const validationErrors = this.validateFile(file);
|
|
25
37
|
if (validationErrors.length > 0) {
|
|
26
38
|
return this.createErrorResult(validationErrors.join(', '));
|
|
27
39
|
}
|
|
28
|
-
// Generate unique filename
|
|
29
40
|
const fileName = this.generateFileName(file.originalname);
|
|
30
|
-
|
|
31
|
-
const gcsFile = this.bucket.file(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
const filePath = this.buildFilePath(fileName);
|
|
42
|
+
const gcsFile = this.bucket.file(filePath);
|
|
43
|
+
const metadata = {
|
|
44
|
+
contentType: options?.contentType || file.mimetype,
|
|
45
|
+
};
|
|
46
|
+
if (options?.cacheControl) {
|
|
47
|
+
metadata.cacheControl = options.cacheControl;
|
|
48
|
+
}
|
|
49
|
+
if (options?.contentDisposition) {
|
|
50
|
+
metadata.contentDisposition = options.contentDisposition;
|
|
51
|
+
}
|
|
52
|
+
if (options?.metadata) {
|
|
53
|
+
metadata.metadata = options.metadata;
|
|
54
|
+
}
|
|
55
|
+
// Use streaming upload for large files to reduce memory usage
|
|
56
|
+
if (this.shouldUseStreaming(file)) {
|
|
57
|
+
await this.uploadWithStream(gcsFile, file, metadata);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const fileContent = this.getFileContent(file);
|
|
61
|
+
await gcsFile.save(fileContent, { metadata });
|
|
62
|
+
}
|
|
63
|
+
// Build the public URL with proper encoding
|
|
64
|
+
const encodedPath = filePath.split('/').map(segment => encodeURIComponent(segment)).join('/');
|
|
65
|
+
const fileUrl = `https://storage.googleapis.com/${this.bucketName}/${encodedPath}`;
|
|
66
|
+
return this.createSuccessResult(filePath, fileUrl);
|
|
41
67
|
}
|
|
42
68
|
catch (error) {
|
|
43
69
|
return this.createErrorResult(error instanceof Error ? error.message : 'Failed to upload file to GCS');
|
|
44
70
|
}
|
|
45
71
|
}
|
|
46
72
|
/**
|
|
47
|
-
*
|
|
73
|
+
* Uploads a large file using streaming.
|
|
74
|
+
*
|
|
75
|
+
* This method pipes the file stream directly to GCS, which is more
|
|
76
|
+
* memory-efficient for large files.
|
|
48
77
|
*/
|
|
49
|
-
async
|
|
78
|
+
async uploadWithStream(gcsFile, file, metadata) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const fileStream = this.getFileStream(file);
|
|
81
|
+
const writeStream = gcsFile.createWriteStream({
|
|
82
|
+
metadata,
|
|
83
|
+
resumable: true, // Enable resumable uploads for reliability
|
|
84
|
+
});
|
|
85
|
+
fileStream
|
|
86
|
+
.pipe(writeStream)
|
|
87
|
+
.on('error', reject)
|
|
88
|
+
.on('finish', resolve);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Creates a presigned URL for uploading directly to GCS.
|
|
93
|
+
*
|
|
94
|
+
* The URL enforces:
|
|
95
|
+
* - Exact content type
|
|
96
|
+
* - Exact file size (via x-goog-content-length-range header)
|
|
97
|
+
*
|
|
98
|
+
* Clients that try to upload different content will get a 403.
|
|
99
|
+
*/
|
|
100
|
+
async generateUploadUrl(fileName, contentType, fileSize) {
|
|
101
|
+
// Security: Defense-in-depth validation (StorageManager also validates)
|
|
102
|
+
if (fileName.includes('..') || fileName.includes('\0')) {
|
|
103
|
+
return this.createPresignedErrorResult('Invalid fileName: path traversal sequences are not allowed');
|
|
104
|
+
}
|
|
50
105
|
try {
|
|
51
106
|
const gcsFile = this.bucket.file(fileName);
|
|
52
|
-
const
|
|
107
|
+
const resolvedContentType = contentType || 'application/octet-stream';
|
|
108
|
+
const expiresOn = new Date(Date.now() + (this.getPresignedUrlExpiry() * 1000));
|
|
109
|
+
const options = {
|
|
53
110
|
version: 'v4',
|
|
54
111
|
action: 'write',
|
|
55
|
-
expires:
|
|
56
|
-
contentType:
|
|
57
|
-
}
|
|
112
|
+
expires: expiresOn,
|
|
113
|
+
contentType: resolvedContentType,
|
|
114
|
+
};
|
|
115
|
+
// GCS enforces exact file size via this header
|
|
116
|
+
if (fileSize !== undefined) {
|
|
117
|
+
options.extensionHeaders = {
|
|
118
|
+
'x-goog-content-length-range': `${fileSize},${fileSize}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const [uploadUrl] = await gcsFile.getSignedUrl(options);
|
|
58
122
|
return this.createPresignedSuccessResult(uploadUrl);
|
|
59
123
|
}
|
|
60
124
|
catch (error) {
|
|
@@ -62,15 +126,20 @@ export class GCSStorageDriver extends BaseStorageDriver {
|
|
|
62
126
|
}
|
|
63
127
|
}
|
|
64
128
|
/**
|
|
65
|
-
*
|
|
129
|
+
* Creates a presigned URL for downloading/viewing a file.
|
|
66
130
|
*/
|
|
67
131
|
async generateViewUrl(fileName) {
|
|
132
|
+
// Security: Defense-in-depth validation
|
|
133
|
+
if (fileName.includes('..') || fileName.includes('\0')) {
|
|
134
|
+
return this.createPresignedErrorResult('Invalid fileName: path traversal sequences are not allowed');
|
|
135
|
+
}
|
|
68
136
|
try {
|
|
69
137
|
const gcsFile = this.bucket.file(fileName);
|
|
138
|
+
const expiresOn = new Date(Date.now() + (this.getPresignedUrlExpiry() * 1000));
|
|
70
139
|
const [viewUrl] = await gcsFile.getSignedUrl({
|
|
71
140
|
version: 'v4',
|
|
72
141
|
action: 'read',
|
|
73
|
-
expires:
|
|
142
|
+
expires: expiresOn,
|
|
74
143
|
});
|
|
75
144
|
return this.createPresignedSuccessResult(undefined, viewUrl);
|
|
76
145
|
}
|
|
@@ -79,44 +148,176 @@ export class GCSStorageDriver extends BaseStorageDriver {
|
|
|
79
148
|
}
|
|
80
149
|
}
|
|
81
150
|
/**
|
|
82
|
-
*
|
|
151
|
+
* Deletes a file from GCS.
|
|
152
|
+
* Returns false if the file doesn't exist, throws on real errors.
|
|
83
153
|
*/
|
|
84
154
|
async delete(fileName) {
|
|
155
|
+
// Security: Defense-in-depth validation
|
|
156
|
+
if (fileName.includes('..') || fileName.includes('\0')) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
const gcsFile = this.bucket.file(fileName);
|
|
160
|
+
const [exists] = await gcsFile.exists();
|
|
161
|
+
if (!exists) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
await gcsFile.delete();
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Confirms an upload and optionally validates the file.
|
|
169
|
+
*
|
|
170
|
+
* For GCS, validation is optional since constraints are enforced at URL level.
|
|
171
|
+
* But you can still use this to verify the file matches expectations.
|
|
172
|
+
*/
|
|
173
|
+
async validateAndConfirmUpload(reference, options) {
|
|
174
|
+
const deleteOnFailure = options?.deleteOnFailure !== false;
|
|
85
175
|
try {
|
|
86
|
-
const gcsFile = this.bucket.file(
|
|
87
|
-
await gcsFile.
|
|
88
|
-
|
|
176
|
+
const gcsFile = this.bucket.file(reference);
|
|
177
|
+
const [metadata] = await gcsFile.getMetadata();
|
|
178
|
+
const actualContentType = metadata.contentType;
|
|
179
|
+
let actualFileSize;
|
|
180
|
+
if (metadata.size !== undefined) {
|
|
181
|
+
const parsed = typeof metadata.size === 'string' ? parseInt(metadata.size, 10) : metadata.size;
|
|
182
|
+
actualFileSize = Number.isNaN(parsed) ? undefined : parsed;
|
|
183
|
+
}
|
|
184
|
+
// Validate content type if expected
|
|
185
|
+
if (options?.expectedContentType && actualContentType !== options.expectedContentType) {
|
|
186
|
+
if (deleteOnFailure) {
|
|
187
|
+
await this.delete(reference);
|
|
188
|
+
}
|
|
189
|
+
const errorResult = {
|
|
190
|
+
success: false,
|
|
191
|
+
error: `Content type mismatch: expected '${options.expectedContentType}', got '${actualContentType}'${deleteOnFailure ? ' (file deleted)' : ' (file kept for inspection)'}`,
|
|
192
|
+
};
|
|
193
|
+
if (actualContentType)
|
|
194
|
+
errorResult.actualContentType = actualContentType;
|
|
195
|
+
if (actualFileSize !== undefined)
|
|
196
|
+
errorResult.actualFileSize = actualFileSize;
|
|
197
|
+
return errorResult;
|
|
198
|
+
}
|
|
199
|
+
// Validate file size if expected
|
|
200
|
+
if (options?.expectedFileSize !== undefined && actualFileSize !== options.expectedFileSize) {
|
|
201
|
+
if (deleteOnFailure) {
|
|
202
|
+
await this.delete(reference);
|
|
203
|
+
}
|
|
204
|
+
const errorResult = {
|
|
205
|
+
success: false,
|
|
206
|
+
error: `File size mismatch: expected ${options.expectedFileSize} bytes, got ${actualFileSize} bytes${deleteOnFailure ? ' (file deleted)' : ' (file kept for inspection)'}`,
|
|
207
|
+
};
|
|
208
|
+
if (actualContentType)
|
|
209
|
+
errorResult.actualContentType = actualContentType;
|
|
210
|
+
if (actualFileSize !== undefined)
|
|
211
|
+
errorResult.actualFileSize = actualFileSize;
|
|
212
|
+
return errorResult;
|
|
213
|
+
}
|
|
214
|
+
const viewResult = await this.generateViewUrl(reference);
|
|
215
|
+
const result = {
|
|
216
|
+
success: true,
|
|
217
|
+
reference,
|
|
218
|
+
expiresIn: this.getPresignedUrlExpiry(),
|
|
219
|
+
};
|
|
220
|
+
if (viewResult.viewUrl) {
|
|
221
|
+
result.viewUrl = viewResult.viewUrl;
|
|
222
|
+
}
|
|
223
|
+
if (actualContentType) {
|
|
224
|
+
result.actualContentType = actualContentType;
|
|
225
|
+
}
|
|
226
|
+
if (actualFileSize !== undefined) {
|
|
227
|
+
result.actualFileSize = actualFileSize;
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
89
230
|
}
|
|
90
231
|
catch (error) {
|
|
91
|
-
|
|
232
|
+
const errorMessage = error instanceof Error ? error.message : 'File not found or access denied';
|
|
233
|
+
return {
|
|
234
|
+
success: false,
|
|
235
|
+
error: errorMessage,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Lists files in the bucket with optional prefix filtering and pagination.
|
|
241
|
+
*/
|
|
242
|
+
async listFiles(prefix, maxResults = 1000, continuationToken) {
|
|
243
|
+
try {
|
|
244
|
+
const validatedMaxResults = Math.floor(Math.max(1, Math.min(Number.isNaN(maxResults) ? 1000 : maxResults, 1000)));
|
|
245
|
+
const options = {
|
|
246
|
+
maxResults: validatedMaxResults,
|
|
247
|
+
};
|
|
248
|
+
if (prefix)
|
|
249
|
+
options.prefix = prefix;
|
|
250
|
+
if (continuationToken)
|
|
251
|
+
options.pageToken = continuationToken;
|
|
252
|
+
const [files, , apiResponse] = await this.bucket.getFiles(options);
|
|
253
|
+
const fileList = files.map((file) => {
|
|
254
|
+
const fileInfo = { name: file.name };
|
|
255
|
+
if (file.metadata.size) {
|
|
256
|
+
const parsed = parseInt(String(file.metadata.size), 10);
|
|
257
|
+
if (!Number.isNaN(parsed)) {
|
|
258
|
+
fileInfo.size = parsed;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (file.metadata.contentType) {
|
|
262
|
+
fileInfo.contentType = file.metadata.contentType;
|
|
263
|
+
}
|
|
264
|
+
if (file.metadata.updated) {
|
|
265
|
+
fileInfo.lastModified = new Date(file.metadata.updated);
|
|
266
|
+
}
|
|
267
|
+
return fileInfo;
|
|
268
|
+
});
|
|
269
|
+
const result = {
|
|
270
|
+
success: true,
|
|
271
|
+
files: fileList,
|
|
272
|
+
};
|
|
273
|
+
const responseWithToken = apiResponse;
|
|
274
|
+
if (responseWithToken?.nextPageToken) {
|
|
275
|
+
result.nextToken = responseWithToken.nextPageToken;
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
error: error instanceof Error ? error.message : 'Failed to list files',
|
|
283
|
+
};
|
|
92
284
|
}
|
|
93
285
|
}
|
|
94
286
|
}
|
|
95
287
|
/**
|
|
96
|
-
*
|
|
288
|
+
* GCSPresignedStorageDriver - GCS driver that returns presigned URLs from upload().
|
|
289
|
+
*
|
|
290
|
+
* Use this when you want clients to upload directly to GCS without
|
|
291
|
+
* the file passing through your server.
|
|
97
292
|
*/
|
|
98
293
|
export class GCSPresignedStorageDriver extends GCSStorageDriver {
|
|
99
294
|
constructor(config) {
|
|
100
295
|
super(config);
|
|
101
296
|
}
|
|
102
297
|
/**
|
|
103
|
-
*
|
|
298
|
+
* Instead of uploading the file, returns a presigned URL for the client to use.
|
|
299
|
+
*
|
|
300
|
+
* The returned fileUrl is the presigned upload URL.
|
|
301
|
+
* After the client uploads, use validateAndConfirmUpload() to get a view URL.
|
|
302
|
+
*
|
|
303
|
+
* Note: The `options` parameter (metadata, cacheControl, etc.) is NOT applied
|
|
304
|
+
* when using presigned uploads. These options must be set by the client when
|
|
305
|
+
* making the actual upload request to GCS, or configured via bucket settings.
|
|
306
|
+
* For server-side uploads with full options support, use the regular 'gcs' driver.
|
|
104
307
|
*/
|
|
105
|
-
async upload(file) {
|
|
308
|
+
async upload(file, _options) {
|
|
106
309
|
try {
|
|
107
|
-
// Validate file
|
|
108
310
|
const validationErrors = this.validateFile(file);
|
|
109
311
|
if (validationErrors.length > 0) {
|
|
110
312
|
return this.createErrorResult(validationErrors.join(', '));
|
|
111
313
|
}
|
|
112
|
-
// Generate unique filename
|
|
113
314
|
const fileName = this.generateFileName(file.originalname);
|
|
114
|
-
|
|
115
|
-
const presignedResult = await this.generateUploadUrl(
|
|
315
|
+
const filePath = this.buildFilePath(fileName);
|
|
316
|
+
const presignedResult = await this.generateUploadUrl(filePath, file.mimetype, file.size);
|
|
116
317
|
if (!presignedResult.success) {
|
|
117
318
|
return this.createErrorResult(presignedResult.error || 'Failed to generate presigned URL');
|
|
118
319
|
}
|
|
119
|
-
return this.createSuccessResult(
|
|
320
|
+
return this.createSuccessResult(filePath, presignedResult.uploadUrl);
|
|
120
321
|
}
|
|
121
322
|
catch (error) {
|
|
122
323
|
return this.createErrorResult(error instanceof Error ? error.message : 'Failed to generate presigned URL');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gcs.driver.js","sourceRoot":"","sources":["../../src/drivers/gcs.driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"gcs.driver.js","sourceRoot":"","sources":["../../src/drivers/gcs.driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD;;;;;;;;;GASG;AACH,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB;IAMrD,YAAY,MAAqB;QAC/B,KAAK,CAAC,MAAM,CAAC,CAAC;QAEd,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAW,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,YAAa,CAAC;QAEtC,MAAM,cAAc,GAAgD;YAClE,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;QAEF,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;YAC1B,cAAc,CAAC,WAAW,GAAG,MAAM,CAAC,cAAc,CAAC;QACrD,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CAAC,IAAyB,EAAE,OAAuB;QAC7D,IAAI,CAAC;YACH,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,OAAO,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7D,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAE3C,MAAM,QAAQ,GAKV;gBACF,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,IAAI,CAAC,QAAQ;aACnD,CAAC;YAEF,IAAI,OAAO,EAAE,YAAY,EAAE,CAAC;gBAC1B,QAAQ,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;YAC/C,CAAC;YACD,IAAI,OAAO,EAAE,kBAAkB,EAAE,CAAC;gBAChC,QAAQ,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAC3D,CAAC;YACD,IAAI,OAAO,EAAE,QAAQ,EAAE,CAAC;gBACtB,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YACvC,CAAC;YAED,8DAA8D;YAC9D,IAAI,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YACvD,CAAC;iBAAM,CAAC;gBACN,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;gBAC9C,MAAM,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;YAChD,CAAC;YAED,4CAA4C;YAC5C,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC9F,MAAM,OAAO,GAAG,kCAAkC,IAAI,CAAC,UAAU,IAAI,WAAW,EAAE,CAAC;YAEnF,OAAO,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,iBAAiB,CAC3B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CACxE,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,gBAAgB,CAC5B,OAAa,EACb,IAAyB,EACzB,QAAwH;QAExH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC;gBAC5C,QAAQ;gBACR,SAAS,EAAE,IAAI,EAAE,2CAA2C;aAC7D,CAAC,CAAC;YAEH,UAAU;iBACP,IAAI,CAAC,WAAW,CAAC;iBACjB,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;iBACnB,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,iBAAiB,CAAC,QAAgB,EAAE,WAAoB,EAAE,QAAiB;QAC/E,wEAAwE;QACxE,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC,0BAA0B,CAAC,4DAA4D,CAAC,CAAC;QACvG,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC3C,MAAM,mBAAmB,GAAG,WAAW,IAAI,0BAA0B,CAAC;YACtE,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;YAE/E,MAAM,OAAO,GAMT;gBACF,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,SAAS;gBAClB,WAAW,EAAE,mBAAmB;aACjC,CAAC;YAEF,+CAA+C;YAC/C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,OAAO,CAAC,gBAAgB,GAAG;oBACzB,6BAA6B,EAAE,GAAG,QAAQ,IAAI,QAAQ,EAAE;iBACzD,CAAC;YACJ,CAAC;YAED,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YAExD,OAAO,IAAI,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,0BAA0B,CACpC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,+BAA+B,CACzE,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CAAC,QAAgB;QACpC,wCAAwC;QACxC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC,0BAA0B,CAAC,4DAA4D,CAAC,CAAC;QACvG,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;YAE/E,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC;gBAC3C,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,SAAS;aACnB,CAAC,CAAC;YAEH,OAAO,IAAI,CAAC,4BAA4B,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,0BAA0B,CACpC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CACvE,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,QAAgB;QAC3B,wCAAwC;QACxC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE3C,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;QACxC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACM,KAAK,CAAC,wBAAwB,CACrC,SAAiB,EACjB,OAA+B;QAE/B,MAAM,eAAe,GAAG,OAAO,EAAE,eAAe,KAAK,KAAK,CAAC;QAE3D,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5C,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;YAE/C,MAAM,iBAAiB,GAAG,QAAQ,CAAC,WAAW,CAAC;YAC/C,IAAI,cAAkC,CAAC;YACvC,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAChC,MAAM,MAAM,GAAG,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC/F,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;YAC7D,CAAC;YAED,oCAAoC;YACpC,IAAI,OAAO,EAAE,mBAAmB,IAAI,iBAAiB,KAAK,OAAO,CAAC,mBAAmB,EAAE,CAAC;gBACtF,IAAI,eAAe,EAAE,CAAC;oBACpB,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC/B,CAAC;gBACD,MAAM,WAAW,GAAyB;oBACxC,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,oCAAoC,OAAO,CAAC,mBAAmB,WAAW,iBAAiB,IAAI,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,6BAA6B,EAAE;iBAC5K,CAAC;gBACF,IAAI,iBAAiB;oBAAE,WAAW,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;gBACzE,IAAI,cAAc,KAAK,SAAS;oBAAE,WAAW,CAAC,cAAc,GAAG,cAAc,CAAC;gBAC9E,OAAO,WAAW,CAAC;YACrB,CAAC;YAED,iCAAiC;YACjC,IAAI,OAAO,EAAE,gBAAgB,KAAK,SAAS,IAAI,cAAc,KAAK,OAAO,CAAC,gBAAgB,EAAE,CAAC;gBAC3F,IAAI,eAAe,EAAE,CAAC;oBACpB,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC/B,CAAC;gBACD,MAAM,WAAW,GAAyB;oBACxC,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,gCAAgC,OAAO,CAAC,gBAAgB,eAAe,cAAc,SAAS,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,6BAA6B,EAAE;iBAC3K,CAAC;gBACF,IAAI,iBAAiB;oBAAE,WAAW,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;gBACzE,IAAI,cAAc,KAAK,SAAS;oBAAE,WAAW,CAAC,cAAc,GAAG,cAAc,CAAC;gBAC9E,OAAO,WAAW,CAAC;YACrB,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;YAEzD,MAAM,MAAM,GAAyB;gBACnC,OAAO,EAAE,IAAI;gBACb,SAAS;gBACT,SAAS,EAAE,IAAI,CAAC,qBAAqB,EAAE;aACxC,CAAC;YAEF,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,MAAM,CAAC,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;YACtC,CAAC;YACD,IAAI,iBAAiB,EAAE,CAAC;gBACtB,MAAM,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;YAC/C,CAAC;YACD,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;gBACjC,MAAM,CAAC,cAAc,GAAG,cAAc,CAAC;YACzC,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC;YAChG,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,YAAY;aACpB,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CACb,MAAe,EACf,aAAqB,IAAI,EACzB,iBAA0B;QAE1B,IAAI,CAAC;YACH,MAAM,mBAAmB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CACzD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,EAC5C,IAAI,CACL,CAAC,CAAC,CAAC;YAEJ,MAAM,OAAO,GAAgE;gBAC3E,UAAU,EAAE,mBAAmB;aAChC,CAAC;YACF,IAAI,MAAM;gBAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;YACpC,IAAI,iBAAiB;gBAAE,OAAO,CAAC,SAAS,GAAG,iBAAiB,CAAC;YAE7D,MAAM,CAAC,KAAK,EAAE,AAAD,EAAG,WAAW,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAEnE,MAAM,QAAQ,GAAe,KAAK,CAAC,GAAG,CAAC,CAAC,IAAU,EAAE,EAAE;gBACpD,MAAM,QAAQ,GAAa,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC/C,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;oBACvB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;oBACxD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;wBAC1B,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAC;oBACzB,CAAC;gBACH,CAAC;gBACD,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;oBAC9B,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;gBACnD,CAAC;gBACD,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;oBAC1B,QAAQ,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAC1D,CAAC;gBACD,OAAO,QAAQ,CAAC;YAClB,CAAC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAoB;gBAC9B,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,QAAQ;aAChB,CAAC;YAEF,MAAM,iBAAiB,GAAG,WAAqD,CAAC;YAChF,IAAI,iBAAiB,EAAE,aAAa,EAAE,CAAC;gBACrC,MAAM,CAAC,SAAS,GAAG,iBAAiB,CAAC,aAAa,CAAC;YACrD,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB;aACvE,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,yBAA0B,SAAQ,gBAAgB;IAC7D,YAAY,MAAqB;QAC/B,KAAK,CAAC,MAAM,CAAC,CAAC;IAChB,CAAC;IAED;;;;;;;;;;OAUG;IACM,KAAK,CAAC,MAAM,CAAC,IAAyB,EAAE,QAAwB;QACvE,IAAI,CAAC;YACH,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,OAAO,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7D,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAE9C,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAClD,QAAQ,EACR,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,IAAI,CACV,CAAC;YAEF,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,KAAK,IAAI,kCAAkC,CAAC,CAAC;YAC7F,CAAC;YAED,OAAO,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;QACvE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,iBAAiB,CAC3B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,kCAAkC,CAC5E,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,30 +1,107 @@
|
|
|
1
1
|
import { BaseStorageDriver } from './base.driver.js';
|
|
2
|
-
import { FileUploadResult, PresignedUrlResult } from '../types/storage.types.js';
|
|
2
|
+
import { FileUploadResult, PresignedUrlResult, StorageConfig, ListFilesResult, UploadOptions } from '../types/storage.types.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* LocalStorageDriver - Saves files to your local filesystem.
|
|
5
|
+
*
|
|
6
|
+
* Great for development and small-scale applications.
|
|
7
|
+
* Files are organized by year/month folders automatically.
|
|
8
|
+
*
|
|
9
|
+
* **Security features:**
|
|
10
|
+
* - Path traversal prevention (blocks ../ and null bytes)
|
|
11
|
+
* - Symlinks are NOT followed or deleted (prevents directory escape attacks)
|
|
12
|
+
* - Magic byte detection for content-type validation (prevents extension spoofing)
|
|
13
|
+
* - Files stay within the configured base directory
|
|
14
|
+
*
|
|
15
|
+
* **Symlink behavior:** This driver explicitly rejects symlinks for security.
|
|
16
|
+
* If a file is a symlink, it will not be read, deleted, or listed. This prevents
|
|
17
|
+
* attackers from using symlinks to access files outside the storage directory.
|
|
18
|
+
*
|
|
19
|
+
* Note: Local storage doesn't support presigned URLs since
|
|
20
|
+
* there's no external service to sign requests against.
|
|
5
21
|
*/
|
|
6
22
|
export declare class LocalStorageDriver extends BaseStorageDriver {
|
|
7
23
|
private basePath;
|
|
8
|
-
constructor(config:
|
|
24
|
+
constructor(config: StorageConfig);
|
|
9
25
|
/**
|
|
10
|
-
*
|
|
26
|
+
* Saves a file to the local filesystem.
|
|
27
|
+
*
|
|
28
|
+
* Files are automatically organized into YYYY/MM folders.
|
|
29
|
+
* For example, a file uploaded in January 2026 goes into:
|
|
30
|
+
* {basePath}/2026/01/{unique_filename}.jpg
|
|
31
|
+
*
|
|
32
|
+
* For large files (>100MB), uses streaming to reduce memory usage
|
|
33
|
+
* and prevent application crashes.
|
|
11
34
|
*/
|
|
12
|
-
upload(file: Express.Multer.File): Promise<FileUploadResult>;
|
|
35
|
+
upload(file: Express.Multer.File, _options?: UploadOptions): Promise<FileUploadResult>;
|
|
13
36
|
/**
|
|
14
|
-
*
|
|
37
|
+
* Uploads a large file using streaming.
|
|
38
|
+
*
|
|
39
|
+
* This method pipes the file stream directly to disk, which is more
|
|
40
|
+
* memory-efficient for large files (>100MB).
|
|
15
41
|
*/
|
|
16
|
-
|
|
42
|
+
private uploadWithStream;
|
|
17
43
|
/**
|
|
18
|
-
*
|
|
44
|
+
* Builds a URL for accessing the file.
|
|
45
|
+
*
|
|
46
|
+
* If your basePath starts with 'public/', we strip that prefix
|
|
47
|
+
* since Express.static('public') serves files from /
|
|
48
|
+
*
|
|
49
|
+
* Example: public/uploads/2026/01/photo.jpg -> /uploads/2026/01/photo.jpg
|
|
50
|
+
*/
|
|
51
|
+
private generateFileUrl;
|
|
52
|
+
/**
|
|
53
|
+
* Converts Windows backslashes to forward slashes.
|
|
54
|
+
*/
|
|
55
|
+
private normalizePathSeparators;
|
|
56
|
+
/**
|
|
57
|
+
* Removes duplicate slashes from URLs.
|
|
58
|
+
*/
|
|
59
|
+
private normalizeUrl;
|
|
60
|
+
/**
|
|
61
|
+
* Local storage doesn't support presigned upload URLs.
|
|
62
|
+
*/
|
|
63
|
+
generateUploadUrl(_fileName: string, _contentType?: string, _maxSize?: number): Promise<PresignedUrlResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Local storage doesn't support presigned view URLs.
|
|
19
66
|
*/
|
|
20
67
|
generateViewUrl(_fileName: string): Promise<PresignedUrlResult>;
|
|
21
68
|
/**
|
|
22
|
-
*
|
|
69
|
+
* Validates a local file exists and matches expected values.
|
|
70
|
+
*
|
|
71
|
+
* Content type detection uses a two-tier approach:
|
|
72
|
+
* 1. Magic byte detection (examines actual file content for security)
|
|
73
|
+
* 2. Extension-based fallback (when magic bytes don't match)
|
|
74
|
+
*
|
|
75
|
+
* This helps detect extension spoofing attacks where a malicious file
|
|
76
|
+
* is renamed with an innocent extension (e.g., malware.exe -> photo.jpg).
|
|
77
|
+
*/
|
|
78
|
+
validateAndConfirmUpload(reference: string, options?: import('../types/storage.types.js').BlobValidationOptions): Promise<import('../types/storage.types.js').BlobValidationResult>;
|
|
79
|
+
/**
|
|
80
|
+
* Deletes a file from local storage.
|
|
81
|
+
*
|
|
82
|
+
* Security checks:
|
|
83
|
+
* - Rejects path traversal attempts (../ sequences)
|
|
84
|
+
* - Rejects null bytes in paths
|
|
85
|
+
* - Verifies target stays within base directory
|
|
86
|
+
* - Won't follow or delete symlinks (security: prevents directory escape attacks)
|
|
87
|
+
* - Only deletes regular files (not directories)
|
|
88
|
+
*
|
|
89
|
+
* @param reference - The file path relative to the storage base directory
|
|
90
|
+
* @returns true if file was deleted, false if not found or security check failed
|
|
91
|
+
*/
|
|
92
|
+
delete(reference: string): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Safely resolves a reference to a file path within our base directory.
|
|
95
|
+
* Returns null for any suspicious input.
|
|
23
96
|
*/
|
|
24
|
-
|
|
97
|
+
private resolveFilePath;
|
|
25
98
|
/**
|
|
26
|
-
*
|
|
99
|
+
* Lists files in local storage with optional prefix filtering and pagination.
|
|
100
|
+
*
|
|
101
|
+
* Uses early termination to avoid loading all files into memory when possible.
|
|
102
|
+
* Files are collected in sorted order and iteration stops once we have enough
|
|
103
|
+
* results for the requested page.
|
|
27
104
|
*/
|
|
28
|
-
|
|
105
|
+
listFiles(prefix?: string, maxResults?: number, continuationToken?: string): Promise<ListFilesResult>;
|
|
29
106
|
}
|
|
30
107
|
//# sourceMappingURL=local.driver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/local.driver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"local.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/local.driver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAY,MAAM,2BAA2B,CAAC;AA0I1I;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,kBAAmB,SAAQ,iBAAiB;IACvD,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,aAAa;IAKjC;;;;;;;;;OASG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwC5F;;;;;OAKG;YACW,gBAAgB;IAmC9B;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAiBvB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAI/B;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAMjH;;OAEG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAMrE;;;;;;;;;OASG;IACY,wBAAwB,CACrC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,OAAO,2BAA2B,EAAE,qBAAqB,GAClE,OAAO,CAAC,OAAO,2BAA2B,EAAE,oBAAoB,CAAC;IAgFpE;;;;;;;;;;;;OAYG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAyCjD;;;OAGG;IACH,OAAO,CAAC,eAAe;IA+BvB;;;;;;OAMG;IACG,SAAS,CACb,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,GAAE,MAAa,EACzB,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,eAAe,CAAC;CA2K5B"}
|