@veloxts/storage 0.6.91 → 0.6.92

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.
@@ -7,6 +7,7 @@
7
7
  import { createReadStream, createWriteStream } from 'node:fs';
8
8
  import { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
9
9
  import { dirname, join, relative } from 'node:path';
10
+ import { StorageObjectNotFoundError } from '../errors.js';
10
11
  import { detectMimeType, isReadableStream, normalizePath, toBuffer, validatePath, } from '../utils.js';
11
12
  /**
12
13
  * Default configuration for local storage.
@@ -93,6 +94,10 @@ export function createLocalStore(config) {
93
94
  }
94
95
  }
95
96
  const store = {
97
+ async init() {
98
+ // Create root directory if it doesn't exist
99
+ await mkdir(root, { recursive: true });
100
+ },
96
101
  async put(path, content, putOptions = {}) {
97
102
  const fullPath = getFullPath(path);
98
103
  await ensureDir(fullPath);
@@ -101,9 +106,20 @@ export function createLocalStore(config) {
101
106
  // Stream content to file
102
107
  await new Promise((resolve, reject) => {
103
108
  const writeStream = createWriteStream(fullPath);
104
- content.pipe(writeStream);
109
+ const readStream = content;
110
+ // Handle errors on both streams to prevent resource leaks
111
+ readStream.on('error', (err) => {
112
+ writeStream.destroy();
113
+ reject(err);
114
+ });
115
+ writeStream.on('error', (err) => {
116
+ if (typeof readStream.destroy === 'function') {
117
+ readStream.destroy();
118
+ }
119
+ reject(err);
120
+ });
105
121
  writeStream.on('finish', resolve);
106
- writeStream.on('error', reject);
122
+ readStream.pipe(writeStream);
107
123
  });
108
124
  }
109
125
  else {
@@ -236,6 +252,13 @@ export function createLocalStore(config) {
236
252
  return null;
237
253
  }
238
254
  },
255
+ async head(path) {
256
+ const result = await store.metadata(path);
257
+ if (result === null) {
258
+ throw new StorageObjectNotFoundError(path);
259
+ }
260
+ return result;
261
+ },
239
262
  async list(prefix = '', listOptions = {}) {
240
263
  const { recursive = false, limit = 1000 } = listOptions;
241
264
  const files = [];
@@ -286,6 +309,12 @@ export function createLocalStore(config) {
286
309
  // In production, you'd implement a token-based verification endpoint
287
310
  return store.url(path);
288
311
  },
312
+ async signedUploadUrl(options) {
313
+ // Local storage doesn't support true presigned upload URLs
314
+ // Return the URL where the file would be served from
315
+ // In production, you'd implement a token-based upload endpoint
316
+ return store.url(options.key);
317
+ },
289
318
  async setVisibility(path, visibility) {
290
319
  const storedMeta = (await readStoredMetadata(path)) ?? {
291
320
  visibility,
@@ -4,6 +4,7 @@
4
4
  * S3-compatible file storage implementation.
5
5
  * Works with AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, and other S3-compatible services.
6
6
  */
7
+ import { StorageObjectNotFoundError } from '../errors.js';
7
8
  import { detectMimeType, isReadableStream, joinPath, normalizePath, streamToBuffer, // Still needed for get()
8
9
  toBuffer, validatePath, } from '../utils.js';
9
10
  /**
@@ -50,7 +51,7 @@ const DEFAULT_CONFIG = {
50
51
  */
51
52
  export async function createS3Store(config) {
52
53
  const options = { ...DEFAULT_CONFIG, ...config };
53
- const { bucket, region, endpoint, accessKeyId, secretAccessKey, forcePathStyle, defaultVisibility, prefix, } = options;
54
+ const { bucket, region, endpoint, accessKeyId, secretAccessKey, forcePathStyle, defaultVisibility, prefix, publicUrl, } = options;
54
55
  // Dynamic import of AWS SDK (peer dependency)
55
56
  const { S3Client, CopyObjectCommand, DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, } = await import('@aws-sdk/client-s3');
56
57
  const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
@@ -108,6 +109,19 @@ export async function createS3Store(config) {
108
109
  // Import Upload for streaming multipart uploads (prevents memory exhaustion for large files)
109
110
  const { Upload } = await import('@aws-sdk/lib-storage');
110
111
  const store = {
112
+ async init() {
113
+ // Verify connectivity by listing a single object
114
+ try {
115
+ await client.send(new ListObjectsV2Command({
116
+ Bucket: bucket,
117
+ MaxKeys: 1,
118
+ }));
119
+ }
120
+ catch (error) {
121
+ const message = error instanceof Error ? error.message : 'Unknown error';
122
+ throw new Error(`S3 storage init failed for bucket "${bucket}": ${message}`);
123
+ }
124
+ },
111
125
  async put(path, content, putOptions = {}) {
112
126
  const key = getKey(path);
113
127
  const visibility = putOptions.visibility ?? defaultVisibility ?? 'private';
@@ -229,28 +243,33 @@ export async function createS3Store(config) {
229
243
  if (paths.length === 0) {
230
244
  return 0;
231
245
  }
232
- // S3 supports batch delete of up to 1000 objects
233
- const keys = paths.map((path) => ({ Key: getKey(path) }));
234
- try {
235
- const response = await client.send(new DeleteObjectsCommand({
236
- Bucket: bucket,
237
- Delete: {
238
- Objects: keys,
239
- Quiet: false,
240
- },
241
- }));
242
- return response.Deleted?.length ?? 0;
243
- }
244
- catch {
245
- // Fall back to individual deletes if batch fails
246
- let count = 0;
247
- const results = await Promise.all(paths.map((path) => store.delete(path)));
248
- for (const deleted of results) {
249
- if (deleted)
250
- count++;
246
+ // S3 supports batch delete of up to 1000 objects per request
247
+ const BATCH_SIZE = 1000;
248
+ let totalDeleted = 0;
249
+ // Process in batches to respect S3 API limit
250
+ for (let i = 0; i < paths.length; i += BATCH_SIZE) {
251
+ const batch = paths.slice(i, i + BATCH_SIZE);
252
+ const keys = batch.map((path) => ({ Key: getKey(path) }));
253
+ try {
254
+ const response = await client.send(new DeleteObjectsCommand({
255
+ Bucket: bucket,
256
+ Delete: {
257
+ Objects: keys,
258
+ Quiet: false,
259
+ },
260
+ }));
261
+ totalDeleted += response.Deleted?.length ?? 0;
262
+ }
263
+ catch {
264
+ // Fall back to individual deletes if batch fails
265
+ const results = await Promise.all(batch.map((path) => store.delete(path)));
266
+ for (const deleted of results) {
267
+ if (deleted)
268
+ totalDeleted++;
269
+ }
251
270
  }
252
- return count;
253
271
  }
272
+ return totalDeleted;
254
273
  },
255
274
  async copy(source, destination, copyOptions = {}) {
256
275
  const sourceKey = getKey(source);
@@ -260,8 +279,11 @@ export async function createS3Store(config) {
260
279
  const visibility = copyOptions.visibility ?? sourceMeta?.visibility ?? defaultVisibility ?? 'private';
261
280
  await client.send(new CopyObjectCommand({
262
281
  Bucket: bucket,
263
- // CopySource must be URL-encoded for special characters in paths
264
- CopySource: encodeURIComponent(`${bucket}/${sourceKey}`),
282
+ // CopySource format: bucket/key - encode key segments but not the bucket/key separator
283
+ CopySource: `${bucket}/${sourceKey
284
+ .split('/')
285
+ .map((s) => encodeURIComponent(s))
286
+ .join('/')}`,
265
287
  Key: destKey,
266
288
  ACL: visibilityToAcl(visibility),
267
289
  }));
@@ -294,6 +316,13 @@ export async function createS3Store(config) {
294
316
  throw error;
295
317
  }
296
318
  },
319
+ async head(path) {
320
+ const result = await store.metadata(path);
321
+ if (result === null) {
322
+ throw new StorageObjectNotFoundError(path);
323
+ }
324
+ return result;
325
+ },
297
326
  async list(listPrefix = '', listOptions = {}) {
298
327
  const { recursive = false, limit = 1000, cursor } = listOptions;
299
328
  const fullPrefix = prefix ? joinPath(prefix, listPrefix) : listPrefix;
@@ -322,7 +351,18 @@ export async function createS3Store(config) {
322
351
  },
323
352
  async url(path) {
324
353
  const key = getKey(path);
325
- return `${getBaseUrl()}/${key}`;
354
+ // URL-encode each path segment separately to preserve `/` delimiters
355
+ // but properly encode special characters like spaces, ?, #, etc.
356
+ const encodedKey = key
357
+ .split('/')
358
+ .map((segment) => encodeURIComponent(segment))
359
+ .join('/');
360
+ // Use custom publicUrl (e.g., CDN) if provided, otherwise use S3 URL
361
+ if (publicUrl) {
362
+ const base = publicUrl.endsWith('/') ? publicUrl.slice(0, -1) : publicUrl;
363
+ return `${base}/${encodedKey}`;
364
+ }
365
+ return `${getBaseUrl()}/${encodedKey}`;
326
366
  },
327
367
  async signedUrl(path, signedOptions = {}) {
328
368
  const key = getKey(path);
@@ -335,13 +375,26 @@ export async function createS3Store(config) {
335
375
  });
336
376
  return await getSignedUrl(client, command, { expiresIn });
337
377
  },
378
+ async signedUploadUrl(options) {
379
+ const { key, expiresIn = 3600, contentType } = options;
380
+ const fullKey = getKey(key);
381
+ const command = new PutObjectCommand({
382
+ Bucket: bucket,
383
+ Key: fullKey,
384
+ ContentType: contentType,
385
+ });
386
+ return await getSignedUrl(client, command, { expiresIn });
387
+ },
338
388
  async setVisibility(path, visibility) {
339
389
  const key = getKey(path);
340
390
  // S3 requires copying the object to itself to change ACL
341
391
  await client.send(new CopyObjectCommand({
342
392
  Bucket: bucket,
343
- // CopySource must be URL-encoded for special characters in paths
344
- CopySource: encodeURIComponent(`${bucket}/${key}`),
393
+ // CopySource format: bucket/key - encode key segments but not the bucket/key separator
394
+ CopySource: `${bucket}/${key
395
+ .split('/')
396
+ .map((s) => encodeURIComponent(s))
397
+ .join('/')}`,
345
398
  Key: key,
346
399
  ACL: visibilityToAcl(visibility),
347
400
  MetadataDirective: 'COPY',
package/dist/errors.d.ts CHANGED
@@ -70,6 +70,15 @@ export declare class S3Error extends StorageError {
70
70
  readonly statusCode?: number;
71
71
  constructor(operation: string, message: string, statusCode?: number);
72
72
  }
73
+ /**
74
+ * Error thrown when a storage object is not found.
75
+ * Used by head() and similar methods that require the object to exist.
76
+ */
77
+ export declare class StorageObjectNotFoundError extends StorageError {
78
+ readonly code = "STORAGE_OBJECT_NOT_FOUND";
79
+ readonly key: string;
80
+ constructor(key: string);
81
+ }
73
82
  /**
74
83
  * Type guard to check if an error is a StorageError.
75
84
  */
@@ -82,3 +91,7 @@ export declare function isFileNotFoundError(error: unknown): error is FileNotFou
82
91
  * Type guard to check if an error is a PermissionDeniedError.
83
92
  */
84
93
  export declare function isPermissionDeniedError(error: unknown): error is PermissionDeniedError;
94
+ /**
95
+ * Type guard to check if an error is a StorageObjectNotFoundError.
96
+ */
97
+ export declare function isStorageObjectNotFoundError(error: unknown): error is StorageObjectNotFoundError;
package/dist/errors.js CHANGED
@@ -99,6 +99,18 @@ export class S3Error extends StorageError {
99
99
  this.statusCode = statusCode;
100
100
  }
101
101
  }
102
+ /**
103
+ * Error thrown when a storage object is not found.
104
+ * Used by head() and similar methods that require the object to exist.
105
+ */
106
+ export class StorageObjectNotFoundError extends StorageError {
107
+ code = 'STORAGE_OBJECT_NOT_FOUND';
108
+ key;
109
+ constructor(key) {
110
+ super(`Storage object not found: ${key}`);
111
+ this.key = key;
112
+ }
113
+ }
102
114
  /**
103
115
  * Type guard to check if an error is a StorageError.
104
116
  */
@@ -117,3 +129,9 @@ export function isFileNotFoundError(error) {
117
129
  export function isPermissionDeniedError(error) {
118
130
  return error instanceof PermissionDeniedError;
119
131
  }
132
+ /**
133
+ * Type guard to check if an error is a StorageObjectNotFoundError.
134
+ */
135
+ export function isStorageObjectNotFoundError(error) {
136
+ return error instanceof StorageObjectNotFoundError;
137
+ }
package/dist/index.d.ts CHANGED
@@ -39,10 +39,10 @@
39
39
  */
40
40
  export { createLocalStore, DRIVER_NAME as LOCAL_DRIVER } from './drivers/local.js';
41
41
  export { createS3Store, DRIVER_NAME as S3_DRIVER } from './drivers/s3.js';
42
- export { FileExistsError, FileNotFoundError, InvalidPathError, isFileNotFoundError, isPermissionDeniedError, isStorageError, PermissionDeniedError, QuotaExceededError, S3Error, StorageConfigError, StorageError, } from './errors.js';
42
+ export { FileExistsError, FileNotFoundError, InvalidPathError, isFileNotFoundError, isPermissionDeniedError, isStorageError, isStorageObjectNotFoundError, PermissionDeniedError, QuotaExceededError, S3Error, StorageConfigError, StorageError, StorageObjectNotFoundError, } from './errors.js';
43
43
  export { createStorageManager, type StorageManager, storage } from './manager.js';
44
44
  export { _resetStandaloneStorage, closeStorage, getStorage, getStorageFromInstance, storagePlugin, } from './plugin.js';
45
- export type { CopyOptions, FileMetadata, FileVisibility, GetOptions, ListOptions, ListResult, LocalStorageConfig, PutOptions, S3StorageConfig, SignedUrlOptions, StorageBaseOptions, StorageConfig, StorageDefaultOptions, StorageDriver, StorageLocalOptions, StorageManagerOptions, StoragePluginOptions, StorageS3Options, StorageStore, } from './types.js';
45
+ export type { CopyOptions, FileMetadata, FileVisibility, GetOptions, ListOptions, ListResult, LocalStorageConfig, PutOptions, S3StorageConfig, SignedUploadOptions, SignedUrlOptions, StorageBaseOptions, StorageConfig, StorageDefaultOptions, StorageDriver, StorageLocalOptions, StorageManagerOptions, StoragePluginOptions, StorageProvider, StorageS3Options, StorageStore, } from './types.js';
46
46
  /**
47
47
  * Utility functions for storage operations.
48
48
  *
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@
41
41
  export { createLocalStore, DRIVER_NAME as LOCAL_DRIVER } from './drivers/local.js';
42
42
  export { createS3Store, DRIVER_NAME as S3_DRIVER } from './drivers/s3.js';
43
43
  // Errors
44
- export { FileExistsError, FileNotFoundError, InvalidPathError, isFileNotFoundError, isPermissionDeniedError, isStorageError, PermissionDeniedError, QuotaExceededError, S3Error, StorageConfigError, StorageError, } from './errors.js';
44
+ export { FileExistsError, FileNotFoundError, InvalidPathError, isFileNotFoundError, isPermissionDeniedError, isStorageError, isStorageObjectNotFoundError, PermissionDeniedError, QuotaExceededError, S3Error, StorageConfigError, StorageError, StorageObjectNotFoundError, } from './errors.js';
45
45
  // Manager
46
46
  export { createStorageManager, storage } from './manager.js';
47
47
  // Plugin
package/dist/manager.d.ts CHANGED
@@ -4,11 +4,17 @@
4
4
  * High-level file storage API with unified interface across all drivers.
5
5
  */
6
6
  import type { Readable } from 'node:stream';
7
- import type { CopyOptions, FileMetadata, FileVisibility, GetOptions, ListOptions, ListResult, PutOptions, SignedUrlOptions, StorageManagerOptions } from './types.js';
7
+ import type { CopyOptions, FileMetadata, FileVisibility, GetOptions, ListOptions, ListResult, PutOptions, SignedUploadOptions, SignedUrlOptions, StorageManagerOptions } from './types.js';
8
8
  /**
9
9
  * Storage manager interface providing a unified file storage API.
10
10
  */
11
11
  export interface StorageManager {
12
+ /**
13
+ * Initialize the storage manager.
14
+ * Called automatically by the plugin during registration.
15
+ * Can be called manually when using standalone storage.
16
+ */
17
+ init(): Promise<void>;
12
18
  /**
13
19
  * Upload a file.
14
20
  *
@@ -151,6 +157,28 @@ export interface StorageManager {
151
157
  * ```
152
158
  */
153
159
  metadata(path: string): Promise<FileMetadata | null>;
160
+ /**
161
+ * Get file metadata, throwing if not found.
162
+ * Unlike metadata() which returns null for missing files,
163
+ * head() throws StorageObjectNotFoundError.
164
+ *
165
+ * @param path - File path/key
166
+ * @returns File metadata
167
+ * @throws StorageObjectNotFoundError if file does not exist
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * try {
172
+ * const meta = await storage.head('uploads/image.jpg');
173
+ * console.log(`Size: ${meta.size}`);
174
+ * } catch (error) {
175
+ * if (isStorageObjectNotFoundError(error)) {
176
+ * console.log('File does not exist');
177
+ * }
178
+ * }
179
+ * ```
180
+ */
181
+ head(path: string): Promise<FileMetadata>;
154
182
  /**
155
183
  * List files in a directory/prefix.
156
184
  *
@@ -205,6 +233,30 @@ export interface StorageManager {
205
233
  * ```
206
234
  */
207
235
  signedUrl(path: string, options?: SignedUrlOptions): Promise<string>;
236
+ /**
237
+ * Get a signed/temporary URL for uploading a file directly to storage.
238
+ * Enables direct browser-to-storage uploads without proxying through the server.
239
+ *
240
+ * @param options - Signed upload URL options
241
+ * @returns Signed URL string for PUT upload
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * const uploadUrl = await storage.signedUploadUrl({
246
+ * key: 'uploads/user-123/avatar.jpg',
247
+ * contentType: 'image/jpeg',
248
+ * expiresIn: 300, // 5 minutes
249
+ * });
250
+ *
251
+ * // Client can now PUT directly to this URL
252
+ * await fetch(uploadUrl, {
253
+ * method: 'PUT',
254
+ * body: file,
255
+ * headers: { 'Content-Type': 'image/jpeg' },
256
+ * });
257
+ * ```
258
+ */
259
+ signedUploadUrl(options: SignedUploadOptions): Promise<string>;
208
260
  /**
209
261
  * Set file visibility.
210
262
  *
package/dist/manager.js CHANGED
@@ -85,6 +85,7 @@ export async function createStorageManager(options = {}) {
85
85
  }
86
86
  // Create manager that delegates to the store
87
87
  const manager = {
88
+ init: () => store.init?.() ?? Promise.resolve(),
88
89
  put: (path, content, putOptions) => store.put(path, content, putOptions),
89
90
  get: (path, getOptions) => store.get(path, getOptions),
90
91
  stream: (path, streamOptions) => store.stream(path, streamOptions),
@@ -94,9 +95,11 @@ export async function createStorageManager(options = {}) {
94
95
  copy: (source, destination, copyOptions) => store.copy(source, destination, copyOptions),
95
96
  move: (source, destination, moveOptions) => store.move(source, destination, moveOptions),
96
97
  metadata: (path) => store.metadata(path),
98
+ head: (path) => store.head(path),
97
99
  list: (prefix, listOptions) => store.list(prefix, listOptions),
98
100
  url: (path) => store.url(path),
99
101
  signedUrl: (path, signedOptions) => store.signedUrl(path, signedOptions),
102
+ signedUploadUrl: (options) => store.signedUploadUrl(options),
100
103
  setVisibility: (path, visibility) => store.setVisibility(path, visibility),
101
104
  getVisibility: (path) => store.getVisibility(path),
102
105
  makePublic: (path) => store.setVisibility(path, 'public'),
package/dist/plugin.js CHANGED
@@ -52,6 +52,8 @@ export function storagePlugin(options = {}) {
52
52
  return fp(async (fastify) => {
53
53
  // Create storage manager
54
54
  const storage = await createStorageManager(options);
55
+ // Initialize the storage (create directories, verify connectivity, etc.)
56
+ await storage.init();
55
57
  // Store on fastify instance
56
58
  fastify[STORAGE_KEY] = storage;
57
59
  // Decorate request with storage accessor
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Storage Providers
3
+ *
4
+ * Re-exports all storage provider factory functions.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { r2, minio } from '@veloxts/storage/providers';
9
+ *
10
+ * // Cloudflare R2
11
+ * const r2Storage = await r2({
12
+ * bucket: 'my-bucket',
13
+ * accountId: 'xxx',
14
+ * accessKeyId: 'xxx',
15
+ * secretAccessKey: 'xxx',
16
+ * });
17
+ *
18
+ * // MinIO
19
+ * const minioStorage = await minio({
20
+ * bucket: 'my-bucket',
21
+ * endpoint: 'http://localhost:9000',
22
+ * accessKeyId: 'minioadmin',
23
+ * secretAccessKey: 'minioadmin',
24
+ * });
25
+ * ```
26
+ */
27
+ export { createLocalStore, DRIVER_NAME as LOCAL_DRIVER } from '../drivers/local.js';
28
+ export { createS3Store, DRIVER_NAME as S3_DRIVER } from '../drivers/s3.js';
29
+ export { type MinIOConfig, minio, PROVIDER_NAME as MINIO_PROVIDER } from './minio.js';
30
+ export { PROVIDER_NAME as R2_PROVIDER, type R2Config, r2 } from './r2.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Storage Providers
3
+ *
4
+ * Re-exports all storage provider factory functions.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { r2, minio } from '@veloxts/storage/providers';
9
+ *
10
+ * // Cloudflare R2
11
+ * const r2Storage = await r2({
12
+ * bucket: 'my-bucket',
13
+ * accountId: 'xxx',
14
+ * accessKeyId: 'xxx',
15
+ * secretAccessKey: 'xxx',
16
+ * });
17
+ *
18
+ * // MinIO
19
+ * const minioStorage = await minio({
20
+ * bucket: 'my-bucket',
21
+ * endpoint: 'http://localhost:9000',
22
+ * accessKeyId: 'minioadmin',
23
+ * secretAccessKey: 'minioadmin',
24
+ * });
25
+ * ```
26
+ */
27
+ // Re-export the base drivers for convenience
28
+ export { createLocalStore, DRIVER_NAME as LOCAL_DRIVER } from '../drivers/local.js';
29
+ export { createS3Store, DRIVER_NAME as S3_DRIVER } from '../drivers/s3.js';
30
+ // Provider factories
31
+ export { minio, PROVIDER_NAME as MINIO_PROVIDER } from './minio.js';
32
+ export { PROVIDER_NAME as R2_PROVIDER, r2 } from './r2.js';
@@ -0,0 +1,74 @@
1
+ /**
2
+ * MinIO Storage Provider
3
+ *
4
+ * Factory function that creates an S3-compatible storage store configured
5
+ * for MinIO.
6
+ */
7
+ import { z } from 'zod';
8
+ import type { StorageStore } from '../types.js';
9
+ /**
10
+ * MinIO configuration schema.
11
+ */
12
+ declare const MinIOConfigSchema: z.ZodObject<{
13
+ /** MinIO bucket name */
14
+ bucket: z.ZodString;
15
+ /** MinIO endpoint (e.g., 'http://localhost:9000') */
16
+ endpoint: z.ZodString;
17
+ /** MinIO access key ID */
18
+ accessKeyId: z.ZodString;
19
+ /** MinIO secret access key */
20
+ secretAccessKey: z.ZodString;
21
+ /** AWS region (default: 'us-east-1') */
22
+ region: z.ZodDefault<z.ZodString>;
23
+ /** Custom public URL for accessing files */
24
+ publicUrl: z.ZodOptional<z.ZodString>;
25
+ /** Key prefix for all operations */
26
+ prefix: z.ZodOptional<z.ZodString>;
27
+ }, "strip", z.ZodTypeAny, {
28
+ bucket: string;
29
+ region: string;
30
+ endpoint: string;
31
+ accessKeyId: string;
32
+ secretAccessKey: string;
33
+ prefix?: string | undefined;
34
+ publicUrl?: string | undefined;
35
+ }, {
36
+ bucket: string;
37
+ endpoint: string;
38
+ accessKeyId: string;
39
+ secretAccessKey: string;
40
+ region?: string | undefined;
41
+ prefix?: string | undefined;
42
+ publicUrl?: string | undefined;
43
+ }>;
44
+ /**
45
+ * MinIO configuration options.
46
+ */
47
+ export type MinIOConfig = z.input<typeof MinIOConfigSchema>;
48
+ /**
49
+ * Create a MinIO storage store.
50
+ *
51
+ * @param config - MinIO configuration
52
+ * @returns Storage store implementation
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * import { minio } from '@veloxts/storage/providers/minio';
57
+ *
58
+ * const storage = await minio({
59
+ * bucket: 'my-bucket',
60
+ * endpoint: 'http://localhost:9000',
61
+ * accessKeyId: 'minioadmin',
62
+ * secretAccessKey: 'minioadmin',
63
+ * });
64
+ *
65
+ * await storage.init();
66
+ * await storage.put('uploads/file.txt', 'Hello World');
67
+ * ```
68
+ */
69
+ export declare function minio(config: MinIOConfig): Promise<StorageStore>;
70
+ /**
71
+ * MinIO provider name identifier.
72
+ */
73
+ export declare const PROVIDER_NAME: "minio";
74
+ export {};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * MinIO Storage Provider
3
+ *
4
+ * Factory function that creates an S3-compatible storage store configured
5
+ * for MinIO.
6
+ */
7
+ import { z } from 'zod';
8
+ import { createS3Store } from '../drivers/s3.js';
9
+ /**
10
+ * MinIO configuration schema.
11
+ */
12
+ const MinIOConfigSchema = z.object({
13
+ /** MinIO bucket name */
14
+ bucket: z.string().min(1, 'Bucket name is required'),
15
+ /** MinIO endpoint (e.g., 'http://localhost:9000') */
16
+ endpoint: z.string().url('Endpoint must be a valid URL'),
17
+ /** MinIO access key ID */
18
+ accessKeyId: z.string().min(1, 'Access key ID is required'),
19
+ /** MinIO secret access key */
20
+ secretAccessKey: z.string().min(1, 'Secret access key is required'),
21
+ /** AWS region (default: 'us-east-1') */
22
+ region: z.string().default('us-east-1'),
23
+ /** Custom public URL for accessing files */
24
+ publicUrl: z.string().url().optional(),
25
+ /** Key prefix for all operations */
26
+ prefix: z.string().optional(),
27
+ });
28
+ /**
29
+ * Create a MinIO storage store.
30
+ *
31
+ * @param config - MinIO configuration
32
+ * @returns Storage store implementation
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { minio } from '@veloxts/storage/providers/minio';
37
+ *
38
+ * const storage = await minio({
39
+ * bucket: 'my-bucket',
40
+ * endpoint: 'http://localhost:9000',
41
+ * accessKeyId: 'minioadmin',
42
+ * secretAccessKey: 'minioadmin',
43
+ * });
44
+ *
45
+ * await storage.init();
46
+ * await storage.put('uploads/file.txt', 'Hello World');
47
+ * ```
48
+ */
49
+ export async function minio(config) {
50
+ // Validate configuration with Zod
51
+ const validated = MinIOConfigSchema.parse(config);
52
+ // Create S3-compatible store with MinIO-specific configuration
53
+ return createS3Store({
54
+ driver: 's3',
55
+ bucket: validated.bucket,
56
+ region: validated.region,
57
+ endpoint: validated.endpoint,
58
+ accessKeyId: validated.accessKeyId,
59
+ secretAccessKey: validated.secretAccessKey,
60
+ forcePathStyle: true, // MinIO requires path-style URLs
61
+ prefix: validated.prefix,
62
+ publicUrl: validated.publicUrl,
63
+ });
64
+ }
65
+ /**
66
+ * MinIO provider name identifier.
67
+ */
68
+ export const PROVIDER_NAME = 'minio';