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.
Files changed (48) hide show
  1. package/README.md +519 -348
  2. package/dist/drivers/azure.driver.d.ts +88 -0
  3. package/dist/drivers/azure.driver.d.ts.map +1 -0
  4. package/dist/drivers/azure.driver.js +367 -0
  5. package/dist/drivers/azure.driver.js.map +1 -0
  6. package/dist/drivers/base.driver.d.ts +125 -24
  7. package/dist/drivers/base.driver.d.ts.map +1 -1
  8. package/dist/drivers/base.driver.js +248 -62
  9. package/dist/drivers/base.driver.js.map +1 -1
  10. package/dist/drivers/gcs.driver.d.ts +60 -13
  11. package/dist/drivers/gcs.driver.d.ts.map +1 -1
  12. package/dist/drivers/gcs.driver.js +242 -41
  13. package/dist/drivers/gcs.driver.js.map +1 -1
  14. package/dist/drivers/local.driver.d.ts +89 -12
  15. package/dist/drivers/local.driver.d.ts.map +1 -1
  16. package/dist/drivers/local.driver.js +533 -45
  17. package/dist/drivers/local.driver.js.map +1 -1
  18. package/dist/drivers/s3.driver.d.ts +64 -13
  19. package/dist/drivers/s3.driver.d.ts.map +1 -1
  20. package/dist/drivers/s3.driver.js +269 -41
  21. package/dist/drivers/s3.driver.js.map +1 -1
  22. package/dist/factory/driver.factory.d.ts +35 -29
  23. package/dist/factory/driver.factory.d.ts.map +1 -1
  24. package/dist/factory/driver.factory.js +119 -59
  25. package/dist/factory/driver.factory.js.map +1 -1
  26. package/dist/index.d.ts +23 -22
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +26 -46
  29. package/dist/index.js.map +1 -1
  30. package/dist/storage-manager.d.ts +205 -52
  31. package/dist/storage-manager.d.ts.map +1 -1
  32. package/dist/storage-manager.js +644 -73
  33. package/dist/storage-manager.js.map +1 -1
  34. package/dist/types/storage.types.d.ts +243 -18
  35. package/dist/types/storage.types.d.ts.map +1 -1
  36. package/dist/utils/config.utils.d.ts +28 -4
  37. package/dist/utils/config.utils.d.ts.map +1 -1
  38. package/dist/utils/config.utils.js +121 -47
  39. package/dist/utils/config.utils.js.map +1 -1
  40. package/dist/utils/file.utils.d.ts +111 -14
  41. package/dist/utils/file.utils.d.ts.map +1 -1
  42. package/dist/utils/file.utils.js +215 -32
  43. package/dist/utils/file.utils.js.map +1 -1
  44. package/package.json +51 -27
  45. package/dist/drivers/oci.driver.d.ts +0 -37
  46. package/dist/drivers/oci.driver.d.ts.map +0 -1
  47. package/dist/drivers/oci.driver.js +0 -84
  48. 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 driver
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: any);
18
+ constructor(config: StorageConfig);
12
19
  /**
13
- * Upload file to GCS
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
- * Generate presigned upload URL
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
- generateUploadUrl(fileName: string): Promise<PresignedUrlResult>;
33
+ private uploadWithStream;
20
34
  /**
21
- * Generate presigned view URL
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
- * Delete file from GCS
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
- * Google Cloud Storage presigned driver
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: any);
72
+ constructor(config: StorageConfig);
34
73
  /**
35
- * Override upload to return presigned URL instead of direct upload
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;AAEjF;;GAEG;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,GAAG;IAevB;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgClE;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmBtE;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAkBpE;;OAEG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CASjD;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
+ {"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 driver
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
- // Initialize GCS client
12
- this.storage = new Storage({
18
+ const storageOptions = {
13
19
  projectId: this.projectId,
14
- keyFilename: config.gcsCredentials,
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
- * Upload file to GCS
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
- // Create file reference
31
- const gcsFile = this.bucket.file(fileName);
32
- // Upload file
33
- await gcsFile.save(file.buffer, {
34
- metadata: {
35
- contentType: file.mimetype,
36
- },
37
- });
38
- // Generate file URL
39
- const fileUrl = `https://storage.googleapis.com/${this.bucketName}/${fileName}`;
40
- return this.createSuccessResult(fileName, fileUrl);
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
- * Generate presigned upload URL
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 generateUploadUrl(fileName) {
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 [uploadUrl] = await gcsFile.getSignedUrl({
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: Date.now() + (this.getPresignedUrlExpiry() * 1000),
56
- contentType: 'application/octet-stream',
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
- * Generate presigned view URL
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: Date.now() + (this.getPresignedUrlExpiry() * 1000),
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
- * Delete file from GCS
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(fileName);
87
- await gcsFile.delete();
88
- return true;
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
- return false;
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
- * Google Cloud Storage presigned driver
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
- * Override upload to return presigned URL instead of direct upload
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
- // Generate presigned upload URL
115
- const presignedResult = await this.generateUploadUrl(fileName);
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(fileName, presignedResult.uploadUrl);
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":"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"}
1
+ {"version":3,"file":"gcs.driver.js","sourceRoot":"","sources":["../../src/drivers/gcs.driver.ts"],"names":[],"mappings":"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"}
@@ -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
- * Local storage driver for file system storage
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: any);
24
+ constructor(config: StorageConfig);
9
25
  /**
10
- * Upload file to local storage
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
- * Generate upload URL (not supported for local storage)
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
- generateUploadUrl(_fileName: string): Promise<PresignedUrlResult>;
42
+ private uploadWithStream;
17
43
  /**
18
- * Generate view URL (not supported for local storage)
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
- * Delete file from local storage
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
- delete(fileName: string): Promise<boolean>;
97
+ private resolveFilePath;
25
98
  /**
26
- * Find file path by searching through month directories
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
- private findFilePath;
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;AAGjF;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,iBAAiB;IACvD,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,GAAG;IAKvB;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAoClE;;OAEG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAMvE;;OAEG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAMrE;;OAEG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBhD;;OAEG;IACH,OAAO,CAAC,YAAY;CA+BrB"}
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"}